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 / cssstyledeclaration.py @ 475

History | View | Annotate | Download (26.5 KB)

1
"""CSSStyleDeclaration implements DOM Level 2 CSS CSSStyleDeclaration and
2
extends CSS2Properties
3

4
see
5
    http://www.w3.org/TR/1998/REC-CSS2-19980512/syndata.html#parsing-errors
6

7
Unknown properties
8
------------------
9
User agents must ignore a declaration with an unknown property.
10
For example, if the style sheet is::
11

12
    H1 { color: red; rotation: 70minutes }
13

14
the user agent will treat this as if the style sheet had been::
15

16
    H1 { color: red }
17

18
Cssutils gives a message about any unknown properties but
19
keeps any property (if syntactically correct).
20

21
Illegal values
22
--------------
23
User agents must ignore a declaration with an illegal value. For example::
24

25
    IMG { float: left }       /* correct CSS2 */
26
    IMG { float: left here }  /* "here" is not a value of 'float' */
27
    IMG { background: "red" } /* keywords cannot be quoted in CSS2 */
28
    IMG { border-width: 3 }   /* a unit must be specified for length values */
29

30
A CSS2 parser would honor the first rule and ignore the rest, as if the
31
style sheet had been::
32

33
    IMG { float: left }
34
    IMG { }
35
    IMG { }
36
    IMG { }
37

38
Cssutils again will issue a message (WARNING in this case) about invalid
39
CSS2 property values.
40

41
TODO:
42
    This interface is also used to provide a read-only access to the
43
    computed values of an element. See also the ViewCSS interface.
44

45
    - return computed values and not literal values
46
    - simplify unit pairs/triples/quadruples
47
      2px 2px 2px 2px -> 2px for border/padding...
48
    - normalize compound properties like:
49
      background: no-repeat left url()  #fff
50
      -> background: #fff url() no-repeat left
51
"""
52
__all__ = ['CSSStyleDeclaration', 'Property']
53
__docformat__ = 'restructuredtext'
54
__version__ = '$Id$'
55

    
56
from cssproperties import CSS2Properties
57
from property import Property
58
import cssutils
59
import xml.dom
60

    
61
class CSSStyleDeclaration(CSS2Properties, cssutils.util.Base2):
62
    """The CSSStyleDeclaration class represents a single CSS declaration
63
    block. This class may be used to determine the style properties
64
    currently set in a block or to set style properties explicitly
65
    within the block.
66

67
    While an implementation may not recognize all CSS properties within
68
    a CSS declaration block, it is expected to provide access to all
69
    specified properties in the style sheet through the
70
    CSSStyleDeclaration interface.
71
    Furthermore, implementations that support a specific level of CSS
72
    should correctly handle CSS shorthand properties for that level. For
73
    a further discussion of shorthand properties, see the CSS2Properties
74
    interface.
75

76
    Additionally the CSS2Properties interface is implemented.
77

78
    $css2propertyname
79
        All properties defined in the CSS2Properties class are available
80
        as direct properties of CSSStyleDeclaration with their respective
81
        DOM name, so e.g. ``fontStyle`` for property 'font-style'.
82

83
        These may be used as::
84

85
            >>> style = CSSStyleDeclaration(cssText='color: red')
86
            >>> style.color = 'green'
87
            >>> print style.color
88
            green
89
            >>> del style.color
90
            >>> print style.color
91
            <BLANKLINE>
92

93
    Format::
94

95
        [Property: Value Priority?;]* [Property: Value Priority?]?
96
    """
97
    def __init__(self, cssText=u'', parentRule=None, readonly=False,
98
                 validating=None):
99
        """
100
        :param cssText:
101
            Shortcut, sets CSSStyleDeclaration.cssText
102
        :param parentRule:
103
            The CSS rule that contains this declaration block or
104
            None if this CSSStyleDeclaration is not attached to a CSSRule.
105
        :param readonly:
106
            defaults to False
107
        :param validating:
108
            a flag defining if this sheet should be validated on change.
109
            Defaults to None, which means defer to the parent stylesheet.
110
        """
111
        super(CSSStyleDeclaration, self).__init__()
112
        self._parentRule = parentRule
113
        self.validating = validating
114
        self.cssText = cssText
115
        self._readonly = readonly
