You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
814 lines
32 KiB
814 lines
32 KiB
12 years ago
|
"""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")
|