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 1116 2008-03-05 13:52:23Z 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 self.seq = []
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', '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 x in reversed(self.seq):
181 if isinstance(x, Property) and not x.name in names:
182 names.append(x.name)
183 return reversed(names)
184
185
186 - def _getP(self, CSSName):
187 """
188 (DOM CSS2Properties)
189 Overwritten here and effectively the same as
190 ``self.getPropertyValue(CSSname)``.
191
192 Parameter is in CSSname format ('font-style'), see CSS2Properties.
193
194 Example::
195
196 >>> style = CSSStyleDeclaration(cssText='font-style:italic;')
197 >>> print style.fontStyle
198 italic
199 """
200 return self.getPropertyValue(CSSName)
201
202 - def _setP(self, CSSName, value):
203 """
204 (DOM CSS2Properties)
205 Overwritten here and effectively the same as
206 ``self.setProperty(CSSname, value)``.
207
208 Only known CSS2Properties may be set this way, otherwise an
209 AttributeError is raised.
210 For these unknown properties ``setPropertyValue(CSSname, value)``
211 has to be called explicitly.
212 Also setting the priority of properties needs to be done with a
213 call like ``setPropertyValue(CSSname, value, priority)``.
214
215 Example::
216
217 >>> style = CSSStyleDeclaration()
218 >>> style.fontStyle = 'italic'
219 >>> # or
220 >>> style.setProperty('font-style', 'italic', '!important')
221 """
222 self.setProperty(CSSName, value)
223
224
225 - def _delP(self, CSSName):
226 """
227 (cssutils only)
228 Overwritten here and effectively the same as
229 ``self.removeProperty(CSSname)``.
230
231 Example::
232
233 >>> style = CSSStyleDeclaration(cssText='font-style:italic;')
234 >>> del style.fontStyle
235 >>> print style.fontStyle # prints u''
236
237 """
238 self.removeProperty(CSSName)
239
240 - def _getCssText(self):
241 """
242 returns serialized property cssText
243 """
244 return cssutils.ser.do_css_CSSStyleDeclaration(self)
245
246 - def _setCssText(self, cssText):
247 """
248 Setting this attribute will result in the parsing of the new value
249 and resetting of all the properties in the declaration block
250 including the removal or addition of properties.
251
252 DOMException on setting
253
254 - NO_MODIFICATION_ALLOWED_ERR: (self)
255 Raised if this declaration is readonly or a property is readonly.
256 - SYNTAX_ERR: (self)
257 Raised if the specified CSS string value has a syntax error and
258 is unparsable.
259 """
260 self._checkReadonly()
261 tokenizer = self._tokenize2(cssText)
262
263
264 new = {'valid': True,
265 'wellformed': True,
266 'char': None}
267 def ident(expected, seq, token, tokenizer=None):
268
269 if new['char']:
270
271 token = (token[0], u'%s%s' % (new['char'], token[1]),
272 token[2], token[3])
273
274 tokens = self._tokensupto2(tokenizer, starttoken=token,
275 semicolon=True)
276 if self._tokenvalue(tokens[-1]) == u';':
277 tokens.pop()
278 property = Property()
279 property.cssText = tokens
280 if property.wellformed:
281 seq.append(property)
282 else:
283 self._log.error(u'CSSStyleDeclaration: Syntax Error in Property: %s'
284 % self._valuestr(tokens))
285
286 new['char'] = None
287 return expected
288
289 def char(expected, seq, token, tokenizer=None):
290
291 new['valid'] = False
292 self._log.error(u'CSSStyleDeclaration: Unexpected CHAR.', token)
293 c = self._tokenvalue(token)
294 if c in u'$':
295 self._log.warn(u'Trying to use (invalid) CHAR %r in Property name' %
296 c)
297 new['char'] = c
298 return expected
299
300
301 newseq = []
302 wellformed, expected = self._parse(expected=None,
303 seq=newseq, tokenizer=tokenizer,
304 productions={'IDENT': ident, 'CHAR': char})
305 valid = new['valid']
306
307
308
309 if new['char']:
310 valid = wellformed = False
311 self._log.error(u'Could not use unexpected CHAR %r' % new['char'])
312
313 if wellformed:
314 self.seq = newseq
315
316 cssText = property(_getCssText, _setCssText,
317 doc="(DOM) A parsable textual representation of the declaration\
318 block excluding the surrounding curly braces.")
319
320 - def getCssText(self, separator=None):
321 """
322 returns serialized property cssText, each property separated by
323 given ``separator`` which may e.g. be u'' to be able to use
324 cssText directly in an HTML style attribute. ";" is always part of
325 each property (except the last one) and can **not** be set with
326 separator!
327 """
328 return cssutils.ser.do_css_CSSStyleDeclaration(self, separator)
329
331 return self._parentRule
332
335
336 parentRule = property(_getParentRule, _setParentRule,
337 doc="(DOM) The CSS rule that contains this declaration block or\
338 None if this CSSStyleDeclaration is not attached to a CSSRule.")
339
341 """
342 Returns a list of Property objects set in this declaration.
343
344 name
345 optional name of properties which are requested (a filter).
346 Only properties with this **always normalized** name are returned.
347 all=False
348 if False (DEFAULT) only the effective properties (the ones set
349 last) are returned. If name is given a list with only one property
350 is returned.
351
352 if True all properties including properties set multiple times with
353 different values or priorities for different UAs are returned.
354 The order of the properties is fully kept as in the original
355 stylesheet.
356 """
357 if name and not all:
358
359 p = self.getProperty(name)
360 if p:
361 return [p]
362 else:
363 return []
364 elif not all:
365
366 return [self.getProperty(name)for name in self.__nnames()]
367 else:
368
369 nname = self._normalize(name)
370 properties = []
371 for x in self.seq:
372 if isinstance(x, Property) and (
373 (bool(nname) == False) or (x.name == nname)):
374 properties.append(x)
375 return properties
376
378 """
379 Returns the effective Property object.
380
381 name
382 of the CSS property, always lowercase (even if not normalized)
383 normalize
384 if True (DEFAULT) name will be normalized (lowercase, no simple
385 escapes) so "color", "COLOR" or "C\olor" will all be equivalent
386
387 If False may return **NOT** the effective value but the effective
388 for the unnormalized name.
389 """
390 nname = self._normalize(name)
391 found = None
392 for x in reversed(self.seq):
393 if isinstance(x, Property):
394 if (normalize and nname == x.name) or name == x.literalname:
395 if x.priority:
396 return x
397 elif not found:
398 found = x
399 return found
400
402 """
403 Returns CSSValue, the value of the effective property if it has been
404 explicitly set for this declaration block.
405
406 name
407 of the CSS property, always lowercase (even if not normalized)
408 normalize
409 if True (DEFAULT) name will be normalized (lowercase, no simple
410 escapes) so "color", "COLOR" or "C\olor" will all be equivalent
411
412 If False may return **NOT** the effective value but the effective
413 for the unnormalized name.
414
415 (DOM)
416 Used to retrieve the object representation of the value of a CSS
417 property if it has been explicitly set within this declaration
418 block. Returns None if the property has not been set.
419
420 (This method returns None if the property is a shorthand
421 property. Shorthand property values can only be accessed and
422 modified as strings, using the getPropertyValue and setProperty
423 methods.)
424
425 **cssutils currently always returns a CSSValue if the property is
426 set.**
427
428 for more on shorthand properties see
429 http://www.dustindiaz.com/css-shorthand/
430 """
431 nname = self._normalize(name)
432 if nname in self._SHORTHANDPROPERTIES:
433 self._log.info(
434 u'CSSValue for shorthand property "%s" should be None, this may be implemented later.' %
435 nname, neverraise=True)
436
437 p = self.getProperty(name, normalize)
438 if p:
439 return p.cssValue
440 else:
441 return None
442
444 """
445 Returns the value of the effective property if it has been explicitly
446 set for this declaration block. Returns the empty string if the
447 property has not been set.
448
449 name
450 of the CSS property, always lowercase (even if not normalized)
451 normalize
452 if True (DEFAULT) name will be normalized (lowercase, no simple
453 escapes) so "color", "COLOR" or "C\olor" will all be equivalent
454
455 If False may return **NOT** the effective value but the effective
456 for the unnormalized name.
457 """
458 p = self.getProperty(name, normalize)
459 if p:
460 return p.value
461 else:
462 return u''
463
465 """
466 Returns the priority of the effective CSS property (e.g. the
467 "important" qualifier) if the property has been explicitly set in
468 this declaration block. The empty string if none exists.
469
470 name
471 of the CSS property, always lowercase (even if not normalized)
472 normalize
473 if True (DEFAULT) name will be normalized (lowercase, no simple
474 escapes) so "color", "COLOR" or "C\olor" will all be equivalent
475
476 If False may return **NOT** the effective value but the effective
477 for the unnormalized name.
478 """
479 p = self.getProperty(name, normalize)
480 if p:
481 return p.priority
482 else:
483 return u''
484
486 """
487 (DOM)
488 Used to remove a CSS property if it has been explicitly set within
489 this declaration block.
490
491 Returns the value of the property if it has been explicitly set for
492 this declaration block. Returns the empty string if the property
493 has not been set or the property name does not correspond to a
494 known CSS property
495
496 name
497 of the CSS property
498 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 The effective Property value is returned and *all* Properties
502 with ``Property.name == name`` are removed.
503
504 If False may return **NOT** the effective value but the effective
505 for the unnormalized ``name`` only. Also only the Properties with
506 the literal name ``name`` are removed.
507
508 raises DOMException
509
510 - NO_MODIFICATION_ALLOWED_ERR: (self)
511 Raised if this declaration is readonly or the property is
512 readonly.
513 """
514 self._checkReadonly()
515 r = self.getPropertyValue(name, normalize=normalize)
516 if normalize:
517
518 nname = self._normalize(name)
519 newseq = [x for x in self.seq
520 if not(isinstance(x, Property) and x.name == nname)]
521 else:
522
523 newseq = [x for x in self.seq
524 if not(isinstance(x, Property) and x.literalname == name)]
525 self.seq = newseq
526 return r
527
528 - def setProperty(self, name, value=None, priority=u'', normalize=True):
529 """
530 (DOM)
531 Used to set a property value and priority within this declaration
532 block.
533
534 name
535 of the CSS property to set (in W3C DOM the parameter is called
536 "propertyName"), always lowercase (even if not normalized)
537
538 If a property with this name is present it will be reset
539
540 cssutils also allowed name to be a Property object, all other
541 parameter are ignored in this case
542
543 value
544 the new value of the property, omit if name is already a Property
545 priority
546 the optional priority of the property (e.g. "important")
547 normalize
548 if True (DEFAULT) name will be normalized (lowercase, no simple
549 escapes) so "color", "COLOR" or "C\olor" will all be equivalent
550
551 DOMException on setting
552
553 - SYNTAX_ERR: (self)
554 Raised if the specified value has a syntax error and is
555 unparsable.
556 - NO_MODIFICATION_ALLOWED_ERR: (self)
557 Raised if this declaration is readonly or the property is
558 readonly.
559 """
560 self._checkReadonly()
561
562 if isinstance(name, Property):
563 newp = name
564 name = newp.literalname
565 else:
566 newp = Property(name, value, priority)
567 if not newp.wellformed:
568 self._log.warn(u'Invalid Property: %s: %s %s'
569 % (name, value, priority))
570 else:
571 nname = self._normalize(name)
572 properties = self.getProperties(name, all=(not normalize))
573 for property in reversed(properties):
574 if normalize and property.name == nname:
575 property.cssValue = newp.cssValue.cssText
576 property.priority = newp.priority
577 break
578 elif property.literalname == name:
579 property.cssValue = newp.cssValue.cssText
580 property.priority = newp.priority
581 break
582 else:
583 self.seq.append(newp)
584
585 - def item(self, index):
586 """
587 (DOM)
588 Used to retrieve the properties that have been explicitly set in
589 this declaration block. The order of the properties retrieved using
590 this method does not have to be the order in which they were set.
591 This method can be used to iterate over all properties in this
592 declaration block.
593
594 index
595 of the property to retrieve, negative values behave like
596 negative indexes on Python lists, so -1 is the last element
597
598 returns the name of the property at this ordinal position. The
599 empty string if no property exists at this position.
600
601 ATTENTION:
602 Only properties with a different name are counted. If two
603 properties with the same name are present in this declaration
604 only the effective one is included.
605
606 ``item()`` and ``length`` work on the same set here.
607 """
608 names = list(self.__nnames())
609 try:
610 return names[index]
611 except IndexError:
612 return u''
613
614 length = property(lambda self: len(self.__nnames()),
615 doc="(DOM) The number of distinct properties that have been explicitly\
616 in this declaration block. The range of valid indices is 0 to\
617 length-1 inclusive. These are properties with a different ``name``\
618 only. ``item()`` and ``length`` work on the same set here.")
619
621 return "cssutils.css.%s(cssText=%r)" % (
622 self.__class__.__name__, self.getCssText(separator=u' '))
623
625 return "<cssutils.css.%s object length=%r (all: %r) at 0x%x>" % (
626 self.__class__.__name__, self.length,
627 len(self.getProperties(all=True)), id(self))
628