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