116

    
117
    def __contains__(self, nameOrProperty):
118
        """Check if a property (or a property with given name) is in style.
119

120
        :param name:
121
            a string or Property, uses normalized name and not literalname
122
        """
123
        if isinstance(nameOrProperty, Property):
124
            name = nameOrProperty.name
125
        else:
126
            name = self._normalize(nameOrProperty)
127
        return name in self.__nnames()
128

    
129
    def __iter__(self):
130
        """Iterator of set Property objects with different normalized names."""
131
        def properties():
132
            for name in self.__nnames():
133
                yield self.getProperty(name)
134
        return properties()
135

    
136
    def keys(self):
137
        """Analoguous to standard dict returns property names which are set in
138
        this declaration."""
139
        return list(self.__nnames())
140

    
141
    def __getitem__(self, CSSName):
142
        """Retrieve the value of property ``CSSName`` from this declaration.
143

144
        ``CSSName`` will be always normalized.
145
        """
146
        return self.getPropertyValue(CSSName)
147

    
148
    def __setitem__(self, CSSName, value):
149
        """Set value of property ``CSSName``. ``value`` may also be a tuple of
150
        (value, priority), e.g. style['color'] = ('red', 'important')
151

152
        ``CSSName`` will be always normalized.
153
        """
154
        priority = None
155
        if isinstance(value, tuple):
156
            value, priority = value
157

    
158
        return self.setProperty(CSSName, value, priority)
159

    
160
    def __delitem__(self, CSSName):
161
        """Delete property ``CSSName`` from this declaration.
162
        If property is not in this declaration return u'' just like
163
        removeProperty.
164

165
        ``CSSName`` will be always normalized.
166
        """
167
        return self.removeProperty(CSSName)
168

    
169
    def __setattr__(self, n, v):
170
        """Prevent setting of unknown properties on CSSStyleDeclaration
171
        which would not work anyway. For these
172
        ``CSSStyleDeclaration.setProperty`` MUST be called explicitly!
173

174
        TODO:
175
            implementation of known is not really nice, any alternative?
176
        """
177
        known = ['_tokenizer', '_log', '_ttypes',
178
                 '_seq', 'seq', 'parentRule', '_parentRule', 'cssText',
179
                 'valid', 'wellformed', 'validating',
180
                 '_readonly', '_profiles', '_validating']
181
        known.extend(CSS2Properties._properties)
182
        if n in known:
183
            super(CSSStyleDeclaration, self).__setattr__(n, v)
184
        else:
185
            raise AttributeError(u'Unknown CSS Property, '
186
                                 u'``CSSStyleDeclaration.setProperty("%s", '
187
                                 u'...)`` MUST be used.' % n)
188

    
189
    def __repr__(self):
190
        return u"cssutils.css.%s(cssText=%r)" % (
191
                self.__class__.__name__,
192
                self.getCssText(separator=u' '))
193

    
194
    def __str__(self):
195
        return u"<cssutils.css.%s object length=%r (all: %r) at 0x%x>" % (
196
                self.__class__.__name__,
197
                self.length,
198
                len(self.getProperties(all=True)),
199
                id(self))
200

    
201
    def __nnames(self):
202
        """Return iterator for all different names in order as set
203
        if names are set twice the last one is used (double reverse!)
204
        """
205
        names = []
206
        for item in reversed(self.seq):
207
            val = item.value
208
            if isinstance(val, Property) and not val.name in names:
209
                names.append(val.name)
210
        return reversed(names)
211

    
212
    # overwritten accessor functions for CSS2Properties' properties
213
    def _getP(self, CSSName):
214
        """(DOM CSS2Properties) Overwritten here and effectively the same as
215
        ``self.getPropertyValue(CSSname)``.
216

217
        Parameter is in CSSname format ('font-style'), see CSS2Properties.
218

219
        Example::
220

221
            >>> style = CSSStyleDeclaration(cssText='font-style:italic;')
222
            >>> print style.fontStyle
223
            italic
224
        """
225
        return self.getPropertyValue(CSSName)
226

    
227
    def _setP(self, CSSName, value):
