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