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