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: cssstyledeclaration.py 1284 2008-06-05 16:29:17Z cthedot $'
55
56 import xml.dom
57 import cssutils
58 from cssproperties import CSS2Properties
59 from property import Property
60
62 """
63 The CSSStyleDeclaration class represents a single CSS declaration
64 block. This class may be used to determine the style properties
65 currently set in a block or to set style properties explicitly
66 within the block.
67
68 While an implementation may not recognize all CSS properties within
69 a CSS declaration block, it is expected to provide access to all
70 specified properties in the style sheet through the
71 CSSStyleDeclaration interface.
72 Furthermore, implementations that support a specific level of CSS
73 should correctly handle CSS shorthand properties for that level. For
74 a further discussion of shorthand properties, see the CSS2Properties
75 interface.
76
77 Additionally the CSS2Properties interface is implemented.
78
79 Properties
80 ==========
81 cssText
82 The parsable textual representation of the declaration block
83 (excluding the surrounding curly braces). Setting this attribute
84 will result in the parsing of the new value and resetting of the
85 properties in the declaration block. It also allows the insertion
86 of additional properties and their values into the block.
87 length: of type unsigned long, readonly
88 The number of properties that have been explicitly set in this
89 declaration block. The range of valid indices is 0 to length-1
90 inclusive.
91 parentRule: of type CSSRule, readonly
92 The CSS rule that contains this declaration block or None if this
93 CSSStyleDeclaration is not attached to a CSSRule.
94 seq: a list (cssutils)
95 All parts of this style declaration including CSSComments
96
97 $css2propertyname
98 All properties defined in the CSS2Properties class are available
99 as direct properties of CSSStyleDeclaration with their respective
100 DOM name, so e.g. ``fontStyle`` for property 'font-style'.
101
102 These may be used as::
103
104 >>> style = CSSStyleDeclaration(cssText='color: red')
105 >>> style.color = 'green'
106 >>> print style.color
107 green
108 >>> del style.color
109 >>> print style.color # print empty string
110
111 Format
112 ======
113 [Property: Value Priority?;]* [Property: Value Priority?]?
114 """
115 - def __init__(self, cssText=u'', parentRule=None, readonly=False):
116 """
117 cssText
118 Shortcut, sets CSSStyleDeclaration.cssText
119 parentRule
120 The CSS rule that contains this declaration block or
121 None if this CSSStyleDeclaration is not attached to a CSSRule.
122 readonly
123 defaults to False
124 """
125 super(CSSStyleDeclaration, self).__init__()
126 self._parentRule = parentRule
127
128 self.cssText = cssText
129 self._readonly = readonly
130
132 """
133 checks if a property (or a property with given name is in style
134
135 name
136 a string or Property, uses normalized name and not literalname
137 """
138 if isinstance(nameOrProperty, Property):
139 name = nameOrProperty.name
140 else:
141 name = self._normalize(nameOrProperty)
142 return name in self.__nnames()
143
145 """
146 iterator of set Property objects with different normalized names.
147 """
148 def properties():
149 for name in self.__nnames():
150 yield self.getProperty(name)
151 return properties()
152
154 """
155 Prevent setting of unknown properties on CSSStyleDeclaration
156 which would not work anyway. For these
157 ``CSSStyleDeclaration.setProperty`` MUST be called explicitly!
158
159 TODO:
160 implementation of known is not really nice, any alternative?
161 """
162 known = ['_tokenizer', '_log', '_ttypes',
163 '_seq', 'seq', 'parentRule', '_parentRule', 'cssText',
164 'valid', 'wellformed',
165 '_readonly']
166 known.extend(CSS2Properties._properties)
167 if n in known:
168 super(CSSStyleDeclaration, self).__setattr__(n, v)
169 else:
170 raise AttributeError(
171 'Unknown CSS Property, ``CSSStyleDeclaration.setProperty("%s", ...)`` MUST be used.'
172 % n)
173
175 """
176 returns iterator for all different names in order as set
177 if names are set twice the last one is used (double reverse!)
178 """
179 names = []
180 for item in reversed(self.seq):
181 val = item.value
182 if isinstance(val, Property) and not val.name in names:
183 names.append(val.name)
184 return reversed(names)
185
187 """Retrieve the value of property ``CSSName`` from this declaration.
188
189 ``CSSName`` will be always normalized.
190 """
191 return self.getPropertyValue(CSSName)
192
194 """Set value of property ``CSSName``. ``value`` may also be a tuple of
195 (value, priority), e.g. style['color'] = ('red', 'important')
196
197 ``CSSName`` will be always normalized.
198 """
199 priority = None
200 if type(value) == tuple:
201 value, priority = value
202
203 return self.setProperty(CSSName, value, priority)
204
206 """Delete property ``CSSName`` from this declaration.
207 If property is not in this declaration return u'' just like
208 removeProperty.
209
210 ``CSSName`` will be always normalized.
211 """
212 return self.removeProperty(CSSName)
213
214
215 - def _getP(self, CSSName):
216 """
217 (DOM CSS2Properties)
218 Overwritten here and effectively the same as
219 ``self.getPropertyValue(CSSname)``.
220
221 Parameter is in CSSname format ('font-style'), see CSS2Properties.
222
223 Example::
224
225 >>> style = CSSStyleDeclaration(cssText='font-style:italic;')
226 >>> print style.fontStyle
227 italic
228 """
229 return self.getPropertyValue(CSSName)
230
231 - def _setP(self, CSSName, value):
232 """
233 (DOM CSS2Properties)
234 Overwritten here and effectively the same as
235 ``self.setProperty(CSSname, value)``.
236
237 Only known CSS2Properties may be set this way, otherwise an
238 AttributeError is raised.
239 For these unknown properties ``setPropertyValue(CSSname, value)``
240 has to be called explicitly.
241 Also setting the priority of properties needs to be done with a
242 call like ``setPropertyValue(CSSname, value, priority)``.
243
244 Example::
245
246 >>> style = CSSStyleDeclaration()
247 >>> style.fontStyle = 'italic'
248 >>> # or
249 >>> style.setProperty('font-style', 'italic', '!important')
250 """
251 self.setProperty(CSSName, value)
252
253
254 - def _delP(self, CSSName):
255 """
256 (cssutils only)
257 Overwritten here and effectively the same as
258 ``self.removeProperty(CSSname)``.
259
260 Example::
261
262 >>> style = CSSStyleDeclaration(cssText='font-style:italic;')
263 >>> del style.fontStyle
264 >>> print style.fontStyle # prints u''
265
266 """
267 self.removeProperty(CSSName)
268
269 - def _getCssText(self):
270 """
271 returns serialized property cssText
272 """
273 return cssutils.ser.do_css_CSSStyleDeclaration(self)
274
275 - def _setCssText(self, cssText):
276 """
277 Setting this attribute will result in the parsing of the new value
278 and resetting of all the properties in the declaration block
279 including the removal or addition of properties.
280
281 DOMException on setting
282
283 - NO_MODIFICATION_ALLOWED_ERR: (self)
284 Raised if this declaration is readonly or a property is readonly.
285 - SYNTAX_ERR: (self)
286 Raised if the specified CSS string value has a syntax error and
287 is unparsable.
288 """
289 self._checkReadonly()
290 tokenizer = self._tokenize2(cssText)
291
292
293 new = {'wellformed': True}
294 def ident(expected, seq, token, tokenizer=None):
295
296
297 tokens = self._tokensupto2(tokenizer, starttoken=token,
298 semicolon=True)
299 if self._tokenvalue(tokens[-1]) == u';':
300 tokens.pop()
301 property = Property()
302 property.cssText = tokens
303 if property.wellformed:
304 seq.append(property, 'Property')
305 else:
306 self._log.error(u'CSSStyleDeclaration: Syntax Error in Property: %s'
307 % self._valuestr(tokens))
308
309 return expected
310
311 def unexpected(expected, seq, token, tokenizer=None):
312
313 ignored = self._tokenvalue(token) + self._valuestr(
314 self._tokensupto2(tokenizer, propertyvalueendonly=True))
315 self._log.error(u'CSSStyleDeclaration: Unexpected token, ignoring upto %r.' %
316 ignored,token)
317
318 return expected
319
320
321 newseq = self._tempSeq()
322 wellformed, expected = self._parse(expected=None,
323 seq=newseq, tokenizer=tokenizer,
324 productions={'IDENT': ident},
325 default=unexpected)
326
327
328
329
330
331 self._setSeq(newseq)
332
333 cssText = property(_getCssText, _setCssText,
334 doc="(DOM) A parsable textual representation of the declaration\
335 block excluding the surrounding curly braces.")
336
337 - def getCssText(self, separator=None):
338 """
339 returns serialized property cssText, each property separated by
340 given ``separator`` which may e.g. be u'' to be able to use
341 cssText directly in an HTML style attribute. ";" is always part of
342 each property (except the last one) and can **not** be set with
343 separator!
344 """
345 return cssutils.ser.do_css_CSSStyleDeclaration(self, separator)
346
348 return self._parentRule
349
352
353 parentRule = property(_getParentRule, _setParentRule,
354 doc="(DOM) The CSS rule that contains this declaration block or\
355 None if this CSSStyleDeclaration is not attached to a CSSRule.")
356
358 """
359 Returns a list of Property objects set in this declaration.
360
361 name
362 optional name of properties which are requested (a filter).
363 Only properties with this **always normalized** name are returned.
364 all=False
365 if False (DEFAULT) only the effective properties (the ones set
366 last) are returned. If name is given a list with only one property
367 is returned.
368
369 if True all properties including properties set multiple times with
370 different values or priorities for different UAs are returned.
371 The order of the properties is fully kept as in the original
372 stylesheet.
373 """
374 if name and not all:
375
376 p = self.getProperty(name)
377 if p:
378 return [p]
379 else:
380 return []
381 elif not all:
382
383 return [self.getProperty(name)for name in self.__nnames()]
384 else:
385
386 nname = self._normalize(name)
387 properties = []
388 for item in self.seq:
389 val = item.value
390 if isinstance(val, Property) and (
391 (bool(nname) == False) or (val.name == nname)):
392 properties.append(val)
393 return properties
394
396 """
397 Returns the effective Property object.
398
399 name
400 of the CSS property, always lowercase (even if not normalized)
401 normalize
402 if True (DEFAULT) name will be normalized (lowercase, no simple
403 escapes) so "color", "COLOR" or "C\olor" will all be equivalent
404
405 If False may return **NOT** the effective value but the effective
406 for the unnormalized name.
407 """
408 nname = self._normalize(name)
409 found = None
410 for item in reversed(self.seq):
411 val = item.value
412 if isinstance(val, Property):
413 if (normalize and nname == val.name) or name == val.literalname:
414 if val.priority:
415 return val
416 elif not found:
417 found = val
418 return found
419
421 """
422 Returns CSSValue, the value of the effective property if it has been
423 explicitly set for this declaration block.
424
425 name
426 of the CSS property, always lowercase (even if not normalized)
427 normalize
428 if True (DEFAULT) name will be normalized (lowercase, no simple
429 escapes) so "color", "COLOR" or "C\olor" will all be equivalent
430
431 If False may return **NOT** the effective value but the effective
432 for the unnormalized name.
433
434 (DOM)
435 Used to retrieve the object representation of the value of a CSS
436 property if it has been explicitly set within this declaration
437 block. Returns None if the property has not been set.
438
439 (This method returns None if the property is a shorthand
440 property. Shorthand property values can only be accessed and
441 modified as strings, using the getPropertyValue and setProperty
442 methods.)
443
444 **cssutils currently always returns a CSSValue if the property is
445 set.**
446
447 for more on shorthand properties see
448 http://www.dustindiaz.com/css-shorthand/
449 """
450 nname = self._normalize(name)
451 if nname in self._SHORTHANDPROPERTIES:
452 self._log.info(
453 u'CSSValue for shorthand property "%s" should be None, this may be implemented later.' %
454 nname, neverraise=True)
455
456 p = self.getProperty(name, normalize)
457 if p:
458 return p.cssValue
459 else:
460 return None
461
463 """
464 Returns the value of the effective property if it has been explicitly
465 set for this declaration block. Returns the empty string if the
466 property has not been set.
467
468 name
469 of the CSS property, always lowercase (even if not normalized)
470 normalize
471 if True (DEFAULT) name will be normalized (lowercase, no simple
472 escapes) so "color", "COLOR" or "C\olor" will all be equivalent
473
474 If False may return **NOT** the effective value but the effective
475 for the unnormalized name.
476 """
477 p = self.getProperty(name, normalize)
478 if p:
479 return p.value
480 else:
481 return u''
482
484 """
485 Returns the priority of the effective CSS property (e.g. the
486 "important" qualifier) if the property has been explicitly set in
487 this declaration block. The empty string if none exists.
488
489 name
490 of the CSS property, always lowercase (even if not normalized)
491 normalize
492 if True (DEFAULT) name will be normalized (lowercase, no simple
493 escapes) so "color", "COLOR" or "C\olor" will all be equivalent
494
495 If False may return **NOT** the effective value but the effective
496 for the unnormalized name.
497 """
498 p = self.getProperty(name, normalize)
499 if p:
500 return p.priority
501 else:
502 return u''
503
505 """
506 (DOM)
507 Used to remove a CSS property if it has been explicitly set within
508 this declaration block.
509
510 Returns the value of the property if it has been explicitly set for
511 this declaration block. Returns the empty string if the property
512 has not been set or the property name does not correspond to a
513 known CSS property
514
515 name
516 of the CSS property
517 normalize
518 if True (DEFAULT) name will be normalized (lowercase, no simple
519 escapes) so "color", "COLOR" or "C\olor" will all be equivalent.
520 The effective Property value is returned and *all* Properties
521 with ``Property.name == name`` are removed.
522
523 If False may return **NOT** the effective value but the effective
524 for the unnormalized ``name`` only. Also only the Properties with
525 the literal name ``name`` are removed.
526
527 raises DOMException
528
529 - NO_MODIFICATION_ALLOWED_ERR: (self)
530 Raised if this declaration is readonly or the property is
531 readonly.
532 """
533 self._checkReadonly()
534 r = self.getPropertyValue(name, normalize=normalize)
535 newseq = self._tempSeq()
536 if normalize:
537
538 nname = self._normalize(name)
539 for item in self.seq:
540 if not (isinstance(item.value, Property) and item.value.name == nname):
541 newseq.appendItem(item)
542 else:
543
544 for item in self.seq:
545 if not (isinstance(item.value, Property) and item.value.literalname == name):
546 newseq.appendItem(item)
547 self._setSeq(newseq)
548 return r
549
550 - def setProperty(self, name, value=None, priority=u'', normalize=True):
551 """
552 (DOM)
553 Used to set a property value and priority within this declaration
554 block.
555
556 name
557 of the CSS property to set (in W3C DOM the parameter is called
558 "propertyName"), always lowercase (even if not normalized)
559
560 If a property with this name is present it will be reset
561
562 cssutils also allowed name to be a Property object, all other
563 parameter are ignored in this case
564
565 value
566 the new value of the property, omit if name is already a Property
567 priority
568 the optional priority of the property (e.g. "important")
569 normalize
570 if True (DEFAULT) name will be normalized (lowercase, no simple
571 escapes) so "color", "COLOR" or "C\olor" will all be equivalent
572
573 DOMException on setting
574
575 - SYNTAX_ERR: (self)
576 Raised if the specified value has a syntax error and is
577 unparsable.
578 - NO_MODIFICATION_ALLOWED_ERR: (self)
579 Raised if this declaration is readonly or the property is
580 readonly.
581 """
582 self._checkReadonly()
583
584 if isinstance(name, Property):
585 newp = name
586 name = newp.literalname
587 else:
588 newp = Property(name, value, priority)
589 if not newp.wellformed:
590 self._log.warn(u'Invalid Property: %s: %s %s'
591 % (name, value, priority))
592 else:
593 nname = self._normalize(name)
594 properties = self.getProperties(name, all=(not normalize))
595 for property in reversed(properties):
596 if normalize and property.name == nname:
597 property.cssValue = newp.cssValue.cssText
598 property.priority = newp.priority
599 break
600 elif property.literalname == name:
601 property.cssValue = newp.cssValue.cssText
602 property.priority = newp.priority
603 break
604 else:
605 self.seq._readonly = False
606 self.seq.append(newp, 'Property')
607 self.seq._readonly = True
608
609 - def item(self, index):
610 """
611 (DOM)
612 Used to retrieve the properties that have been explicitly set in
613 this declaration block. The order of the properties retrieved using
614 this method does not have to be the order in which they were set.
615 This method can be used to iterate over all properties in this
616 declaration block.
617
618 index
619 of the property to retrieve, negative values behave like
620 negative indexes on Python lists, so -1 is the last element
621
622 returns the name of the property at this ordinal position. The
623 empty string if no property exists at this position.
624
625 ATTENTION:
626 Only properties with a different name are counted. If two
627 properties with the same name are present in this declaration
628 only the effective one is included.
629
630 ``item()`` and ``length`` work on the same set here.
631 """
632 names = list(self.__nnames())
633 try:
634 return names[index]
635 except IndexError:
636 return u''
637
638 length = property(lambda self: len(self.__nnames()),
639 doc="(DOM) The number of distinct properties that have been explicitly\
640 in this declaration block. The range of valid indices is 0 to\
641 length-1 inclusive. These are properties with a different ``name``\
642 only. ``item()`` and ``length`` work on the same set here.")
643
645 return "cssutils.css.%s(cssText=%r)" % (
646 self.__class__.__name__, self.getCssText(separator=u' '))
647
649 return "<cssutils.css.%s object length=%r (all: %r) at 0x%x>" % (
650 self.__class__.__name__, self.length,
651 len(self.getProperties(all=True)), id(self))
652