228
        """(DOM CSS2Properties) Overwritten here and effectively the same as
229
        ``self.setProperty(CSSname, value)``.
230

231
        Only known CSS2Properties may be set this way, otherwise an
232
        AttributeError is raised.
233
        For these unknown properties ``setPropertyValue(CSSname, value)``
234
        has to be called explicitly.
235
        Also setting the priority of properties needs to be done with a
236
        call like ``setPropertyValue(CSSname, value, priority)``.
237

238
        Example::
239

240
            >>> style = CSSStyleDeclaration()
241
            >>> style.fontStyle = 'italic'
242
            >>> # or
243
            >>> style.setProperty('font-style', 'italic', '!important')
244

245
        """
246
        self.setProperty(CSSName, value)
247
        # TODO: Shorthand ones
248

    
249
    def _delP(self, CSSName):
250
        """(cssutils only) Overwritten here and effectively the same as
251
        ``self.removeProperty(CSSname)``.
252

253
        Example::
254

255
            >>> style = CSSStyleDeclaration(cssText='font-style:italic;')
256
            >>> del style.fontStyle
257
            >>> print style.fontStyle
258
            <BLANKLINE>
259

260
        """
261
        self.removeProperty(CSSName)
262

    
263
    def children(self):
264
        """Generator yielding any known child in this declaration including
265
         *all* properties, comments or CSSUnknownrules.
266
        """
267
        for item in self._seq:
268
            yield item.value
269

    
270
    def _getCssText(self):
271
        """Return serialized property cssText."""
272
        return cssutils.ser.do_css_CSSStyleDeclaration(self)
273

    
274
    def _setCssText(self, cssText):
275
        """Setting this attribute will result in the parsing of the new value
276
        and resetting of all the properties in the declaration block
277
        including the removal or addition of properties.
278

279
        :exceptions:
280
            - :exc:`~xml.dom.NoModificationAllowedErr`:
281
              Raised if this declaration is readonly or a property is readonly.
282
            - :exc:`~xml.dom.SyntaxErr`:
283
              Raised if the specified CSS string value has a syntax error and
284
              is unparsable.
285
        """
286
        self._checkReadonly()
287
        tokenizer = self._tokenize2(cssText)
288

    
289
        # for closures: must be a mutable
290
        new = {'wellformed': True}
291
        def ident(expected, seq, token, tokenizer=None):
292
            # a property
293

    
294
            tokens = self._tokensupto2(tokenizer, starttoken=token,
295
                                       semicolon=True)
296
            if self._tokenvalue(tokens[-1]) == u';':
297
                tokens.pop()
298
            property = Property(parent=self)
299
            property.cssText = tokens
300
            if property.wellformed:
301
                seq.append(property, 'Property')
302
            else:
303
                self._log.error(u'CSSStyleDeclaration: Syntax Error in '
304
                                u'Property: %s' % self._valuestr(tokens))
305
            # does not matter in this case
306
            return expected
307

    
308
        def unexpected(expected, seq, token, tokenizer=None):
309
            # error, find next ; or } to omit upto next property
310
            ignored = self._tokenvalue(token) + self._valuestr(
311
                                self._tokensupto2(tokenizer,
312
                                                  propertyvalueendonly=True))
313
            self._log.error(u'CSSStyleDeclaration: Unexpected token, ignoring '
314
                            'upto %r.' % ignored,token)
315
            # does not matter in this case
316
            return expected
317

    
318
        def char(expected, seq, token, tokenizer=None):
319
            # a standalone ; or error...
320
            if self._tokenvalue(token) == u';':
321
                self._log.info(u'CSSStyleDeclaration: Stripped standalone semicolon'
322
                                u': %s' % self._valuestr([token]), neverraise=True)
323
                return expected
324
            else:
325
                return unexpected(expected, seq, token, tokenizer)
326

    
327
        # [Property: Value;]* Property: Value?
328
        newseq = self._tempSeq()
329
        wellformed, expected = self._parse(expected=None,
330
            seq=newseq, tokenizer=tokenizer,
331
            productions={'IDENT': ident, 'CHAR': char},
332
            default=unexpected)
333
        # wellformed set by parse
334

    
335
        for item in newseq:
336
            item.value._parent = self
337

    
338
        # do not check wellformed as invalid things are removed anyway
339
        self._setSeq(newseq)
