Statistics
| Revision:

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")