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