340

    
341
    cssText = property(_getCssText, _setCssText,
342
                       doc=u"(DOM) A parsable textual representation of the "
343
                           u"declaration block excluding the surrounding curly "
344
                           u"braces.")
345

    
346
    def getCssText(self, separator=None):
347
        """
348
        :returns:
349
            serialized property cssText, each property separated by
350
            given `separator` which may e.g. be ``u''`` to be able to use
351
            cssText directly in an HTML style attribute. ``;`` is part of
352
            each property (except the last one) and **cannot** be set with
353
            separator!
354
        """
355
        return cssutils.ser.do_css_CSSStyleDeclaration(self, separator)
356

    
357
    def _setParentRule(self, parentRule):
358
        self._parentRule = parentRule
359
#        for x in self.children():
360
#            x.parent = self
361

    
362
    parentRule = property(lambda self: self._parentRule, _setParentRule,
363
        doc="(DOM) The CSS rule that contains this declaration block or "
364
            "None if this CSSStyleDeclaration is not attached to a CSSRule.")
365

    
366
    def getProperties(self, name=None, all=False):
367
        """
368
        :param name:
369
            optional `name` of properties which are requested.
370
            Only properties with this **always normalized** `name` are returned.
371
            If `name` is ``None`` all properties are returned (at least one for
372
            each set name depending on parameter `all`).
373
        :param all:
374
            if ``False`` (DEFAULT) only the effective properties are returned.
375
            If name is given a list with only one property is returned.
376

377
            if ``True`` all properties including properties set multiple times
378
            with different values or priorities for different UAs are returned.
379
            The order of the properties is fully kept as in the original
380
            stylesheet.
381
        :returns:
382
            a list of :class:`~cssutils.css.Property` objects set in
383
            this declaration.
384
        """
385
        if name and not all:
386
            # single prop but list
387
            p = self.getProperty(name)
388
            if p:
389
                return [p]
390
            else:
391
                return []
392
        elif not all:
393
            # effective Properties in name order
394
            return [self.getProperty(name) for name in self.__nnames()]
395
        else:
396
            # all properties or all with this name
397
            nname = self._normalize(name)
398
            properties = []
399
            for item in self.seq:
400
                val = item.value
401
                if isinstance(val, Property) and (
402
                   (bool(nname) == False) or (val.name == nname)):
403
                    properties.append(val)
404
            return properties
405

    
406
    def getProperty(self, name, normalize=True):
407
        """
408
        :param name:
409
            of the CSS property, always lowercase (even if not normalized)
410
        :param normalize:
411
            if ``True`` (DEFAULT) name will be normalized (lowercase, no simple
412
            escapes) so "color", "COLOR" or "C\olor" will all be equivalent
413

414
            If ``False`` may return **NOT** the effective value but the
415
            effective for the unnormalized name.
416
        :returns:
417
            the effective :class:`~cssutils.css.Property` object.
418
        """
419
        nname = self._normalize(name)
420
        found = None
421
        for item in reversed(self.seq):
422
            val = item.value
423
            if isinstance(val, Property):
424
                if (normalize and nname == val.name) or name == val.literalname:
425
                    if val.priority:
426
                        return val
427
                    elif not found:
428
                        found = val
429
        return found
430

    
431
    def getPropertyCSSValue(self, name, normalize=True):
432
        """
433
        :param name:
434
            of the CSS property, always lowercase (even if not normalized)
435
        :param normalize:
436
            if ``True`` (DEFAULT) name will be normalized (lowercase, no simple
437
            escapes) so "color", "COLOR" or "C\olor" will all be equivalent
438

439
            If ``False`` may return **NOT** the effective value but the
440
            effective for the unnormalized name.
441
        :returns:
442
            :class:`~cssutils.css.CSSValue`, the value of the effective
443
            property if it has been explicitly set for this declaration block.
444

445
        (DOM)
446
        Used to retrieve the object representation of the value of a CSS
447
        property if it has been explicitly set within this declaration
448
        block. Returns None if the property has not been set.
449

450
        (This method returns None if the property is a shorthand
451
        property. Shorthand property values can only be accessed and
452
        modified as strings, using the getPropertyValue and setProperty
453
        methods.)
454

455
        **cssutils currently always returns a CSSValue if the property is
456
        set.**
457

458
        for more on shorthand properties see
459
            http://www.dustindiaz.com/css-shorthand/
460
        """
