gvsig-scripting / org.gvsig.scripting / trunk / org.gvsig.scripting / org.gvsig.scripting.app / org.gvsig.scripting.app.mainplugin / src / main / resources-plugin / scripting / lib / cssutils / css / selector.py @ 475
History | View | Annotate | Download (32.5 KB)
1 |
"""Selector is a single Selector of a CSSStyleRule SelectorList.
|
---|---|
2 |
Partly implements http://www.w3.org/TR/css3-selectors/.
|
3 |
|
4 |
TODO
|
5 |
- .contains(selector)
|
6 |
- .isSubselector(selector)
|
7 |
"""
|
8 |
__all__ = ['Selector']
|
9 |
__docformat__ = 'restructuredtext'
|
10 |
__version__ = '$Id$'
|
11 |
|
12 |
from cssutils.helper import Deprecated |
13 |
from cssutils.util import _SimpleNamespaces |
14 |
import cssutils |
15 |
import xml.dom |
16 |
|
17 |
class Selector(cssutils.util.Base2): |
18 |
"""
|
19 |
(cssutils) a single selector in a :class:`~cssutils.css.SelectorList`
|
20 |
of a :class:`~cssutils.css.CSSStyleRule`.
|
21 |
|
22 |
Format::
|
23 |
|
24 |
# implemented in SelectorList
|
25 |
selectors_group
|
26 |
: selector [ COMMA S* selector ]*
|
27 |
;
|
28 |
|
29 |
selector
|
30 |
: simple_selector_sequence [ combinator simple_selector_sequence ]*
|
31 |
;
|
32 |
|
33 |
combinator
|
34 |
/* combinators can be surrounded by white space */
|
35 |
: PLUS S* | GREATER S* | TILDE S* | S+
|
36 |
;
|
37 |
|
38 |
simple_selector_sequence
|
39 |
: [ type_selector | universal ]
|
40 |
[ HASH | class | attrib | pseudo | negation ]*
|
41 |
| [ HASH | class | attrib | pseudo | negation ]+
|
42 |
;
|
43 |
|
44 |
type_selector
|
45 |
: [ namespace_prefix ]? element_name
|
46 |
;
|
47 |
|
48 |
namespace_prefix
|
49 |
: [ IDENT | '*' ]? '|'
|
50 |
;
|
51 |
|
52 |
element_name
|
53 |
: IDENT
|
54 |
;
|
55 |
|
56 |
universal
|
57 |
: [ namespace_prefix ]? '*'
|
58 |
;
|
59 |
|
60 |
class
|
61 |
: '.' IDENT
|
62 |
;
|
63 |
|
64 |
attrib
|
65 |
: '[' S* [ namespace_prefix ]? IDENT S*
|
66 |
[ [ PREFIXMATCH |
|
67 |
SUFFIXMATCH |
|
68 |
SUBSTRINGMATCH |
|
69 |
'=' |
|
70 |
INCLUDES |
|
71 |
DASHMATCH ] S* [ IDENT | STRING ] S*
|
72 |
]? ']'
|
73 |
;
|
74 |
|
75 |
pseudo
|
76 |
/* '::' starts a pseudo-element, ':' a pseudo-class */
|
77 |
/* Exceptions: :first-line, :first-letter, :before and :after. */
|
78 |
/* Note that pseudo-elements are restricted to one per selector and */
|
79 |
/* occur only in the last simple_selector_sequence. */
|
80 |
: ':' ':'? [ IDENT | functional_pseudo ]
|
81 |
;
|
82 |
|
83 |
functional_pseudo
|
84 |
: FUNCTION S* expression ')'
|
85 |
;
|
86 |
|
87 |
expression
|
88 |
/* In CSS3, the expressions are identifiers, strings, */
|
89 |
/* or of the form "an+b" */
|
90 |
: [ [ PLUS | '-' | DIMENSION | NUMBER | STRING | IDENT ] S* ]+
|
91 |
;
|
92 |
|
93 |
negation
|
94 |
: NOT S* negation_arg S* ')'
|
95 |
;
|
96 |
|
97 |
negation_arg
|
98 |
: type_selector | universal | HASH | class | attrib | pseudo
|
99 |
;
|
100 |
|
101 |
"""
|
102 |
def __init__(self, selectorText=None, parent=None, |
103 |
readonly=False):
|
104 |
"""
|
105 |
:Parameters:
|
106 |
selectorText
|
107 |
initial value of this selector
|
108 |
parent
|
109 |
a SelectorList
|
110 |
readonly
|
111 |
default to False
|
112 |
"""
|
113 |
super(Selector, self).__init__() |
114 |
|
115 |
self.__namespaces = _SimpleNamespaces(log=self._log) |
116 |
self._element = None |
117 |
self._parent = parent
|
118 |
self._specificity = (0, 0, 0, 0) |
119 |
|
120 |
if selectorText:
|
121 |
self.selectorText = selectorText
|
122 |
|
123 |
self._readonly = readonly
|
124 |
|
125 |
def __repr__(self): |
126 |
if self.__getNamespaces(): |
127 |
st = (self.selectorText, self._getUsedNamespaces()) |
128 |
else:
|
129 |
st = self.selectorText
|
130 |
return u"cssutils.css.%s(selectorText=%r)" % (self.__class__.__name__, |
131 |
st) |
132 |
|
133 |
def __str__(self): |
134 |
return u"<cssutils.css.%s object selectorText=%r specificity=%r" \ |
135 |
u" _namespaces=%r at 0x%x>" % (self.__class__.__name__, |
136 |
self.selectorText,
|
137 |
self.specificity,
|
138 |
self._getUsedNamespaces(),
|
139 |
id(self)) |
140 |
|
141 |
def _getUsedUris(self): |
142 |
"Return list of actually used URIs in this Selector."
|
143 |
uris = set()
|
144 |
for item in self.seq: |
145 |
type_, val = item.type, item.value |
146 |
if type_.endswith(u'-selector') or type_ == u'universal' and \ |
147 |
isinstance(val, tuple) and val[0] not in (None, u'*'): |
148 |
uris.add(val[0])
|
149 |
return uris
|
150 |
|
151 |
def _getUsedNamespaces(self): |
152 |
"Return actually used namespaces only."
|
153 |
useduris = self._getUsedUris()
|
154 |
namespaces = _SimpleNamespaces(log=self._log)
|
155 |
for p, uri in self._namespaces.items(): |
156 |
if uri in useduris: |
157 |
namespaces[p] = uri |
158 |
return namespaces
|
159 |
|
160 |
def __getNamespaces(self): |
161 |
"Use own namespaces if not attached to a sheet, else the sheet's ones."
|
162 |
try:
|
163 |
return self._parent.parentRule.parentStyleSheet.namespaces |
164 |
except AttributeError: |
165 |
return self.__namespaces |
166 |
|
167 |
_namespaces = property(__getNamespaces,
|
168 |
doc=u"If this Selector is attached to a "
|
169 |
u"CSSStyleSheet the namespaces of that sheet "
|
170 |
u"are mirrored here. While the Selector (or "
|
171 |
u"parent SelectorList or parentRule(s) of that "
|
172 |
u"are not attached a own dict of {prefix: "
|
173 |
u"namespaceURI} is used.")
|
174 |
|
175 |
|
176 |
element = property(lambda self: self._element, |
177 |
doc=u"Effective element target of this selector.")
|
178 |
|
179 |
parent = property(lambda self: self._parent, |
180 |
doc=u"(DOM) The SelectorList that contains this Selector "
|
181 |
u"or None if this Selector is not attached to a "
|
182 |
u"SelectorList.")
|
183 |
|
184 |
def _getSelectorText(self): |
185 |
"""Return serialized format."""
|
186 |
return cssutils.ser.do_css_Selector(self) |
187 |
|
188 |
def _setSelectorText(self, selectorText): |
189 |
"""
|
190 |
:param selectorText:
|
191 |
parsable string or a tuple of (selectorText, dict-of-namespaces).
|
192 |
Given namespaces are ignored if this object is attached to a
|
193 |
CSSStyleSheet!
|
194 |
|
195 |
:exceptions:
|
196 |
- :exc:`~xml.dom.NamespaceErr`:
|
197 |
Raised if the specified selector uses an unknown namespace
|
198 |
prefix.
|
199 |
- :exc:`~xml.dom.SyntaxErr`:
|
200 |
Raised if the specified CSS string value has a syntax error
|
201 |
and is unparsable.
|
202 |
- :exc:`~xml.dom.NoModificationAllowedErr`:
|
203 |
Raised if this rule is readonly.
|
204 |
"""
|
205 |
self._checkReadonly()
|
206 |
|
207 |
# might be (selectorText, namespaces)
|
208 |
selectorText, namespaces = self._splitNamespacesOff(selectorText)
|
209 |
|
210 |
try:
|
211 |
# uses parent stylesheets namespaces if available,
|
212 |
# otherwise given ones
|
213 |
namespaces = self.parent.parentRule.parentStyleSheet.namespaces
|
214 |
except AttributeError: |
215 |
pass
|
216 |
tokenizer = self._tokenize2(selectorText)
|
217 |
if not tokenizer: |
218 |
self._log.error(u'Selector: No selectorText given.') |
219 |
else:
|
220 |
# prepare tokenlist:
|
221 |
# "*" -> type "universal"
|
222 |
# "*"|IDENT + "|" -> combined to "namespace_prefix"
|
223 |
# "|" -> type "namespace_prefix"
|
224 |
# "." + IDENT -> combined to "class"
|
225 |
# ":" + IDENT, ":" + FUNCTION -> pseudo-class
|
226 |
# FUNCTION "not(" -> negation
|
227 |
# "::" + IDENT, "::" + FUNCTION -> pseudo-element
|
228 |
tokens = [] |
229 |
for t in tokenizer: |
230 |
typ, val, lin, col = t |
231 |
if val == u':' and tokens and\ |
232 |
self._tokenvalue(tokens[-1]) == ':': |
233 |
# combine ":" and ":"
|
234 |
tokens[-1] = (typ, u'::', lin, col) |
235 |
|
236 |
elif typ == 'IDENT' and tokens\ |
237 |
and self._tokenvalue(tokens[-1]) == u'.': |
238 |
# class: combine to .IDENT
|
239 |
tokens[-1] = ('class', u'.'+val, lin, col) |
240 |
elif typ == 'IDENT' and tokens and \ |
241 |
self._tokenvalue(tokens[-1]).startswith(u':') and\ |
242 |
not self._tokenvalue(tokens[-1]).endswith(u'('): |
243 |
# pseudo-X: combine to :IDENT or ::IDENT but not ":a(" + "b"
|
244 |
if self._tokenvalue(tokens[-1]).startswith(u'::'): |
245 |
t = 'pseudo-element'
|
246 |
else:
|
247 |
t = 'pseudo-class'
|
248 |
tokens[-1] = (t, self._tokenvalue(tokens[-1])+val, lin, col) |
249 |
|
250 |
elif typ == 'FUNCTION' and val == u'not(' and tokens and \ |
251 |
u':' == self._tokenvalue(tokens[-1]): |
252 |
tokens[-1] = ('negation', u':' + val, lin, tokens[-1][3]) |
253 |
elif typ == 'FUNCTION' and tokens\ |
254 |
and self._tokenvalue(tokens[-1]).startswith(u':'): |
255 |
# pseudo-X: combine to :FUNCTION( or ::FUNCTION(
|
256 |
if self._tokenvalue(tokens[-1]).startswith(u'::'): |
257 |
t = 'pseudo-element'
|
258 |
else:
|
259 |
t = 'pseudo-class'
|
260 |
tokens[-1] = (t, self._tokenvalue(tokens[-1])+val, lin, col) |
261 |
|
262 |
elif val == u'*' and tokens and\ |
263 |
self._type(tokens[-1]) == 'namespace_prefix' and\ |
264 |
self._tokenvalue(tokens[-1]).endswith(u'|'): |
265 |
# combine prefix|*
|
266 |
tokens[-1] = ('universal', self._tokenvalue(tokens[-1])+val, |
267 |
lin, col) |
268 |
elif val == u'*': |
269 |
# universal: "*"
|
270 |
tokens.append(('universal', val, lin, col))
|
271 |
|
272 |
elif val == u'|' and tokens and\ |
273 |
self._type(tokens[-1]) in (self._prods.IDENT, 'universal')\ |
274 |
and self._tokenvalue(tokens[-1]).find(u'|') == -1: |
275 |
# namespace_prefix: "IDENT|" or "*|"
|
276 |
tokens[-1] = ('namespace_prefix', |
277 |
self._tokenvalue(tokens[-1])+u'|', lin, col) |
278 |
elif val == u'|': |
279 |
# namespace_prefix: "|"
|
280 |
tokens.append(('namespace_prefix', val, lin, col))
|
281 |
|
282 |
else:
|
283 |
tokens.append(t) |
284 |
|
285 |
tokenizer = iter(tokens)
|
286 |
|
287 |
# for closures: must be a mutable
|
288 |
new = {'context': [''], # stack of: 'attrib', 'negation', 'pseudo' |
289 |
'element': None, |
290 |
'_PREFIX': None, |
291 |
'specificity': [0, 0, 0, 0], # mutable, finally a tuple! |
292 |
'wellformed': True |
293 |
} |
294 |
# used for equality checks and setting of a space combinator
|
295 |
S = u' '
|
296 |
|
297 |
def append(seq, val, typ=None, token=None): |
298 |
"""
|
299 |
appends to seq
|
300 |
|
301 |
namespace_prefix, IDENT will be combined to a tuple
|
302 |
(prefix, name) where prefix might be None, the empty string
|
303 |
or a prefix.
|
304 |
|
305 |
Saved are also:
|
306 |
- specificity definition: style, id, class/att, type
|
307 |
- element: the element this Selector is for
|
308 |
"""
|
309 |
context = new['context'][-1] |
310 |
if token:
|
311 |
line, col = token[2], token[3] |
312 |
else:
|
313 |
line, col = None, None |
314 |
|
315 |
if typ == '_PREFIX': |
316 |
# SPECIAL TYPE: save prefix for combination with next
|
317 |
new['_PREFIX'] = val[:-1] |
318 |
# handle next time
|
319 |
return
|
320 |
|
321 |
if new['_PREFIX'] is not None: |
322 |
# as saved from before and reset to None
|
323 |
prefix, new['_PREFIX'] = new['_PREFIX'], None |
324 |
elif typ == 'universal' and '|' in val: |
325 |
# val == *|* or prefix|*
|
326 |
prefix, val = val.split('|')
|
327 |
else:
|
328 |
prefix = None
|
329 |
|
330 |
# namespace
|
331 |
if (typ.endswith('-selector') or typ == 'universal') and not ( |
332 |
'attribute-selector' == typ and not prefix): |
333 |
# att **IS NOT** in default ns
|
334 |
if prefix == u'*': |
335 |
# *|name: in ANY_NS
|
336 |
namespaceURI = cssutils._ANYNS |
337 |
elif prefix is None: |
338 |
# e or *: default namespace with prefix u''
|
339 |
# or local-name()
|
340 |
namespaceURI = namespaces.get(u'', None) |
341 |
elif prefix == u'': |
342 |
# |name or |*: in no (or the empty) namespace
|
343 |
namespaceURI = u''
|
344 |
else:
|
345 |
# explicit namespace prefix
|
346 |
# does not raise KeyError, see _SimpleNamespaces
|
347 |
namespaceURI = namespaces[prefix] |
348 |
|
349 |
if namespaceURI is None: |
350 |
new['wellformed'] = False |
351 |
self._log.error(u'Selector: No namespaceURI found ' |
352 |
u'for prefix %r' % prefix,
|
353 |
token=token, |
354 |
error=xml.dom.NamespaceErr) |
355 |
return
|
356 |
|
357 |
# val is now (namespaceprefix, name) tuple
|
358 |
val = (namespaceURI, val) |
359 |
|
360 |
# specificity
|
361 |
if not context or context == 'negation': |
362 |
if 'id' == typ: |
363 |
new['specificity'][1] += 1 |
364 |
elif 'class' == typ or '[' == val: |
365 |
new['specificity'][2] += 1 |
366 |
elif typ in ('type-selector', 'negation-type-selector', |
367 |
'pseudo-element'):
|
368 |
new['specificity'][3] += 1 |
369 |
if not context and typ in ('type-selector', 'universal'): |
370 |
# define element
|
371 |
new['element'] = val
|
372 |
|
373 |
seq.append(val, typ, line=line, col=col) |
374 |
|
375 |
# expected constants
|
376 |
simple_selector_sequence = 'type_selector universal HASH class ' \
|
377 |
'attrib pseudo negation '
|
378 |
simple_selector_sequence2 = 'HASH class attrib pseudo negation '
|
379 |
|
380 |
element_name = 'element_name'
|
381 |
|
382 |
negation_arg = 'type_selector universal HASH class attrib pseudo'
|
383 |
negationend = ')'
|
384 |
|
385 |
attname = 'prefix attribute'
|
386 |
attname2 = 'attribute'
|
387 |
attcombinator = 'combinator ]' # optional |
388 |
attvalue = 'value' # optional |
389 |
attend = ']'
|
390 |
|
391 |
expressionstart = 'PLUS - DIMENSION NUMBER STRING IDENT'
|
392 |
expression = expressionstart + ' )'
|
393 |
|
394 |
combinator = ' combinator'
|
395 |
|
396 |
def _COMMENT(expected, seq, token, tokenizer=None): |
397 |
"special implementation for comment token"
|
398 |
append(seq, cssutils.css.CSSComment([token]), 'COMMENT',
|
399 |
token=token) |
400 |
return expected
|
401 |
|
402 |
def _S(expected, seq, token, tokenizer=None): |
403 |
# S
|
404 |
context = new['context'][-1] |
405 |
if context.startswith('pseudo-'): |
406 |
if seq and seq[-1].value not in u'+-': |
407 |
# e.g. x:func(a + b)
|
408 |
append(seq, S, 'S', token=token)
|
409 |
return expected
|
410 |
|
411 |
elif context != 'attrib' and 'combinator' in expected: |
412 |
append(seq, S, 'descendant', token=token)
|
413 |
return simple_selector_sequence + combinator
|
414 |
|
415 |
else:
|
416 |
return expected
|
417 |
|
418 |
def _universal(expected, seq, token, tokenizer=None): |
419 |
# *|* or prefix|*
|
420 |
context = new['context'][-1] |
421 |
val = self._tokenvalue(token)
|
422 |
if 'universal' in expected: |
423 |
append(seq, val, 'universal', token=token)
|
424 |
|
425 |
if 'negation' == context: |
426 |
return negationend
|
427 |
else:
|
428 |
return simple_selector_sequence2 + combinator
|
429 |
|
430 |
else:
|
431 |
new['wellformed'] = False |
432 |
self._log.error(
|
433 |
u'Selector: Unexpected universal.', token=token)
|
434 |
return expected
|
435 |
|
436 |
def _namespace_prefix(expected, seq, token, tokenizer=None): |
437 |
# prefix| => element_name
|
438 |
# or prefix| => attribute_name if attrib
|
439 |
context = new['context'][-1] |
440 |
val = self._tokenvalue(token)
|
441 |
if 'attrib' == context and 'prefix' in expected: |
442 |
# [PREFIX|att]
|
443 |
append(seq, val, '_PREFIX', token=token)
|
444 |
return attname2
|
445 |
elif 'type_selector' in expected: |
446 |
# PREFIX|*
|
447 |
append(seq, val, '_PREFIX', token=token)
|
448 |
return element_name
|
449 |
else:
|
450 |
new['wellformed'] = False |
451 |
self._log.error(
|
452 |
u'Selector: Unexpected namespace prefix.', token=token)
|
453 |
return expected
|
454 |
|
455 |
def _pseudo(expected, seq, token, tokenizer=None): |
456 |
# pseudo-class or pseudo-element :a ::a :a( ::a(
|
457 |
"""
|
458 |
/* '::' starts a pseudo-element, ':' a pseudo-class */
|
459 |
/* Exceptions: :first-line, :first-letter, :before and
|
460 |
:after. */
|
461 |
/* Note that pseudo-elements are restricted to one per selector
|
462 |
and */
|
463 |
/* occur only in the last simple_selector_sequence. */
|
464 |
"""
|
465 |
context = new['context'][-1] |
466 |
val, typ = self._tokenvalue(token, normalize=True),\ |
467 |
self._type(token)
|
468 |
if 'pseudo' in expected: |
469 |
if val in (':first-line', |
470 |
':first-letter',
|
471 |
':before',
|
472 |
':after'):
|
473 |
# always pseudo-element ???
|
474 |
typ = 'pseudo-element'
|
475 |
append(seq, val, typ, token=token) |
476 |
|
477 |
if val.endswith(u'('): |
478 |
# function
|
479 |
# "pseudo-" "class" or "element"
|
480 |
new['context'].append(typ)
|
481 |
return expressionstart
|
482 |
elif 'negation' == context: |
483 |
return negationend
|
484 |
elif 'pseudo-element' == typ: |
485 |
# only one per element, check at ) also!
|
486 |
return combinator
|
487 |
else:
|
488 |
return simple_selector_sequence2 + combinator
|
489 |
|
490 |
else:
|
491 |
new['wellformed'] = False |
492 |
self._log.error(
|
493 |
u'Selector: Unexpected start of pseudo.', token=token)
|
494 |
return expected
|
495 |
|
496 |
def _expression(expected, seq, token, tokenizer=None): |
497 |
# [ [ PLUS | '-' | DIMENSION | NUMBER | STRING | IDENT ] S* ]+
|
498 |
context = new['context'][-1] |
499 |
val, typ = self._tokenvalue(token), self._type(token) |
500 |
if context.startswith('pseudo-'): |
501 |
append(seq, val, typ, token=token) |
502 |
return expression
|
503 |
else:
|
504 |
new['wellformed'] = False |
505 |
self._log.error(
|
506 |
u'Selector: Unexpected %s.' % typ, token=token)
|
507 |
return expected
|
508 |
|
509 |
def _attcombinator(expected, seq, token, tokenizer=None): |
510 |
# context: attrib
|
511 |
# PREFIXMATCH | SUFFIXMATCH | SUBSTRINGMATCH | INCLUDES |
|
512 |
# DASHMATCH
|
513 |
context = new['context'][-1] |
514 |
val, typ = self._tokenvalue(token), self._type(token) |
515 |
if 'attrib' == context and 'combinator' in expected: |
516 |
# combinator in attrib
|
517 |
append(seq, val, typ.lower(), token=token) |
518 |
return attvalue
|
519 |
else:
|
520 |
new['wellformed'] = False |
521 |
self._log.error(
|
522 |
u'Selector: Unexpected %s.' % typ, token=token)
|
523 |
return expected
|
524 |
|
525 |
def _string(expected, seq, token, tokenizer=None): |
526 |
# identifier
|
527 |
context = new['context'][-1] |
528 |
typ, val = self._type(token), self._stringtokenvalue(token) |
529 |
|
530 |
# context: attrib
|
531 |
if 'attrib' == context and 'value' in expected: |
532 |
# attrib: [...=VALUE]
|
533 |
append(seq, val, typ, token=token) |
534 |
return attend
|
535 |
|
536 |
# context: pseudo
|
537 |
elif context.startswith('pseudo-'): |
538 |
# :func(...)
|
539 |
append(seq, val, typ, token=token) |
540 |
return expression
|
541 |
|
542 |
else:
|
543 |
new['wellformed'] = False |
544 |
self._log.error(
|
545 |
u'Selector: Unexpected STRING.', token=token)
|
546 |
return expected
|
547 |
|
548 |
def _ident(expected, seq, token, tokenizer=None): |
549 |
# identifier
|
550 |
context = new['context'][-1] |
551 |
val, typ = self._tokenvalue(token), self._type(token) |
552 |
|
553 |
# context: attrib
|
554 |
if 'attrib' == context and 'attribute' in expected: |
555 |
# attrib: [...|ATT...]
|
556 |
append(seq, val, 'attribute-selector', token=token)
|
557 |
return attcombinator
|
558 |
|
559 |
elif 'attrib' == context and 'value' in expected: |
560 |
# attrib: [...=VALUE]
|
561 |
append(seq, val, 'attribute-value', token=token)
|
562 |
return attend
|
563 |
|
564 |
# context: negation
|
565 |
elif 'negation' == context: |
566 |
# negation: (prefix|IDENT)
|
567 |
append(seq, val, 'negation-type-selector', token=token)
|
568 |
return negationend
|
569 |
|
570 |
# context: pseudo
|
571 |
elif context.startswith('pseudo-'): |
572 |
# :func(...)
|
573 |
append(seq, val, typ, token=token) |
574 |
return expression
|
575 |
|
576 |
elif 'type_selector' in expected or element_name == expected: |
577 |
# element name after ns or complete type_selector
|
578 |
append(seq, val, 'type-selector', token=token)
|
579 |
return simple_selector_sequence2 + combinator
|
580 |
|
581 |
else:
|
582 |
new['wellformed'] = False |
583 |
self._log.error(u'Selector: Unexpected IDENT.', token=token) |
584 |
return expected
|
585 |
|
586 |
def _class(expected, seq, token, tokenizer=None): |
587 |
# .IDENT
|
588 |
context = new['context'][-1] |
589 |
val = self._tokenvalue(token)
|
590 |
if 'class' in expected: |
591 |
append(seq, val, 'class', token=token)
|
592 |
|
593 |
if 'negation' == context: |
594 |
return negationend
|
595 |
else:
|
596 |
return simple_selector_sequence2 + combinator
|
597 |
|
598 |
else:
|
599 |
new['wellformed'] = False |
600 |
self._log.error(u'Selector: Unexpected class.', token=token) |
601 |
return expected
|
602 |
|
603 |
def _hash(expected, seq, token, tokenizer=None): |
604 |
# #IDENT
|
605 |
context = new['context'][-1] |
606 |
val = self._tokenvalue(token)
|
607 |
if 'HASH' in expected: |
608 |
append(seq, val, 'id', token=token)
|
609 |
|
610 |
if 'negation' == context: |
611 |
return negationend
|
612 |
else:
|
613 |
return simple_selector_sequence2 + combinator
|
614 |
|
615 |
else:
|
616 |
new['wellformed'] = False |
617 |
self._log.error(u'Selector: Unexpected HASH.', token=token) |
618 |
return expected
|
619 |
|
620 |
def _char(expected, seq, token, tokenizer=None): |
621 |
# + > ~ ) [ ] + -
|
622 |
context = new['context'][-1] |
623 |
val = self._tokenvalue(token)
|
624 |
|
625 |
# context: attrib
|
626 |
if u']' == val and 'attrib' == context and ']' in expected: |
627 |
# end of attrib
|
628 |
append(seq, val, 'attribute-end', token=token)
|
629 |
context = new['context'].pop() # attrib is done |
630 |
context = new['context'][-1] |
631 |
if 'negation' == context: |
632 |
return negationend
|
633 |
else:
|
634 |
return simple_selector_sequence2 + combinator
|
635 |
|
636 |
elif u'=' == val and 'attrib' == context\ |
637 |
and 'combinator' in expected: |
638 |
# combinator in attrib
|
639 |
append(seq, val, 'equals', token=token)
|
640 |
return attvalue
|
641 |
|
642 |
# context: negation
|
643 |
elif u')' == val and 'negation' == context and u')' in expected: |
644 |
# not(negation_arg)"
|
645 |
append(seq, val, 'negation-end', token=token)
|
646 |
new['context'].pop() # negation is done |
647 |
context = new['context'][-1] |
648 |
return simple_selector_sequence + combinator
|
649 |
|
650 |
# context: pseudo (at least one expression)
|
651 |
elif val in u'+-' and context.startswith('pseudo-'): |
652 |
# :func(+ -)"
|
653 |
_names = {'+': 'plus', '-': 'minus'} |
654 |
if val == u'+' and seq and seq[-1].value == S: |
655 |
seq.replace(-1, val, _names[val])
|
656 |
else:
|
657 |
append(seq, val, _names[val], |
658 |
token=token) |
659 |
return expression
|
660 |
|
661 |
elif u')' == val and context.startswith('pseudo-') and\ |
662 |
expression == expected: |
663 |
# :func(expression)"
|
664 |
append(seq, val, 'function-end', token=token)
|
665 |
new['context'].pop() # pseudo is done |
666 |
if 'pseudo-element' == context: |
667 |
return combinator
|
668 |
else:
|
669 |
return simple_selector_sequence + combinator
|
670 |
|
671 |
# context: ROOT
|
672 |
elif u'[' == val and 'attrib' in expected: |
673 |
# start of [attrib]
|
674 |
append(seq, val, 'attribute-start', token=token)
|
675 |
new['context'].append('attrib') |
676 |
return attname
|
677 |
|
678 |
elif val in u'+>~' and 'combinator' in expected: |
679 |
# no other combinator except S may be following
|
680 |
_names = { |
681 |
'>': 'child', |
682 |
'+': 'adjacent-sibling', |
683 |
'~': 'following-sibling'} |
684 |
if seq and seq[-1].value == S: |
685 |
seq.replace(-1, val, _names[val])
|
686 |
else:
|
687 |
append(seq, val, _names[val], token=token) |
688 |
return simple_selector_sequence
|
689 |
|
690 |
elif u',' == val: |
691 |
# not a selectorlist
|
692 |
new['wellformed'] = False |
693 |
self._log.error(
|
694 |
u'Selector: Single selector only.',
|
695 |
error=xml.dom.InvalidModificationErr, |
696 |
token=token) |
697 |
return expected
|
698 |
|
699 |
else:
|
700 |
new['wellformed'] = False |
701 |
self._log.error(
|
702 |
u'Selector: Unexpected CHAR.', token=token)
|
703 |
return expected
|
704 |
|
705 |
def _negation(expected, seq, token, tokenizer=None): |
706 |
# not(
|
707 |
context = new['context'][-1] |
708 |
val = self._tokenvalue(token, normalize=True) |
709 |
if 'negation' in expected: |
710 |
new['context'].append('negation') |
711 |
append(seq, val, 'negation-start', token=token)
|
712 |
return negation_arg
|
713 |
else:
|
714 |
new['wellformed'] = False |
715 |
self._log.error(
|
716 |
u'Selector: Unexpected negation.', token=token)
|
717 |
return expected
|
718 |
|
719 |
def _atkeyword(expected, seq, token, tokenizer=None): |
720 |
"invalidates selector"
|
721 |
new['wellformed'] = False |
722 |
self._log.error(
|
723 |
u'Selector: Unexpected ATKEYWORD.', token=token)
|
724 |
return expected
|
725 |
|
726 |
|
727 |
# expected: only|not or mediatype, mediatype, feature, and
|
728 |
newseq = self._tempSeq()
|
729 |
|
730 |
wellformed, expected = self._parse(
|
731 |
expected=simple_selector_sequence, |
732 |
seq=newseq, tokenizer=tokenizer, |
733 |
productions={'CHAR': _char,
|
734 |
'class': _class,
|
735 |
'HASH': _hash,
|
736 |
'STRING': _string,
|
737 |
'IDENT': _ident,
|
738 |
'namespace_prefix': _namespace_prefix,
|
739 |
'negation': _negation,
|
740 |
'pseudo-class': _pseudo,
|
741 |
'pseudo-element': _pseudo,
|
742 |
'universal': _universal,
|
743 |
# pseudo
|
744 |
'NUMBER': _expression,
|
745 |
'DIMENSION': _expression,
|
746 |
# attribute
|
747 |
'PREFIXMATCH': _attcombinator,
|
748 |
'SUFFIXMATCH': _attcombinator,
|
749 |
'SUBSTRINGMATCH': _attcombinator,
|
750 |
'DASHMATCH': _attcombinator,
|
751 |
'INCLUDES': _attcombinator,
|
752 |
|
753 |
'S': _S,
|
754 |
'COMMENT': _COMMENT,
|
755 |
'ATKEYWORD': _atkeyword})
|
756 |
wellformed = wellformed and new['wellformed'] |
757 |
|
758 |
# post condition
|
759 |
if len(new['context']) > 1 or not newseq: |
760 |
wellformed = False
|
761 |
self._log.error(u'Selector: Invalid or incomplete selector: %s' |
762 |
% self._valuestr(selectorText))
|
763 |
|
764 |
if expected == 'element_name': |
765 |
wellformed = False
|
766 |
self._log.error(u'Selector: No element name found: %s' |
767 |
% self._valuestr(selectorText))
|
768 |
|
769 |
if expected == simple_selector_sequence and newseq: |
770 |
wellformed = False
|
771 |
self._log.error(u'Selector: Cannot end with combinator: %s' |
772 |
% self._valuestr(selectorText))
|
773 |
|
774 |
if newseq and hasattr(newseq[-1].value, 'strip') \ |
775 |
and newseq[-1].value.strip() == u'': |
776 |
del newseq[-1] |
777 |
|
778 |
# set
|
779 |
if wellformed:
|
780 |
self.__namespaces = namespaces
|
781 |
self._element = new['element'] |
782 |
self._specificity = tuple(new['specificity']) |
783 |
self._setSeq(newseq)
|
784 |
# filter that only used ones are kept
|
785 |
self.__namespaces = self._getUsedNamespaces() |
786 |
|
787 |
selectorText = property(_getSelectorText, _setSelectorText,
|
788 |
doc=u"(DOM) The parsable textual representation of "
|
789 |
u"the selector.")
|
790 |
|
791 |
specificity = property(lambda self: self._specificity, |
792 |
doc="""Specificity of this selector (READONLY).
|
793 |
Tuple of (a, b, c, d) where:
|
794 |
|
795 |
a
|
796 |
presence of style in document, always 0 if not used on a
|
797 |
document
|
798 |
b
|
799 |
number of ID selectors
|
800 |
c
|
801 |
number of .class selectors
|
802 |
d
|
803 |
number of Element (type) selectors""")
|
804 |
|
805 |
wellformed = property(lambda self: bool(len(self.seq))) |
806 |
|
807 |
|
808 |
@Deprecated('Use property parent instead') |
809 |
def _getParentList(self): |
810 |
return self.parent |
811 |
|
812 |
parentList = property(_getParentList,
|
813 |
doc="DEPRECATED, see property parent instead")
|