def _productions(self): """Return definition used for parsing.""" types = self._prods # rename! func = Prod(name='MSValue-Sub', match=lambda t, v: t == self._prods.FUNCTION, toSeq=lambda t, tokens: (MSValue._functionName, MSValue(pushtoken(t, tokens), parent=self))) funcProds = Sequence( Prod(name='FUNCTION', match=lambda t, v: t == types.FUNCTION, toSeq=lambda t, tokens: (t[0], t[1])), Sequence( Choice( _ColorProd(self), _DimensionProd(self), _URIProd(self), _ValueProd(self), _MSValueProd(self), # _CalcValueProd(self), _CSSVariableProd(self), func, # _CSSFunctionProd(self), Prod(name='MSValuePart', match=lambda t, v: v != ')', toSeq=lambda t, tokens: (t[0], t[1]))), minmax=lambda: (0, None)), PreDef.funcEnd(stop=True)) return funcProds
def test_init(self): "Choice.__init__()" p1 = Prod('p1', lambda t, v: t == 1) p2 = Prod('p2', lambda t, v: t == 2) t0 = (0, 0, 0, 0) t1 = (1, 0, 0, 0) t2 = (2, 0, 0, 0) ch = Choice(p1, p2) self.assertRaisesMsg(ParseError, 'No match for (0, 0, 0, 0) in Choice(p1, p2)', ch.nextProd, t0) self.assertEqual(p1, ch.nextProd(t1)) self.assertRaisesMsg(Exhausted, 'Extra token', ch.nextProd, t1) ch = Choice(p1, p2) self.assertEqual(p2, ch.nextProd(t2)) self.assertRaisesMsg(Exhausted, 'Extra token', ch.nextProd, t2) ch = Choice(p2, p1) self.assertRaisesMsg(ParseError, 'No match for (0, 0, 0, 0) in Choice(p2, p1)', ch.nextProd, t0) self.assertEqual(p1, ch.nextProd(t1)) self.assertRaisesMsg(Exhausted, 'Extra token', ch.nextProd, t1) ch = Choice(p2, p1) self.assertEqual(p2, ch.nextProd(t2)) self.assertRaisesMsg(Exhausted, 'Extra token', ch.nextProd, t2)
def test_initToStore(self): "Prod.__init__(...toStore=...)" p = Prod('all', lambda t, v: True, toStore='key') # save as key s = {} p.toStore(s, 1) self.assertEqual(s['key'], 1) # append to key s = {'key': []} p.toStore(s, 1) p.toStore(s, 2) self.assertEqual(s['key'], [1, 2]) # callback def doubleToStore(key): def toStore(store, item): store[key] = item * 2 return toStore p = Prod('all', lambda t, v: True, toStore=doubleToStore('key')) s = {'key': []} p.toStore(s, 1) self.assertEqual(s['key'], 2)
def test_init(self): "Prod.__init__(...)" p = Prod('min', lambda t, v: t == 1 and v == 2) self.assertEqual(str(p), 'min') self.assertEqual(p.toStore, None) self.assertEqual(p.optional, False) p = Prod('optional', lambda t, v: True, optional=True) self.assertEqual(p.optional, True)
def _setCssText(self, cssText): self._checkReadonly() types = self._prods # rename! _operator = Choice(Prod(name='Operator */', match=lambda t, v: v in '*/', toSeq=lambda t, tokens: (t[0], t[1]) ), Sequence( PreDef.S(), Choice( Sequence( Prod(name='Operator */', match=lambda t, v: v in '*/', toSeq=lambda t, tokens: (t[0], t[1]) ), PreDef.S(optional=True) ), Sequence( Prod(name='Operator +-', match=lambda t, v: v in '+-', toSeq=lambda t, tokens: (t[0], t[1]) ), PreDef.S() ), PreDef.funcEnd(stop=True, mayEnd=True) ) ) ) def _operant(): return Choice(_DimensionProd(self), _CSSVariableProd(self)) prods = Sequence(Prod(name='CALC', match=lambda t, v: t == types.FUNCTION and normalize(v) == 'calc(' ), PreDef.S(optional=True), _operant(), Sequence(_operator, _operant(), minmax=lambda: (0, None) ), PreDef.funcEnd(stop=True) ) # store: name of variable ok, seq, store, unused = ProdParser().parse(cssText, 'CSSCalc', prods, checkS=True) self.wellformed = ok if ok: self._setSeq(seq)
def test_reset(self): "Sequence.reset()" p1 = Prod('p1', lambda t, v: t == 1) p2 = Prod('p2', lambda t, v: t == 2) seq = Sequence(p1, p2) t1 = (1, 0, 0, 0) t2 = (2, 0, 0, 0) self.assertEqual(p1, seq.nextProd(t1)) self.assertEqual(p2, seq.nextProd(t2)) self.assertRaises(Exhausted, seq.nextProd, t1) seq.reset() self.assertEqual(p1, seq.nextProd(t1))
def test_reset(self): "Choice.reset()" p1 = Prod('p1', lambda t, v: t == 1) p2 = Prod('p2', lambda t, v: t == 2) t1 = (1, 0, 0, 0) t2 = (2, 0, 0, 0) ch = Choice(p1, p2) self.assertEqual(p1, ch.nextProd(t1)) self.assertRaises(Exhausted, ch.nextProd, t1) ch.reset() self.assertEqual(p2, ch.nextProd(t2))
def test_matches(self): "Prod.matches(token)" p1 = Prod('p1', lambda t, v: t == 1 and v == 2) p2 = Prod('p2', lambda t, v: t == 1 and v == 2, optional=True) self.assertEqual(p1.matches([1, 2, 0, 0]), True) self.assertEqual(p2.matches([1, 2, 0, 0]), True) self.assertEqual(p1.matches([0, 0, 0, 0]), False) self.assertEqual(p2.matches([0, 0, 0, 0]), False)
def _MSValueProd(parent, nextSor=False): return Prod(name=MSValue._functionName, match=lambda t, v: ( # t == self._prods.FUNCTION and ( normalize(v) in ('expression(', 'alpha(', 'blur(', 'chroma(', 'dropshadow(', 'fliph(', 'flipv(', 'glow(', 'gray(', 'invert(', 'mask(', 'shadow(', 'wave(', 'xray(') or v.startswith('progid:DXImageTransform.Microsoft.') ), nextSor=nextSor, toSeq=lambda t, tokens: (MSValue._functionName, MSValue(pushtoken(t, tokens ), parent=parent ) ) )
def test_init(self): "Sequence.__init__()" p1 = Prod('p1', lambda t, v: t == 1) seq = Sequence(p1, p1) self.assertEqual(1, seq._min) self.assertEqual(1, seq._max)
def _URIProd(parent, nextSor=False, toStore=None): return Prod(name='URIValue', match=lambda t, v: t == 'URI', toStore=toStore, nextSor=nextSor, toSeq=lambda t, tokens: ('URIValue', URIValue(pushtoken(t, tokens), parent=parent)))
def _ValueProd(parent, nextSor=False, toStore=None): return Prod(name='Value', match=lambda t, v: t in ('IDENT', 'STRING', 'UNICODE-RANGE'), nextSor=nextSor, toStore=toStore, toSeq=lambda t, tokens: ('Value', Value(pushtoken(t, tokens), parent=parent)))
def _setCssText(self, cssText): self._checkReadonly() types = self._prods # rename! prods = Sequence( Prod(name='var', match=lambda t, v: t == types.FUNCTION and normalize(v) == 'var('), PreDef.ident(toStore='ident'), Sequence(PreDef.comma(), Choice(_ColorProd(self, toStore='fallback'), _DimensionProd(self, toStore='fallback'), _URIProd(self, toStore='fallback'), _ValueProd(self, toStore='fallback'), _CalcValueProd(self, toStore='fallback'), _CSSVariableProd(self, toStore='fallback'), _CSSFunctionProd(self, toStore='fallback')), minmax=lambda: (0, 1)), PreDef.funcEnd(stop=True)) # store: name of variable store = {'ident': None, 'fallback': None} ok, seq, store, unused = ProdParser().parse(cssText, 'CSSVariable', prods) self.wellformed = ok if ok: self._name = store['ident'].value try: self._fallback = store['fallback'].value except KeyError: self._fallback = None self._setSeq(seq)
def mediaquery(): return Prod(name='MediaQueryStart', match=lambda t, v: t == 'IDENT' or v == '(', toSeq=lambda t, tokens: ('MediaQuery', MediaQuery(pushtoken(t, tokens), _partof=True)) ) prods = Sequence(Sequence(PreDef.comment(parent=self),
def _DimensionProd(parent, nextSor=False, toStore=None): return Prod( name='Dimension', match=lambda t, v: t in ('DIMENSION', 'NUMBER', 'PERCENTAGE'), nextSor=nextSor, toStore=toStore, toSeq=lambda t, tokens: ('DIMENSION', DimensionValue(pushtoken(t, tokens), parent=parent)))
def _CalcValueProd(parent, nextSor=False, toStore=None): return Prod( name=CSSCalc._functionName, match=lambda t, v: t == PreDef.types.FUNCTION and normalize( v) == 'calc(', toStore=toStore, toSeq=lambda t, tokens: (CSSCalc._functionName, CSSCalc(pushtoken(t, tokens), parent=parent)), nextSor=nextSor)
def test_matches(self): "Choice.matches()" p1 = Prod('p1', lambda t, v: t == 1) p2 = Prod('p2', lambda t, v: t == 2, optional=True) t1 = (1, 0, 0, 0) t2 = (2, 0, 0, 0) t3 = (3, 0, 0, 0) c = Choice(p1, p2) self.assertEqual(True, c.matches(t1)) self.assertEqual(True, c.matches(t2)) self.assertEqual(False, c.matches(t3)) c = Choice(Sequence(p1), Sequence(p2)) self.assertEqual(True, c.matches(t1)) self.assertEqual(True, c.matches(t2)) self.assertEqual(False, c.matches(t3))
def test_initminmax(self): "Sequence.__init__(...minmax=...)" p1 = Prod('p1', lambda t, v: t == 1) p2 = Prod('p2', lambda t, v: t == 2) s = Sequence(p1, p2, minmax=lambda: (2, 3)) self.assertEqual(2, s._min) self.assertEqual(3, s._max) s = Sequence(p1, p2, minmax=lambda: (0, None)) self.assertEqual(0, s._min) try: # py2.6/3 m = sys.maxsize except AttributeError: # py<1.6 m = sys.maxsize self.assertEqual(m, s._max)
def test_optional(self): "Sequence.optional" p1 = Prod('p1', lambda t, v: t == 1) s = Sequence(p1, minmax=lambda: (1, 3)) self.assertEqual(False, s.optional) s = Sequence(p1, minmax=lambda: (0, 3)) self.assertEqual(True, s.optional) s = Sequence(p1, minmax=lambda: (0, None)) self.assertEqual(True, s.optional)
def _ColorProd(parent, nextSor=False, toStore=None): return Prod( name='ColorValue', match=lambda t, v: (t == 'HASH' and reHexcolor.match(v)) or (t == 'FUNCTION' and normalize(v) in ('rgb(', 'rgba(', 'hsl(', 'hsla(')) or (t == 'IDENT' and normalize(v) in as_list(ColorValue.COLORS.keys())), nextSor=nextSor, toStore=toStore, toSeq=lambda t, tokens: ('ColorValue', ColorValue(pushtoken(t, tokens), parent=parent)))
def test_nested(self): "Choice with nested Sequence" p1 = Prod('p1', lambda t, v: t == 1) p2 = Prod('p2', lambda t, v: t == 2) s1 = Sequence(p1, p1) s2 = Sequence(p2, p2) t0 = (0, 0, 0, 0) t1 = (1, 0, 0, 0) t2 = (2, 0, 0, 0) ch = Choice(s1, s2) self.assertRaisesMsg( ParseError, 'No match for (0, 0, 0, 0) in Choice(Sequence(p1, p1), Sequence(p2, p2))', ch.nextProd, t0) self.assertEqual(s1, ch.nextProd(t1)) self.assertRaisesMsg(Exhausted, 'Extra token', ch.nextProd, t1) ch = Choice(s1, s2) self.assertEqual(s2, ch.nextProd(t2)) self.assertRaisesMsg(Exhausted, 'Extra token', ch.nextProd, t1)
def expression(): return Sequence( PreDef.char(name='expression', char='('), Prod(name='media_feature', match=lambda t, v: t == PreDef.types.IDENT), Sequence( PreDef.char(name='colon', char=':'), css_parser.css.value.MediaQueryValueProd(self), minmax=lambda: (0, 1) # optional ), PreDef.char(name='expression END', char=')', stopIfNoMoreMatch=self._partof))
def test_initToSeq(self): "Prod.__init__(...toSeq=...)" # simply saves p = Prod('all', lambda t, tokens: True, toSeq=None) self.assertEqual(p.toSeq([1, 2], None), (1, 2)) # simply saves self.assertEqual(p.toSeq(['s1', 's2'], None), ('s1', 's2')) # simply saves # saves callback(val) p = Prod('all', lambda t, v: True, toSeq=lambda t, tokens: (1 == t[0], 3 == t[1])) self.assertEqual(p.toSeq([1, 3], None), (True, True)) self.assertEqual(p.toSeq([2, 4], None), (False, False))
def _productions(self): """Return definition used for parsing.""" types = self._prods # rename! itemProd = Choice(_ColorProd(self), _DimensionProd(self), _URIProd(self), _ValueProd(self), _CalcValueProd(self), _CSSVariableProd(self), _CSSFunctionProd(self)) funcProds = Sequence( Prod(name='FUNCTION', match=lambda t, v: t == types.FUNCTION, toSeq=lambda t, tokens: (t[0], normalize(t[1]))), Choice( Sequence( itemProd, Sequence(PreDef.comma(optional=True), itemProd, minmax=lambda: (0, None)), PreDef.funcEnd(stop=True)), PreDef.funcEnd(stop=True))) return funcProds
def _setCssText(self, cssText): """ :exceptions: - :exc:`~xml.dom.SyntaxErr`: Raised if the specified CSS string value has a syntax error and is unparsable. - :exc:`~xml.dom.InvalidModificationErr`: Raised if the specified CSS string value represents a different type of rule than the current one. - :exc:`~xml.dom.HierarchyRequestErr`: Raised if the rule cannot be inserted at this point in the style sheet. - :exc:`~xml.dom.NoModificationAllowedErr`: Raised if the rule is readonly. """ super(MarginRule, self)._setCssText(cssText) # TEMP: all style tokens are saved in store to fill styledeclaration # TODO: resolve when all generators styletokens = Prod(name='styletokens', match=lambda t, v: v != '}', # toSeq=False, toStore='styletokens', storeToken=True ) prods = Sequence(Prod(name='@ margin', match=lambda t, v: t == 'ATKEYWORD' and self._normalize(v) in MarginRule.margins, toStore='margin' # TODO? # , exception=xml.dom.InvalidModificationErr ), PreDef.char('OPEN', '{'), Sequence(Choice(PreDef.unknownrule(toStore='@'), styletokens), minmax=lambda: (0, None) ), PreDef.char('CLOSE', '}', stopAndKeep=True) ) # parse ok, seq, store, unused = ProdParser().parse(cssText, 'MarginRule', prods) if ok: # TODO: use seq for serializing instead of fixed stuff? self._setSeq(seq) if 'margin' in store: # may raise: self.margin = store['margin'].value else: self._log.error('No margin @keyword for this %s rule' % self.margin, error=xml.dom.InvalidModificationErr) # new empty style self.style = CSSStyleDeclaration(parentRule=self) if 'styletokens' in store: # may raise: self.style.cssText = store['styletokens']
def mediaquery(): return Prod( name='MediaQueryStart', match=lambda t, v: t == 'IDENT' or v == '(', toSeq=lambda t, tokens: ('MediaQuery', MediaQuery(pushtoken(t, tokens), _partof=True)))
def _setMediaText(self, mediaText): """ :param mediaText: a single media query string, e.g. ``print and (min-width: 25cm)`` :exceptions: - :exc:`~xml.dom.SyntaxErr`: Raised if the specified string value has a syntax error and is unparsable. - :exc:`~xml.dom.InvalidCharacterErr`: Raised if the given mediaType is unknown. - :exc:`~xml.dom.NoModificationAllowedErr`: Raised if this media query is readonly. media_query : [ONLY | NOT]? S* media_type S* [ AND S* expression ]* | expression [ AND S* expression ]* ; media_type : IDENT ; expression : '(' S* media_feature S* [ ':' S* expr ]? ')' S* ; media_feature : IDENT ; """ self._checkReadonly() def expression(): return Sequence( PreDef.char(name='expression', char='('), Prod(name='media_feature', match=lambda t, v: t == PreDef.types.IDENT), Sequence( PreDef.char(name='colon', char=':'), css_parser.css.value.MediaQueryValueProd(self), minmax=lambda: (0, 1) # optional ), PreDef.char(name='expression END', char=')', stopIfNoMoreMatch=self._partof)) prods = Choice( Sequence( Prod( name='ONLY|NOT', # media_query match=lambda t, v: t == PreDef.types.IDENT and normalize(v) in ('only', 'not'), optional=True, toStore='not simple'), Prod(name='media_type', match=lambda t, v: t == PreDef.types.IDENT and normalize( v) in self.MEDIA_TYPES, stopIfNoMoreMatch=True, toStore='media_type'), Sequence(Prod(name='AND', match=lambda t, v: t == PreDef.types.IDENT and normalize(v) == 'and', toStore='not simple'), expression(), minmax=lambda: (0, None))), Sequence( expression(), Sequence(Prod(name='AND', match=lambda t, v: t == PreDef.types.IDENT and normalize(v) == 'and'), expression(), minmax=lambda: (0, None))), Sequence( Prod( name='ONLY|NOT', # media_query match=lambda t, v: t == PreDef.types.IDENT and normalize(v) in ('only', 'not'), optional=True, toStore='not simple'), Prod(name='media_type', match=lambda t, v: t == PreDef.types.IDENT, toStore='media_type'), Sequence(Prod(name='AND', match=lambda t, v: t == PreDef.types.IDENT and normalize(v) == 'and', toStore='not simple'), expression(), minmax=lambda: (0, None)))) # parse ok, seq, store, unused = ProdParser().parse(mediaText, 'MediaQuery', prods) self._wellformed = ok if ok: try: media_type = store['media_type'] except KeyError: pass else: if 'not simple' not in store: self.mediaType = media_type.value # TODO: filter doubles! self._setSeq(seq)
def test_nextProd(self): "Sequence.nextProd()" p1 = Prod('p1', lambda t, v: t == 1, optional=True) p2 = Prod('p2', lambda t, v: t == 2) t1 = (1, 0, 0, 0) t2 = (2, 0, 0, 0) tests = { # seq: list of list of (token, prod or error msg) ( p1, ): ( [(t1, p1)], [(t2, 'Extra token')], # as p1 optional [(t1, p1), (t1, 'Extra token')], [(t1, p1), (t2, 'Extra token')]), (p2, ): ([(t2, p2)], [(t2, p2), (t2, 'Extra token')], [ (t2, p2), (t1, 'Extra token') ], [(t1, 'Missing token for production p2')]), (p1, p2): ([(t1, p1), (t2, p2)], [(t1, p1), (t1, 'Missing token for production p2')]) } for seqitems, results in tests.items(): for result in results: seq = Sequence(*seqitems) for t, p in result: if isinstance(p, basestring): self.assertRaisesMsg(ParseError, p, seq.nextProd, t) else: self.assertEqual(p, seq.nextProd(t)) tests = { # seq: list of list of (token, prod or error msg) # as p1 optional! (p1, p1): ( [(t1, p1)], [(t1, p1), (t1, p1)], [(t1, p1), (t1, p1)], [(t1, p1), (t1, p1), (t1, p1)], [(t1, p1), (t1, p1), (t1, p1), (t1, p1)], [(t1, p1), (t1, p1), (t1, p1), (t1, p1), (t1, 'Extra token')], ), (p1, ): ([(t1, p1)], [(t2, 'Extra token')], [(t1, p1), (t1, p1)], [ (t1, p1), (t2, 'Extra token') ], [(t1, p1), (t1, p1), (t1, 'Extra token')], [(t1, p1), (t1, p1), (t2, 'Extra token')]), # as p2 NOT optional ( p2, ): ([(t2, p2)], [(t1, 'Missing token for production p2')], [(t2, p2), (t2, p2)], [(t2, p2), (t1, 'No match for (1, 0, 0, 0) in Sequence(p2)')], [ (t2, p2), (t2, p2), (t2, 'Extra token') ], [(t2, p2), (t2, p2), (t1, 'Extra token')]), (p1, p2): ( [(t1, p1), (t1, 'Missing token for production p2')], [(t2, p2), (t2, p2)], [(t2, p2), (t1, p1), (t2, p2)], [(t1, p1), (t2, p2), (t2, p2)], [(t1, p1), (t2, p2), (t1, p1), (t2, p2)], [(t2, p2), (t2, p2), (t2, 'Extra token')], [(t2, p2), (t1, p1), (t2, p2), (t1, 'Extra token')], [(t2, p2), (t1, p1), (t2, p2), (t2, 'Extra token')], [(t1, p1), (t2, p2), (t2, p2), (t1, 'Extra token')], [(t1, p1), (t2, p2), (t2, p2), (t2, 'Extra token')], [(t1, p1), (t2, p2), (t1, p1), (t2, p2), (t1, 'Extra token')], [(t1, p1), (t2, p2), (t1, p1), (t2, p2), (t2, 'Extra token')], ) } for seqitems, results in tests.items(): for result in results: seq = Sequence(minmax=lambda: (1, 2), *seqitems) for t, p in result: if isinstance(p, basestring): self.assertRaisesMsg(ParseError, p, seq.nextProd, t) else: self.assertEqual(p, seq.nextProd(t))
def test_combi(self): "ProdParser.parse() 2" p1 = Prod('p1', lambda t, v: v == '1') p2 = Prod('p2', lambda t, v: v == '2') p3 = Prod('p3', lambda t, v: v == '3') tests = { '1 2': True, '1 2 1 2': True, '3': True, # '': 'No match in Choice(Sequence(p1, p2), p3)', '1': 'Missing token for production p2', '1 2 1': 'Missing token for production p2', '1 2 1 2 x': "No match: ('IDENT', 'x', 1, 9)", '1 2 1 2 1': "No match: ('NUMBER', '1', 1, 9)", '3 x': "No match: ('IDENT', 'x', 1, 3)", '3 3': "No match: ('NUMBER', '3', 1, 3)", } for text, exp in tests.items(): if sys.version_info.major == 2 and hasattr(exp, 'replace'): exp = exp.replace("('", "(u'").replace(" '", " u'") prods = Choice(Sequence(p1, p2, minmax=lambda: (1, 2)), p3) if exp is True: wellformed, seq, store, unused = ProdParser().parse( text, 'T', prods) self.assertEqual(wellformed, exp) else: self.assertRaisesMsg(xml.dom.SyntaxErr, 'T: %s' % exp, ProdParser().parse, text, 'T', prods) tests = { '1 3': True, '1 1 3': True, '2 3': True, '1': 'Missing token for production p3', '1 1': 'Missing token for production p3', '1 3 3': "No match: ('NUMBER', '3', 1, 5)", '1 1 3 3': "No match: ('NUMBER', '3', 1, 7)", '2 3 3': "No match: ('NUMBER', '3', 1, 5)", '2': 'Missing token for production p3', '3': "Missing token for production Choice(Sequence(p1), p2): ('NUMBER', '3', 1, 1)", } for text, exp in tests.items(): if sys.version_info.major == 2 and hasattr(exp, 'replace'): exp = exp.replace("('", "(u'").replace(" '", " u'") prods = Sequence(Choice(Sequence(p1, minmax=lambda: (1, 2)), p2), p3) if exp is True: wellformed, seq, store, unused = ProdParser().parse( text, 'T', prods) self.assertEqual(wellformed, exp) else: self.assertRaisesMsg(xml.dom.SyntaxErr, 'T: %s' % exp, ProdParser().parse, text, 'T', prods)
def test_initMatch(self): "Prod.__init__(...match=...)" p = Prod('min', lambda t, v: t == 1 and v == 2) self.assertEqual(p.match(1, 2), True) self.assertEqual(p.match(2, 2), False) self.assertEqual(p.match(1, 1), False)