461
        nname = self._normalize(name)
462
        if nname in self._SHORTHANDPROPERTIES:
463
            self._log.info(u'CSSValue for shorthand property "%s" should be '
464
                           u'None, this may be implemented later.' %
465
                           nname, neverraise=True)
466

    
467
        p = self.getProperty(name, normalize)
468
        if p:
469
            return p.propertyValue
470
        else:
471
            return None
472

    
473
    def getPropertyValue(self, name, normalize=True):
474
        """
475
        :param name:
476
            of the CSS property, always lowercase (even if not normalized)
477
        :param normalize:
478
            if ``True`` (DEFAULT) name will be normalized (lowercase, no simple
479
            escapes) so "color", "COLOR" or "C\olor" will all be equivalent
480

481
            If ``False`` may return **NOT** the effective value but the
482
            effective for the unnormalized name.
483
        :returns:
484
            the value of the effective property if it has been explicitly set
485
            for this declaration block. Returns the empty string if the
486
            property has not been set.
487
        """
488
        p = self.getProperty(name, normalize)
489
        if p:
490
            return p.value
491
        else:
492
            return u''
493

    
494
    def getPropertyPriority(self, name, normalize=True):
495
        """
496
        :param name:
497
            of the CSS property, always lowercase (even if not normalized)
498
        :param normalize:
499
            if ``True`` (DEFAULT) name will be normalized (lowercase, no simple
500
            escapes) so "color", "COLOR" or "C\olor" will all be equivalent
501

502
            If ``False`` may return **NOT** the effective value but the
503
            effective for the unnormalized name.
504
        :returns:
505
            the priority of the effective CSS property (e.g. the
506
            "important" qualifier) if the property has been explicitly set in
507
            this declaration block. The empty string if none exists.
508
        """
509
        p = self.getProperty(name, normalize)
510
        if p:
511
            return p.priority
512
        else:
513
            return u''
514

    
515
    def removeProperty(self, name, normalize=True):
516
        """
517
        (DOM)
518
        Used to remove a CSS property if it has been explicitly set within
519
        this declaration block.
520

521
        :param name:
522
            of the CSS property
523
        :param normalize:
524
            if ``True`` (DEFAULT) name will be normalized (lowercase, no simple
525
            escapes) so "color", "COLOR" or "C\olor" will all be equivalent.
526
            The effective Property value is returned and *all* Properties
527
            with ``Property.name == name`` are removed.
528

529
            If ``False`` may return **NOT** the effective value but the
530
            effective for the unnormalized `name` only. Also only the
531
            Properties with the literal name `name` are removed.
532
        :returns:
533
            the value of the property if it has been explicitly set for
534
            this declaration block. Returns the empty string if the property
535
            has not been set or the property name does not correspond to a
536
            known CSS property
537

538

539
        :exceptions:
540
            - :exc:`~xml.dom.NoModificationAllowedErr`:
541
              Raised if this declaration is readonly or the property is
542
              readonly.
543
        """
544
        self._checkReadonly()
545
        r = self.getPropertyValue(name, normalize=normalize)
546
        newseq = self._tempSeq()
547
        if normalize:
548
            # remove all properties with name == nname
549
            nname = self._normalize(name)
550
            for item in self.seq:
551
                if not (isinstance(item.value, Property)
552
                        and item.value.name == nname):
553
                    newseq.appendItem(item)
554
        else:
555
            # remove all properties with literalname == name
556
            for item in self.seq:
557
                if not (isinstance(item.value, Property)
558
                        and item.value.literalname == name):
559
                    newseq.appendItem(item)
560
        self._setSeq(newseq)
561
        return r
562

    
563
    def setProperty(self, name, value=None, priority=u'',
564
                    normalize=True, replace=True):
