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