Minify on backend
This commit is contained in:
Executable
+813
@@ -0,0 +1,813 @@
|
||||
"""Selector is a single Selector of a CSSStyleRule SelectorList.
|
||||
Partly implements http://www.w3.org/TR/css3-selectors/.
|
||||
|
||||
TODO
|
||||
- .contains(selector)
|
||||
- .isSubselector(selector)
|
||||
"""
|
||||
__all__ = ['Selector']
|
||||
__docformat__ = 'restructuredtext'
|
||||
__version__ = '$Id$'
|
||||
|
||||
from cssutils.helper import Deprecated
|
||||
from cssutils.util import _SimpleNamespaces
|
||||
import cssutils
|
||||
import xml.dom
|
||||
|
||||
class Selector(cssutils.util.Base2):
|
||||
"""
|
||||
(cssutils) a single selector in a :class:`~cssutils.css.SelectorList`
|
||||
of a :class:`~cssutils.css.CSSStyleRule`.
|
||||
|
||||
Format::
|
||||
|
||||
# implemented in SelectorList
|
||||
selectors_group
|
||||
: selector [ COMMA S* selector ]*
|
||||
;
|
||||
|
||||
selector
|
||||
: simple_selector_sequence [ combinator simple_selector_sequence ]*
|
||||
;
|
||||
|
||||
combinator
|
||||
/* combinators can be surrounded by white space */
|
||||
: PLUS S* | GREATER S* | TILDE S* | S+
|
||||
;
|
||||
|
||||
simple_selector_sequence
|
||||
: [ type_selector | universal ]
|
||||
[ HASH | class | attrib | pseudo | negation ]*
|
||||
| [ HASH | class | attrib | pseudo | negation ]+
|
||||
;
|
||||
|
||||
type_selector
|
||||
: [ namespace_prefix ]? element_name
|
||||
;
|
||||
|
||||
namespace_prefix
|
||||
: [ IDENT | '*' ]? '|'
|
||||
;
|
||||
|
||||
element_name
|
||||
: IDENT
|
||||
;
|
||||
|
||||
universal
|
||||
: [ namespace_prefix ]? '*'
|
||||
;
|
||||
|
||||
class
|
||||
: '.' IDENT
|
||||
;
|
||||
|
||||
attrib
|
||||
: '[' S* [ namespace_prefix ]? IDENT S*
|
||||
[ [ PREFIXMATCH |
|
||||
SUFFIXMATCH |
|
||||
SUBSTRINGMATCH |
|
||||
'=' |
|
||||
INCLUDES |
|
||||
DASHMATCH ] S* [ IDENT | STRING ] S*
|
||||
]? ']'
|
||||
;
|
||||
|
||||
pseudo
|
||||
/* '::' starts a pseudo-element, ':' a pseudo-class */
|
||||
/* Exceptions: :first-line, :first-letter, :before and :after. */
|
||||
/* Note that pseudo-elements are restricted to one per selector and */
|
||||
/* occur only in the last simple_selector_sequence. */
|
||||
: ':' ':'? [ IDENT | functional_pseudo ]
|
||||
;
|
||||
|
||||
functional_pseudo
|
||||
: FUNCTION S* expression ')'
|
||||
;
|
||||
|
||||
expression
|
||||
/* In CSS3, the expressions are identifiers, strings, */
|
||||
/* or of the form "an+b" */
|
||||
: [ [ PLUS | '-' | DIMENSION | NUMBER | STRING | IDENT ] S* ]+
|
||||
;
|
||||
|
||||
negation
|
||||
: NOT S* negation_arg S* ')'
|
||||
;
|
||||
|
||||
negation_arg
|
||||
: type_selector | universal | HASH | class | attrib | pseudo
|
||||
;
|
||||
|
||||
"""
|
||||
def __init__(self, selectorText=None, parent=None,
|
||||
readonly=False):
|
||||
"""
|
||||
:Parameters:
|
||||
selectorText
|
||||
initial value of this selector
|
||||
parent
|
||||
a SelectorList
|
||||
readonly
|
||||
default to False
|
||||
"""
|
||||
super(Selector, self).__init__()
|
||||
|
||||
self.__namespaces = _SimpleNamespaces(log=self._log)
|
||||
self._element = None
|
||||
self._parent = parent
|
||||
self._specificity = (0, 0, 0, 0)
|
||||
|
||||
if selectorText:
|
||||
self.selectorText = selectorText
|
||||
|
||||
self._readonly = readonly
|
||||
|
||||
def __repr__(self):
|
||||
if self.__getNamespaces():
|
||||
st = (self.selectorText, self._getUsedNamespaces())
|
||||
else:
|
||||
st = self.selectorText
|
||||
return u"cssutils.css.%s(selectorText=%r)" % (self.__class__.__name__,
|
||||
st)
|
||||
|
||||
def __str__(self):
|
||||
return u"<cssutils.css.%s object selectorText=%r specificity=%r" \
|
||||
u" _namespaces=%r at 0x%x>" % (self.__class__.__name__,
|
||||
self.selectorText,
|
||||
self.specificity,
|
||||
self._getUsedNamespaces(),
|
||||
id(self))
|
||||
|
||||
def _getUsedUris(self):
|
||||
"Return list of actually used URIs in this Selector."
|
||||
uris = set()
|
||||
for item in self.seq:
|
||||
type_, val = item.type, item.value
|
||||
if type_.endswith(u'-selector') or type_ == u'universal' and \
|
||||
isinstance(val, tuple) and val[0] not in (None, u'*'):
|
||||
uris.add(val[0])
|
||||
return uris
|
||||
|
||||
def _getUsedNamespaces(self):
|
||||
"Return actually used namespaces only."
|
||||
useduris = self._getUsedUris()
|
||||
namespaces = _SimpleNamespaces(log=self._log)
|
||||
for p, uri in self._namespaces.items():
|
||||
if uri in useduris:
|
||||
namespaces[p] = uri
|
||||
return namespaces
|
||||
|
||||
def __getNamespaces(self):
|
||||
"Use own namespaces if not attached to a sheet, else the sheet's ones."
|
||||
try:
|
||||
return self._parent.parentRule.parentStyleSheet.namespaces
|
||||
except AttributeError:
|
||||
return self.__namespaces
|
||||
|
||||
_namespaces = property(__getNamespaces,
|
||||
doc=u"If this Selector is attached to a "
|
||||
u"CSSStyleSheet the namespaces of that sheet "
|
||||
u"are mirrored here. While the Selector (or "
|
||||
u"parent SelectorList or parentRule(s) of that "
|
||||
u"are not attached a own dict of {prefix: "
|
||||
u"namespaceURI} is used.")
|
||||
|
||||
|
||||
element = property(lambda self: self._element,
|
||||
doc=u"Effective element target of this selector.")
|
||||
|
||||
parent = property(lambda self: self._parent,
|
||||
doc=u"(DOM) The SelectorList that contains this Selector "
|
||||
u"or None if this Selector is not attached to a "
|
||||
u"SelectorList.")
|
||||
|
||||
def _getSelectorText(self):
|
||||
"""Return serialized format."""
|
||||
return cssutils.ser.do_css_Selector(self)
|
||||
|
||||
def _setSelectorText(self, selectorText):
|
||||
"""
|
||||
:param selectorText:
|
||||
parsable string or a tuple of (selectorText, dict-of-namespaces).
|
||||
Given namespaces are ignored if this object is attached to a
|
||||
CSSStyleSheet!
|
||||
|
||||
:exceptions:
|
||||
- :exc:`~xml.dom.NamespaceErr`:
|
||||
Raised if the specified selector uses an unknown namespace
|
||||
prefix.
|
||||
- :exc:`~xml.dom.SyntaxErr`:
|
||||
Raised if the specified CSS string value has a syntax error
|
||||
and is unparsable.
|
||||
- :exc:`~xml.dom.NoModificationAllowedErr`:
|
||||
Raised if this rule is readonly.
|
||||
"""
|
||||
self._checkReadonly()
|
||||
|
||||
# might be (selectorText, namespaces)
|
||||
selectorText, namespaces = self._splitNamespacesOff(selectorText)
|
||||
|
||||
try:
|
||||
# uses parent stylesheets namespaces if available,
|
||||
# otherwise given ones
|
||||
namespaces = self.parent.parentRule.parentStyleSheet.namespaces
|
||||
except AttributeError:
|
||||
pass
|
||||
tokenizer = self._tokenize2(selectorText)
|
||||
if not tokenizer:
|
||||
self._log.error(u'Selector: No selectorText given.')
|
||||
else:
|
||||
# prepare tokenlist:
|
||||
# "*" -> type "universal"
|
||||
# "*"|IDENT + "|" -> combined to "namespace_prefix"
|
||||
# "|" -> type "namespace_prefix"
|
||||
# "." + IDENT -> combined to "class"
|
||||
# ":" + IDENT, ":" + FUNCTION -> pseudo-class
|
||||
# FUNCTION "not(" -> negation
|
||||
# "::" + IDENT, "::" + FUNCTION -> pseudo-element
|
||||
tokens = []
|
||||
for t in tokenizer:
|
||||
typ, val, lin, col = t
|
||||
if val == u':' and tokens and\
|
||||
self._tokenvalue(tokens[-1]) == ':':
|
||||
# combine ":" and ":"
|
||||
tokens[-1] = (typ, u'::', lin, col)
|
||||
|
||||
elif typ == 'IDENT' and tokens\
|
||||
and self._tokenvalue(tokens[-1]) == u'.':
|
||||
# class: combine to .IDENT
|
||||
tokens[-1] = ('class', u'.'+val, lin, col)
|
||||
elif typ == 'IDENT' and tokens and \
|
||||
self._tokenvalue(tokens[-1]).startswith(u':') and\
|
||||
not self._tokenvalue(tokens[-1]).endswith(u'('):
|
||||
# pseudo-X: combine to :IDENT or ::IDENT but not ":a(" + "b"
|
||||
if self._tokenvalue(tokens[-1]).startswith(u'::'):
|
||||
t = 'pseudo-element'
|
||||
else:
|
||||
t = 'pseudo-class'
|
||||
tokens[-1] = (t, self._tokenvalue(tokens[-1])+val, lin, col)
|
||||
|
||||
elif typ == 'FUNCTION' and val == u'not(' and tokens and \
|
||||
u':' == self._tokenvalue(tokens[-1]):
|
||||
tokens[-1] = ('negation', u':' + val, lin, tokens[-1][3])
|
||||
elif typ == 'FUNCTION' and tokens\
|
||||
and self._tokenvalue(tokens[-1]).startswith(u':'):
|
||||
# pseudo-X: combine to :FUNCTION( or ::FUNCTION(
|
||||
if self._tokenvalue(tokens[-1]).startswith(u'::'):
|
||||
t = 'pseudo-element'
|
||||
else:
|
||||
t = 'pseudo-class'
|
||||
tokens[-1] = (t, self._tokenvalue(tokens[-1])+val, lin, col)
|
||||
|
||||
elif val == u'*' and tokens and\
|
||||
self._type(tokens[-1]) == 'namespace_prefix' and\
|
||||
self._tokenvalue(tokens[-1]).endswith(u'|'):
|
||||
# combine prefix|*
|
||||
tokens[-1] = ('universal', self._tokenvalue(tokens[-1])+val,
|
||||
lin, col)
|
||||
elif val == u'*':
|
||||
# universal: "*"
|
||||
tokens.append(('universal', val, lin, col))
|
||||
|
||||
elif val == u'|' and tokens and\
|
||||
self._type(tokens[-1]) in (self._prods.IDENT, 'universal')\
|
||||
and self._tokenvalue(tokens[-1]).find(u'|') == -1:
|
||||
# namespace_prefix: "IDENT|" or "*|"
|
||||
tokens[-1] = ('namespace_prefix',
|
||||
self._tokenvalue(tokens[-1])+u'|', lin, col)
|
||||
elif val == u'|':
|
||||
# namespace_prefix: "|"
|
||||
tokens.append(('namespace_prefix', val, lin, col))
|
||||
|
||||
else:
|
||||
tokens.append(t)
|
||||
|
||||
tokenizer = iter(tokens)
|
||||
|
||||
# for closures: must be a mutable
|
||||
new = {'context': [''], # stack of: 'attrib', 'negation', 'pseudo'
|
||||
'element': None,
|
||||
'_PREFIX': None,
|
||||
'specificity': [0, 0, 0, 0], # mutable, finally a tuple!
|
||||
'wellformed': True
|
||||
}
|
||||
# used for equality checks and setting of a space combinator
|
||||
S = u' '
|
||||
|
||||
def append(seq, val, typ=None, token=None):
|
||||
"""
|
||||
appends to seq
|
||||
|
||||
namespace_prefix, IDENT will be combined to a tuple
|
||||
(prefix, name) where prefix might be None, the empty string
|
||||
or a prefix.
|
||||
|
||||
Saved are also:
|
||||
- specificity definition: style, id, class/att, type
|
||||
- element: the element this Selector is for
|
||||
"""
|
||||
context = new['context'][-1]
|
||||
if token:
|
||||
line, col = token[2], token[3]
|
||||
else:
|
||||
line, col = None, None
|
||||
|
||||
if typ == '_PREFIX':
|
||||
# SPECIAL TYPE: save prefix for combination with next
|
||||
new['_PREFIX'] = val[:-1]
|
||||
# handle next time
|
||||
return
|
||||
|
||||
if new['_PREFIX'] is not None:
|
||||
# as saved from before and reset to None
|
||||
prefix, new['_PREFIX'] = new['_PREFIX'], None
|
||||
elif typ == 'universal' and '|' in val:
|
||||
# val == *|* or prefix|*
|
||||
prefix, val = val.split('|')
|
||||
else:
|
||||
prefix = None
|
||||
|
||||
# namespace
|
||||
if (typ.endswith('-selector') or typ == 'universal') and not (
|
||||
'attribute-selector' == typ and not prefix):
|
||||
# att **IS NOT** in default ns
|
||||
if prefix == u'*':
|
||||
# *|name: in ANY_NS
|
||||
namespaceURI = cssutils._ANYNS
|
||||
elif prefix is None:
|
||||
# e or *: default namespace with prefix u''
|
||||
# or local-name()
|
||||
namespaceURI = namespaces.get(u'', None)
|
||||
elif prefix == u'':
|
||||
# |name or |*: in no (or the empty) namespace
|
||||
namespaceURI = u''
|
||||
else:
|
||||
# explicit namespace prefix
|
||||
# does not raise KeyError, see _SimpleNamespaces
|
||||
namespaceURI = namespaces[prefix]
|
||||
|
||||
if namespaceURI is None:
|
||||
new['wellformed'] = False
|
||||
self._log.error(u'Selector: No namespaceURI found '
|
||||
u'for prefix %r' % prefix,
|
||||
token=token,
|
||||
error=xml.dom.NamespaceErr)
|
||||
return
|
||||
|
||||
# val is now (namespaceprefix, name) tuple
|
||||
val = (namespaceURI, val)
|
||||
|
||||
# specificity
|
||||
if not context or context == 'negation':
|
||||
if 'id' == typ:
|
||||
new['specificity'][1] += 1
|
||||
elif 'class' == typ or '[' == val:
|
||||
new['specificity'][2] += 1
|
||||
elif typ in ('type-selector', 'negation-type-selector',
|
||||
'pseudo-element'):
|
||||
new['specificity'][3] += 1
|
||||
if not context and typ in ('type-selector', 'universal'):
|
||||
# define element
|
||||
new['element'] = val
|
||||
|
||||
seq.append(val, typ, line=line, col=col)
|
||||
|
||||
# expected constants
|
||||
simple_selector_sequence = 'type_selector universal HASH class ' \
|
||||
'attrib pseudo negation '
|
||||
simple_selector_sequence2 = 'HASH class attrib pseudo negation '
|
||||
|
||||
element_name = 'element_name'
|
||||
|
||||
negation_arg = 'type_selector universal HASH class attrib pseudo'
|
||||
negationend = ')'
|
||||
|
||||
attname = 'prefix attribute'
|
||||
attname2 = 'attribute'
|
||||
attcombinator = 'combinator ]' # optional
|
||||
attvalue = 'value' # optional
|
||||
attend = ']'
|
||||
|
||||
expressionstart = 'PLUS - DIMENSION NUMBER STRING IDENT'
|
||||
expression = expressionstart + ' )'
|
||||
|
||||
combinator = ' combinator'
|
||||
|
||||
def _COMMENT(expected, seq, token, tokenizer=None):
|
||||
"special implementation for comment token"
|
||||
append(seq, cssutils.css.CSSComment([token]), 'COMMENT',
|
||||
token=token)
|
||||
return expected
|
||||
|
||||
def _S(expected, seq, token, tokenizer=None):
|
||||
# S
|
||||
context = new['context'][-1]
|
||||
if context.startswith('pseudo-'):
|
||||
if seq and seq[-1].value not in u'+-':
|
||||
# e.g. x:func(a + b)
|
||||
append(seq, S, 'S', token=token)
|
||||
return expected
|
||||
|
||||
elif context != 'attrib' and 'combinator' in expected:
|
||||
append(seq, S, 'descendant', token=token)
|
||||
return simple_selector_sequence + combinator
|
||||
|
||||
else:
|
||||
return expected
|
||||
|
||||
def _universal(expected, seq, token, tokenizer=None):
|
||||
# *|* or prefix|*
|
||||
context = new['context'][-1]
|
||||
val = self._tokenvalue(token)
|
||||
if 'universal' in expected:
|
||||
append(seq, val, 'universal', token=token)
|
||||
|
||||
if 'negation' == context:
|
||||
return negationend
|
||||
else:
|
||||
return simple_selector_sequence2 + combinator
|
||||
|
||||
else:
|
||||
new['wellformed'] = False
|
||||
self._log.error(
|
||||
u'Selector: Unexpected universal.', token=token)
|
||||
return expected
|
||||
|
||||
def _namespace_prefix(expected, seq, token, tokenizer=None):
|
||||
# prefix| => element_name
|
||||
# or prefix| => attribute_name if attrib
|
||||
context = new['context'][-1]
|
||||
val = self._tokenvalue(token)
|
||||
if 'attrib' == context and 'prefix' in expected:
|
||||
# [PREFIX|att]
|
||||
append(seq, val, '_PREFIX', token=token)
|
||||
return attname2
|
||||
elif 'type_selector' in expected:
|
||||
# PREFIX|*
|
||||
append(seq, val, '_PREFIX', token=token)
|
||||
return element_name
|
||||
else:
|
||||
new['wellformed'] = False
|
||||
self._log.error(
|
||||
u'Selector: Unexpected namespace prefix.', token=token)
|
||||
return expected
|
||||
|
||||
def _pseudo(expected, seq, token, tokenizer=None):
|
||||
# pseudo-class or pseudo-element :a ::a :a( ::a(
|
||||
"""
|
||||
/* '::' starts a pseudo-element, ':' a pseudo-class */
|
||||
/* Exceptions: :first-line, :first-letter, :before and
|
||||
:after. */
|
||||
/* Note that pseudo-elements are restricted to one per selector
|
||||
and */
|
||||
/* occur only in the last simple_selector_sequence. */
|
||||
"""
|
||||
context = new['context'][-1]
|
||||
val, typ = self._tokenvalue(token, normalize=True),\
|
||||
self._type(token)
|
||||
if 'pseudo' in expected:
|
||||
if val in (':first-line',
|
||||
':first-letter',
|
||||
':before',
|
||||
':after'):
|
||||
# always pseudo-element ???
|
||||
typ = 'pseudo-element'
|
||||
append(seq, val, typ, token=token)
|
||||
|
||||
if val.endswith(u'('):
|
||||
# function
|
||||
# "pseudo-" "class" or "element"
|
||||
new['context'].append(typ)
|
||||
return expressionstart
|
||||
elif 'negation' == context:
|
||||
return negationend
|
||||
elif 'pseudo-element' == typ:
|
||||
# only one per element, check at ) also!
|
||||
return combinator
|
||||
else:
|
||||
return simple_selector_sequence2 + combinator
|
||||
|
||||
else:
|
||||
new['wellformed'] = False
|
||||
self._log.error(
|
||||
u'Selector: Unexpected start of pseudo.', token=token)
|
||||
return expected
|
||||
|
||||
def _expression(expected, seq, token, tokenizer=None):
|
||||
# [ [ PLUS | '-' | DIMENSION | NUMBER | STRING | IDENT ] S* ]+
|
||||
context = new['context'][-1]
|
||||
val, typ = self._tokenvalue(token), self._type(token)
|
||||
if context.startswith('pseudo-'):
|
||||
append(seq, val, typ, token=token)
|
||||
return expression
|
||||
else:
|
||||
new['wellformed'] = False
|
||||
self._log.error(
|
||||
u'Selector: Unexpected %s.' % typ, token=token)
|
||||
return expected
|
||||
|
||||
def _attcombinator(expected, seq, token, tokenizer=None):
|
||||
# context: attrib
|
||||
# PREFIXMATCH | SUFFIXMATCH | SUBSTRINGMATCH | INCLUDES |
|
||||
# DASHMATCH
|
||||
context = new['context'][-1]
|
||||
val, typ = self._tokenvalue(token), self._type(token)
|
||||
if 'attrib' == context and 'combinator' in expected:
|
||||
# combinator in attrib
|
||||
append(seq, val, typ.lower(), token=token)
|
||||
return attvalue
|
||||
else:
|
||||
new['wellformed'] = False
|
||||
self._log.error(
|
||||
u'Selector: Unexpected %s.' % typ, token=token)
|
||||
return expected
|
||||
|
||||
def _string(expected, seq, token, tokenizer=None):
|
||||
# identifier
|
||||
context = new['context'][-1]
|
||||
typ, val = self._type(token), self._stringtokenvalue(token)
|
||||
|
||||
# context: attrib
|
||||
if 'attrib' == context and 'value' in expected:
|
||||
# attrib: [...=VALUE]
|
||||
append(seq, val, typ, token=token)
|
||||
return attend
|
||||
|
||||
# context: pseudo
|
||||
elif context.startswith('pseudo-'):
|
||||
# :func(...)
|
||||
append(seq, val, typ, token=token)
|
||||
return expression
|
||||
|
||||
else:
|
||||
new['wellformed'] = False
|
||||
self._log.error(
|
||||
u'Selector: Unexpected STRING.', token=token)
|
||||
return expected
|
||||
|
||||
def _ident(expected, seq, token, tokenizer=None):
|
||||
# identifier
|
||||
context = new['context'][-1]
|
||||
val, typ = self._tokenvalue(token), self._type(token)
|
||||
|
||||
# context: attrib
|
||||
if 'attrib' == context and 'attribute' in expected:
|
||||
# attrib: [...|ATT...]
|
||||
append(seq, val, 'attribute-selector', token=token)
|
||||
return attcombinator
|
||||
|
||||
elif 'attrib' == context and 'value' in expected:
|
||||
# attrib: [...=VALUE]
|
||||
append(seq, val, 'attribute-value', token=token)
|
||||
return attend
|
||||
|
||||
# context: negation
|
||||
elif 'negation' == context:
|
||||
# negation: (prefix|IDENT)
|
||||
append(seq, val, 'negation-type-selector', token=token)
|
||||
return negationend
|
||||
|
||||
# context: pseudo
|
||||
elif context.startswith('pseudo-'):
|
||||
# :func(...)
|
||||
append(seq, val, typ, token=token)
|
||||
return expression
|
||||
|
||||
elif 'type_selector' in expected or element_name == expected:
|
||||
# element name after ns or complete type_selector
|
||||
append(seq, val, 'type-selector', token=token)
|
||||
return simple_selector_sequence2 + combinator
|
||||
|
||||
else:
|
||||
new['wellformed'] = False
|
||||
self._log.error(u'Selector: Unexpected IDENT.', token=token)
|
||||
return expected
|
||||
|
||||
def _class(expected, seq, token, tokenizer=None):
|
||||
# .IDENT
|
||||
context = new['context'][-1]
|
||||
val = self._tokenvalue(token)
|
||||
if 'class' in expected:
|
||||
append(seq, val, 'class', token=token)
|
||||
|
||||
if 'negation' == context:
|
||||
return negationend
|
||||
else:
|
||||
return simple_selector_sequence2 + combinator
|
||||
|
||||
else:
|
||||
new['wellformed'] = False
|
||||
self._log.error(u'Selector: Unexpected class.', token=token)
|
||||
return expected
|
||||
|
||||
def _hash(expected, seq, token, tokenizer=None):
|
||||
# #IDENT
|
||||
context = new['context'][-1]
|
||||
val = self._tokenvalue(token)
|
||||
if 'HASH' in expected:
|
||||
append(seq, val, 'id', token=token)
|
||||
|
||||
if 'negation' == context:
|
||||
return negationend
|
||||
else:
|
||||
return simple_selector_sequence2 + combinator
|
||||
|
||||
else:
|
||||
new['wellformed'] = False
|
||||
self._log.error(u'Selector: Unexpected HASH.', token=token)
|
||||
return expected
|
||||
|
||||
def _char(expected, seq, token, tokenizer=None):
|
||||
# + > ~ ) [ ] + -
|
||||
context = new['context'][-1]
|
||||
val = self._tokenvalue(token)
|
||||
|
||||
# context: attrib
|
||||
if u']' == val and 'attrib' == context and ']' in expected:
|
||||
# end of attrib
|
||||
append(seq, val, 'attribute-end', token=token)
|
||||
context = new['context'].pop() # attrib is done
|
||||
context = new['context'][-1]
|
||||
if 'negation' == context:
|
||||
return negationend
|
||||
else:
|
||||
return simple_selector_sequence2 + combinator
|
||||
|
||||
elif u'=' == val and 'attrib' == context\
|
||||
and 'combinator' in expected:
|
||||
# combinator in attrib
|
||||
append(seq, val, 'equals', token=token)
|
||||
return attvalue
|
||||
|
||||
# context: negation
|
||||
elif u')' == val and 'negation' == context and u')' in expected:
|
||||
# not(negation_arg)"
|
||||
append(seq, val, 'negation-end', token=token)
|
||||
new['context'].pop() # negation is done
|
||||
context = new['context'][-1]
|
||||
return simple_selector_sequence + combinator
|
||||
|
||||
# context: pseudo (at least one expression)
|
||||
elif val in u'+-' and context.startswith('pseudo-'):
|
||||
# :func(+ -)"
|
||||
_names = {'+': 'plus', '-': 'minus'}
|
||||
if val == u'+' and seq and seq[-1].value == S:
|
||||
seq.replace(-1, val, _names[val])
|
||||
else:
|
||||
append(seq, val, _names[val],
|
||||
token=token)
|
||||
return expression
|
||||
|
||||
elif u')' == val and context.startswith('pseudo-') and\
|
||||
expression == expected:
|
||||
# :func(expression)"
|
||||
append(seq, val, 'function-end', token=token)
|
||||
new['context'].pop() # pseudo is done
|
||||
if 'pseudo-element' == context:
|
||||
return combinator
|
||||
else:
|
||||
return simple_selector_sequence + combinator
|
||||
|
||||
# context: ROOT
|
||||
elif u'[' == val and 'attrib' in expected:
|
||||
# start of [attrib]
|
||||
append(seq, val, 'attribute-start', token=token)
|
||||
new['context'].append('attrib')
|
||||
return attname
|
||||
|
||||
elif val in u'+>~' and 'combinator' in expected:
|
||||
# no other combinator except S may be following
|
||||
_names = {
|
||||
'>': 'child',
|
||||
'+': 'adjacent-sibling',
|
||||
'~': 'following-sibling'}
|
||||
if seq and seq[-1].value == S:
|
||||
seq.replace(-1, val, _names[val])
|
||||
else:
|
||||
append(seq, val, _names[val], token=token)
|
||||
return simple_selector_sequence
|
||||
|
||||
elif u',' == val:
|
||||
# not a selectorlist
|
||||
new['wellformed'] = False
|
||||
self._log.error(
|
||||
u'Selector: Single selector only.',
|
||||
error=xml.dom.InvalidModificationErr,
|
||||
token=token)
|
||||
return expected
|
||||
|
||||
else:
|
||||
new['wellformed'] = False
|
||||
self._log.error(
|
||||
u'Selector: Unexpected CHAR.', token=token)
|
||||
return expected
|
||||
|
||||
def _negation(expected, seq, token, tokenizer=None):
|
||||
# not(
|
||||
context = new['context'][-1]
|
||||
val = self._tokenvalue(token, normalize=True)
|
||||
if 'negation' in expected:
|
||||
new['context'].append('negation')
|
||||
append(seq, val, 'negation-start', token=token)
|
||||
return negation_arg
|
||||
else:
|
||||
new['wellformed'] = False
|
||||
self._log.error(
|
||||
u'Selector: Unexpected negation.', token=token)
|
||||
return expected
|
||||
|
||||
def _atkeyword(expected, seq, token, tokenizer=None):
|
||||
"invalidates selector"
|
||||
new['wellformed'] = False
|
||||
self._log.error(
|
||||
u'Selector: Unexpected ATKEYWORD.', token=token)
|
||||
return expected
|
||||
|
||||
|
||||
# expected: only|not or mediatype, mediatype, feature, and
|
||||
newseq = self._tempSeq()
|
||||
|
||||
wellformed, expected = self._parse(
|
||||
expected=simple_selector_sequence,
|
||||
seq=newseq, tokenizer=tokenizer,
|
||||
productions={'CHAR': _char,
|
||||
'class': _class,
|
||||
'HASH': _hash,
|
||||
'STRING': _string,
|
||||
'IDENT': _ident,
|
||||
'namespace_prefix': _namespace_prefix,
|
||||
'negation': _negation,
|
||||
'pseudo-class': _pseudo,
|
||||
'pseudo-element': _pseudo,
|
||||
'universal': _universal,
|
||||
# pseudo
|
||||
'NUMBER': _expression,
|
||||
'DIMENSION': _expression,
|
||||
# attribute
|
||||
'PREFIXMATCH': _attcombinator,
|
||||
'SUFFIXMATCH': _attcombinator,
|
||||
'SUBSTRINGMATCH': _attcombinator,
|
||||
'DASHMATCH': _attcombinator,
|
||||
'INCLUDES': _attcombinator,
|
||||
|
||||
'S': _S,
|
||||
'COMMENT': _COMMENT,
|
||||
'ATKEYWORD': _atkeyword})
|
||||
wellformed = wellformed and new['wellformed']
|
||||
|
||||
# post condition
|
||||
if len(new['context']) > 1 or not newseq:
|
||||
wellformed = False
|
||||
self._log.error(u'Selector: Invalid or incomplete selector: %s'
|
||||
% self._valuestr(selectorText))
|
||||
|
||||
if expected == 'element_name':
|
||||
wellformed = False
|
||||
self._log.error(u'Selector: No element name found: %s'
|
||||
% self._valuestr(selectorText))
|
||||
|
||||
if expected == simple_selector_sequence and newseq:
|
||||
wellformed = False
|
||||
self._log.error(u'Selector: Cannot end with combinator: %s'
|
||||
% self._valuestr(selectorText))
|
||||
|
||||
if newseq and hasattr(newseq[-1].value, 'strip') \
|
||||
and newseq[-1].value.strip() == u'':
|
||||
del newseq[-1]
|
||||
|
||||
# set
|
||||
if wellformed:
|
||||
self.__namespaces = namespaces
|
||||
self._element = new['element']
|
||||
self._specificity = tuple(new['specificity'])
|
||||
self._setSeq(newseq)
|
||||
# filter that only used ones are kept
|
||||
self.__namespaces = self._getUsedNamespaces()
|
||||
|
||||
selectorText = property(_getSelectorText, _setSelectorText,
|
||||
doc=u"(DOM) The parsable textual representation of "
|
||||
u"the selector.")
|
||||
|
||||
specificity = property(lambda self: self._specificity,
|
||||
doc="""Specificity of this selector (READONLY).
|
||||
Tuple of (a, b, c, d) where:
|
||||
|
||||
a
|
||||
presence of style in document, always 0 if not used on a
|
||||
document
|
||||
b
|
||||
number of ID selectors
|
||||
c
|
||||
number of .class selectors
|
||||
d
|
||||
number of Element (type) selectors""")
|
||||
|
||||
wellformed = property(lambda self: bool(len(self.seq)))
|
||||
|
||||
|
||||
@Deprecated('Use property parent instead')
|
||||
def _getParentList(self):
|
||||
return self.parent
|
||||
|
||||
parentList = property(_getParentList,
|
||||
doc="DEPRECATED, see property parent instead")
|
||||
Reference in New Issue
Block a user