565
        """(DOM) Set a property value and priority within this declaration
566
        block.
567

568
        :param name:
569
            of the CSS property to set (in W3C DOM the parameter is called
570
            "propertyName"), always lowercase (even if not normalized)
571

572
            If a property with this `name` is present it will be reset.
573

574
            cssutils also allowed `name` to be a
575
            :class:`~cssutils.css.Property` object, all other
576
            parameter are ignored in this case
577

578
        :param value:
579
            the new value of the property, ignored if `name` is a Property.
580
        :param priority:
581
            the optional priority of the property (e.g. "important"),
582
            ignored if `name` is a Property.
583
        :param normalize:
584
            if True (DEFAULT) `name` will be normalized (lowercase, no simple
585
            escapes) so "color", "COLOR" or "C\olor" will all be equivalent
586
        :param replace:
587
            if True (DEFAULT) the given property will replace a present
588
            property. If False a new property will be added always.
589
            The difference to `normalize` is that two or more properties with
590
            the same name may be set, useful for e.g. stuff like::
591

592
                background: red;
593
                background: rgba(255, 0, 0, 0.5);
594

595
            which defines the same property but only capable UAs use the last
596
            property value, older ones use the first value.
597

598
        :exceptions:
599
            - :exc:`~xml.dom.SyntaxErr`:
600
              Raised if the specified value has a syntax error and is
601
              unparsable.
602
            - :exc:`~xml.dom.NoModificationAllowedErr`:
603
              Raised if this declaration is readonly or the property is
604
              readonly.
605
        """
606
        self._checkReadonly()
607

    
608
        if isinstance(name, Property):
609
            newp = name
610
            name = newp.literalname
611
        elif not value:
612
            # empty string or None effectively removed property
613
            return self.removeProperty(name)
614
        else:
615
            newp = Property(name, value, priority)
616

    
617
        if newp.wellformed:
618
            if replace:
619
                # check if update
620
                nname = self._normalize(name)
621
                properties = self.getProperties(name, all=(not normalize))
622
                for property in reversed(properties):
623
                    if normalize and property.name == nname:
624
                        property.propertyValue = newp.propertyValue.cssText
625
                        property.priority = newp.priority
626
                        return
627
                    elif property.literalname == name:
628
                        property.propertyValue = newp.propertyValue.cssText
629
                        property.priority = newp.priority
630
                        return
631

    
632
            # not yet set or forced omit replace
633
            newp.parent = self
634
            self.seq._readonly = False
635
            self.seq.append(newp, 'Property')
636
            self.seq._readonly = True
637

    
638
        else:
639
            self._log.warn(u'Invalid Property: %s: %s %s'
640
                           % (name, value, priority))
641

    
642
    def item(self, index):
643
        """(DOM) Retrieve the properties that have been explicitly set in
644
        this declaration block. The order of the properties retrieved using
645
        this method does not have to be the order in which they were set.
646
        This method can be used to iterate over all properties in this
647
        declaration block.
648

649
        :param index:
650
            of the property to retrieve, negative values behave like
651
            negative indexes on Python lists, so -1 is the last element
652

653
        :returns:
654
            the name of the property at this ordinal position. The
655
            empty string if no property exists at this position.
656

657
        **ATTENTION:**
658
        Only properties with different names are counted. If two
659
        properties with the same name are present in this declaration
660
        only the effective one is included.
661

662
        :meth:`item` and :attr:`length` work on the same set here.
663
        """
664
        names = list(self.__nnames())
665
        try:
666
            return names[index]
667
        except IndexError:
668
            return u''
669

    
670
    length = property(lambda self: len(list(self.__nnames())),
671
                      doc=u"(DOM) The number of distinct properties that have "
672
                          u"been explicitly in this declaration block. The "
673
                          u"range of valid indices is 0 to length-1 inclusive. "
674
                          u"These are properties with a different ``name`` "
675
                          u"only. :meth:`item` and :attr:`length` work on the "
676
                          u"same set here.")
677

    
678
    def _getValidating(self):
679
        try:
680
            # CSSParser.parseX() sets validating of stylesheet
681
            return self.parentRule.parentStyleSheet.validating
682
        except AttributeError:
683
            # CSSParser.parseStyle() sets validating of declaration
684
            if self._validating is not None:
685
                return self._validating
686
        # default
687
        return True
688

    
689
    def _setValidating(self, validating):
690
        self._validating = validating
691

    
692
    validating = property(_getValidating, _setValidating,
693
                          doc=u"If ``True`` this declaration validates "
694
                          u"contained properties. The parent StyleSheet "
695
                          u"validation setting does *always* win though so "
696
                          u"even if validating is True it may not validate "
697
                          u"if the StyleSheet defines else!")