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.
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']
52 __docformat__ = 'restructuredtext'
53 __author__ = '$LastChangedBy: doerwalter $'
54 __date__ = '$LastChangedDate: 2007-08-02 22:58:23 +0200 (Do, 02 Aug 2007) $'
55 __version__ = '0.9.2a2 $LastChangedRevision: 160 $'
56
57 import xml.dom
58
59 import cssutils
60 from cssproperties import CSS2Properties
61 from property import _Property as Property
62
63
65 """
66 The CSSStyleDeclaration class represents a single CSS declaration
67 block. This class may be used to determine the style properties
68 currently set in a block or to set style properties explicitly
69 within the block.
70
71 While an implementation may not recognize all CSS properties within
72 a CSS declaration block, it is expected to provide access to all
73 specified properties in the style sheet through the
74 CSSStyleDeclaration interface.
75 Furthermore, implementations that support a specific level of CSS
76 should correctly handle CSS shorthand properties for that level. For
77 a further discussion of shorthand properties, see the CSS2Properties
78 interface.
79
80 Additionally the CSS2Properties interface is implemented.
81
82 Properties
83 ==========
84 cssText: of type DOMString
85 The parsable textual representation of the declaration block
86 (excluding the surrounding curly braces). Setting this attribute
87 will result in the parsing of the new value and resetting of the
88 properties in the declaration block. It also allows the insertion
89 of additional properties and their values into the block.
90 length: of type unsigned long, readonly
91 The number of properties that have been explicitly set in this
92 declaration block. The range of valid indices is 0 to length-1
93 inclusive.
94 parentRule: of type CSSRule, readonly
95 The CSS rule that contains this declaration block or None if this
96 CSSStyleDeclaration is not attached to a CSSRule.
97 seq: a list (cssutils)
98 All parts of this style declaration including CSSComments
99
100 $css2propertyname
101 All properties defined in the CSS2Properties class are available
102 as direct properties of CSSStyleDeclaration with their respective
103 DOM name, so e.g. ``fontStyle`` for property 'font-style'.
104
105 These may be used as::
106
107 >>> style = CSSStyleDeclaration(cssText='color: red')
108 >>> style.color = 'green'
109 >>> print style.color
110 green
111 >>> del style.color
112 >>> print style.color # print empty string
113
114 Format
115 ======
116 [Property: Value;]* Property: Value?
117 """
118 - def __init__(self, parentRule=None, cssText=u'', readonly=False):
119 """
120 parentRule
121 The CSS rule that contains this declaration block or
122 None if this CSSStyleDeclaration is not attached to a CSSRule.
123 readonly
124 defaults to False
125 """
126 super(CSSStyleDeclaration, self).__init__()
127
128 self.valid = True
129
130 self.seq = []
131 self.parentRule = parentRule
132 self.cssText = cssText
133 self._readonly = readonly
134
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', 'seq', 'parentRule', '_parentRule', 'cssText',
147 '_readonly']
148 known.extend(CSS2Properties._properties)
149 if n in known:
150 super(CSSStyleDeclaration, self).__setattr__(n, v)
151 else:
152 raise AttributeError(
153 'Unknown CSS Property, ``CSSStyleDeclaration.setProperty("%s")`` MUST be used.'
154 % n)
155
156
157 - def _getP(self, CSSname):
158 """
159 (DOM CSS2Properties)
160 Overwritten here and effectively the same as
161 ``self.getPropertyValue(CSSname)``.
162
163 Parameter is in CSSname format ('font-style'), see CSS2Properties.
164
165 Example::
166
167 >>> style = CSSStyleDeclaration(cssText='font-style:italic;')
168 >>> print style.fontStyle
169 italic
170
171 """
172 return self.getPropertyValue(CSSname)
173
174 - def _setP(self, CSSname, value):
175 """
176 (DOM CSS2Properties)
177 Overwritten here and effectively the same as
178 ``self.setProperty(CSSname, value)``.
179
180 Only known CSS2Properties may be set this way, otherwise an
181 AttributeError is raised.
182 For these unknown properties ``setPropertyValue(CSSname, value)``
183 has to be called explicitly.
184 Also setting the priority of properties needs to be done with a
185 call like ``setPropertyValue(CSSname, value, priority)``.
186
187 Example::
188
189 >>> style = CSSStyleDeclaration()
190 >>> style.fontStyle = 'italic'
191 >>> # or
192 >>> style.setProperty('font-style', 'italic', '!important')
193
194 """
195 self.setProperty(CSSname, value)
196
197 - def _delP(self, CSSname):
198 """
199 (cssutils only)
200 Overwritten here and effectively the same as
201 ``self.removeProperty(CSSname)``.
202
203 Example::
204
205 >>> style = CSSStyleDeclaration(cssText='font-style:italic;')
206 >>> del style.fontStyle
207 >>> print style.fontStyle # prints u''
208
209 """
210 self.removeProperty(CSSname)
211
212
213 - def _getCssText(self):
214 """
215 returns serialized property cssText
216 """
217 return cssutils.ser.do_css_CSSStyleDeclaration(self)
218
219 - def _setCssText(self, cssText):
220 """
221 Setting this attribute will result in the parsing of the new value
222 and resetting of all the properties in the declaration block
223 including the removal or addition of properties.
224
225 DOMException on setting
226
227 - NO_MODIFICATION_ALLOWED_ERR: (self)
228 Raised if this declaration is readonly or a property is readonly.
229 - SYNTAX_ERR: (self)
230 Raised if the specified CSS string value has a syntax error and
231 is unparsable.
232 """
233 def ignoreuptopropend(i, tokens):
234 "returns position of ending ;"
235 ignoredtokens, endi = self._tokensupto(
236 tokens[i:], propertypriorityendonly=True)
237 if ignoredtokens:
238 ignored = ''.join([x.value for x in ignoredtokens])
239 self._log.error(u'CSSStyleDeclaration: Ignored: "%s".' %
240 self._valuestr(ignoredtokens), t)
241 return endi + 1
242
243 self._checkReadonly()
244 tokens = self._tokenize(cssText)
245
246 newseq = []
247 i, imax = 0, len(tokens)
248 while i < imax:
249 t = tokens[i]
250 if self._ttypes.S == t.type:
251 pass
252 elif self._ttypes.COMMENT == t.type:
253 newseq.append(cssutils.css.CSSComment(t))
254 else:
255
256 nametokens, endi = self._tokensupto(
257 tokens[i:], propertynameendonly=True)
258 i += endi
259
260 shouldbecolon = nametokens.pop()
261 if shouldbecolon.value == u':':
262 i += 1
263 elif shouldbecolon.value == u';':
264 self._log.error(
265 u'CSSStyleDeclaration: Incomplete Property starting here: %s.' %
266 self._valuestr(tokens[i-1:]), t)
267 i += 1
268 continue
269 else:
270 self._log.error(
271 u'CSSStyleDeclaration: No Propertyname and/or ":" found: %s.' %
272 self._valuestr(tokens[i:]), t)
273 i += ignoreuptopropend(i, tokens)
274 continue
275
276 for x in nametokens:
277 if x.type == self._ttypes.IDENT:
278 break
279 else:
280 self._log.error(
281 u'CSSStyleDeclaration: No Propertyname found: %s.'
282 % self._valuestr(tokens[i-1:]), t)
283 i += ignoreuptopropend(i, tokens)
284 continue
285
286
287 valuetokens, endi = self._tokensupto(
288 tokens[i:], propertyvalueendonly=True)
289 i += endi
290 if valuetokens and \
291 valuetokens[-1].type == self._ttypes.SEMICOLON:
292 del valuetokens[-1]
293 prioritytokens = None
294 elif valuetokens and \
295 valuetokens[-1].type == self._ttypes.IMPORTANT_SYM:
296 del valuetokens[-1]
297
298
299 prioritytokens, endi = self._tokensupto(
300 tokens[i:], propertypriorityendonly=True)
301 i += endi
302
303 if prioritytokens and prioritytokens[-1].type == \
304 self._ttypes.SEMICOLON:
305 del prioritytokens[-1]
306 elif not valuetokens:
307 self._log.error(u'CSSStyleDeclaration: No property value: %s'
308 % self._valuestr(cssText))
309 i += ignoreuptopropend(i, tokens)
310 continue
311 else:
312 prioritytokens = None
313
314 self.setProperty(nametokens, valuetokens, prioritytokens,
315 overwrite=False, _seq=newseq)
316 i += 1
317
318 self.seq = newseq
319
320 cssText = property(_getCssText, _setCssText,
321 doc="(DOM) The parsable textual representation of the declaration\
322 block excluding the surrounding curly braces.")
323
324
327
328 length = property(_getLength,
329 doc="(DOM) the number of properties that have been explicitly set\
330 in this declaration block. The range of valid indices is 0 to\
331 length-1 inclusive.")
332
333
335 return self._parentRule
336
339
340 parentRule = property(_getParentRule, _setParentRule,
341 doc="(DOM) The CSS rule that contains this declaration block or\
342 None if this CSSStyleDeclaration is not attached to a CSSRule.")
343
344
346 """
347 (DOM)
348 Used to retrieve the object representation of the value of a CSS
349 property if it has been explicitly set within this declaration
350 block. This method returns None if the property is a shorthand
351 property. Shorthand property values can only be accessed and
352 modified as strings, using the getPropertyValue and setProperty
353 methods.
354
355 name
356 of the CSS property
357
358 The name will be normalized (lowercase, no simple escapes) so
359 "color", "COLOR" or "C\olor" are all equivalent
360
361 returns CSSValue, the value of the property if it has been
362 explicitly set for this declaration block. Returns None if the
363 property has not been set.
364
365 for more on shorthand properties see
366 http://www.dustindiaz.com/css-shorthand/
367 """
368 SHORTHAND = [
369 u'background',
370 u'border',
371 u'border-left', u'border-right',
372 u'border-top', u'border-bottom',
373 u'border-color', u'border-style', u'border-width',
374 u'cue',
375 u'font',
376 u'list-style',
377 u'margin',
378 u'outline',
379 u'padding',
380 u'pause']
381
382 normalname = self._normalize(name)
383
384 if normalname in SHORTHAND:
385 self._log.info(
386 u'Shorthand property "%s" always returns None.' %
387 normalname, neverraise=True)
388 return None
389
390 for pl in self.seq:
391 if isinstance(pl, SameNamePropertyList) and \
392 pl.name == normalname:
393 return pl[pl._currentIndex()].cssValue
394
395
397 """
398 (DOM)
399 Used to retrieve the value of a CSS property if it has been
400 explicitly set within this declaration block.
401
402 name
403 of the CSS property
404
405 The name will be normalized (lowercase, no simple escapes) so
406 "color", "COLOR" or "C\olor" are all equivalent
407
408 returns the value of the property if it has been explicitly set
409 for this declaration block. Returns the empty string if the
410 property has not been set.
411 """
412 normalname = self._normalize(name)
413
414 for pl in self.seq:
415 if isinstance(pl, SameNamePropertyList) and \
416 pl.name == normalname:
417 print pl
418 return pl[pl._currentIndex()].cssValue._value
419 return u''
420
421
423 """
424 (DOM)
425 Used to retrieve the priority of a CSS property (e.g. the
426 "important" qualifier) if the property has been explicitly set in
427 this declaration block.
428
429 name
430 of the CSS property
431
432 The name will be normalized (lowercase, no simple escapes) so
433 "color", "COLOR" or "C\olor" are all equivalent
434
435 returns a string representing the priority (e.g. "important") if
436 one exists. The empty string if none exists.
437 """
438 normalname = self._normalize(name)
439
440 for pl in self.seq:
441 if isinstance(pl, SameNamePropertyList) and \
442 pl.name == normalname:
443 return pl[pl._currentIndex()].priority
444 return u''
445
446
448 """
449 (cssutils) EXPERIMENTAL
450 Used to retrieve all properties set with this name. For cases where
451 a property is set multiple times with different values or
452 priorities for different UAs::
453
454 background: url(1.gif) fixed;
455 background: url(2.gif) scroll;
456
457 name
458 of the CSS property
459
460 The name will be normalized (lowercase, no simple escapes) so
461 "color", "COLOR" or "C\olor" are all equivalent
462
463 Returns the SameNamePropertyList object if available for the given
464 property name, else returns ``None``.
465 """
466 normalname = self._normalize(name)
467
468 for pl in self.seq:
469 if isinstance(pl, SameNamePropertyList) and \
470 pl.name == normalname:
471 return pl
472
473
474 - def item(self, index):
475 """
476 (DOM)
477 Used to retrieve the properties that have been explicitly set in
478 this declaration block. The order of the properties retrieved using
479 this method does not have to be the order in which they were set.
480 This method can be used to iterate over all properties in this
481 declaration block.
482
483 index
484 of the property to retrieve, negative values behave like
485 negative indexes on Python lists, so -1 is the last element
486
487 returns the name of the property at this ordinal position. The
488 empty string if no property exists at this position.
489 """
490 properties = [x.name for x in self.seq
491 if isinstance(x, SameNamePropertyList)]
492 try:
493 return properties[index]
494 except IndexError:
495 return u''
496
497
499 """
500 (DOM)
501 Used to remove a CSS property if it has been explicitly set within
502 this declaration block.
503
504 name
505 of the CSS property to remove
506
507 The name will be normalized (lowercase, no simple escapes) so
508 "color", "COLOR" or "C\olor" are all equivalent
509
510 returns the value of the property if it has been explicitly set for
511 this declaration block. Returns the empty string if the property
512 has not been set or the property name does not correspond to a
513 known CSS property
514
515 raises DOMException
516
517 - NO_MODIFICATION_ALLOWED_ERR: (self)
518 Raised if this declaration is readonly or the property is
519 readonly.
520 """
521 self._checkReadonly()
522
523 normalname = self._normalize(name)
524
525 r = u''
526 for i, pl in enumerate(self.seq):
527 if isinstance(pl, SameNamePropertyList) and \
528 pl.name == normalname:
529 r = pl[pl._currentIndex()].cssValue._value
530 del self.seq[i]
531 break
532
533 return r
534
535
536 - def setProperty(self, name, value, priority=None, overwrite=True,
537 _seq=None):
538 """
539 (DOM)
540 Used to set a property value and priority within this declaration
541 block.
542
543 name
544 of the CSS property to set
545 (in W3C DOM the parameter is called "propertyName")
546 will be normalized in the Property
547
548 value
549 the new value of the property
550 priority
551 the optional priority of the property (e.g. "important")
552 _seq
553 used by self._setCssText only as in temp seq
554
555 DOMException on setting
556
557 - SYNTAX_ERR: (self)
558 Raised if the specified value has a syntax error and is
559 unparsable.
560 - NO_MODIFICATION_ALLOWED_ERR: (self)
561 Raised if this declaration is readonly or the property is
562 readonly.
563 """
564 self._checkReadonly()
565
566 if _seq is None:
567 _seq = self.seq
568
569 newp = Property(name, value, priority)
570 if newp.name and newp.value:
571 newnormalname = newp.normalname
572
573
574 index = -1
575 for i, pl in enumerate(_seq):
576 if isinstance(pl, SameNamePropertyList) and \
577 pl.name == newnormalname:
578 index = i
579 break
580
581 if index == -1:
582
583 newpl = SameNamePropertyList(newnormalname)
584 newpl.append(newp)
585 _seq.append(newpl)
586 else:
587 if overwrite:
588
589 del _seq[index][:]
590 _seq[index].append(newp)
591 else:
592
593 _seq[index].append(newp)
594
595
597 """
598 (cssutils) EXPERIMENTAL
599
600 A list of properties with the same normalname. Used for equivelant
601 properties like color, c\olor, co\lor.
602 To get the actual Property (latest with the highest priority) use
603 SameNamePropertyList[SameNamePropertyList._currentIndex()].
604
605 Properties
606 ==========
607 name
608 normalized Property name (e.g. ``color``)
609
610 see ``token.Token`` for details on "normalized"
611
612 Useful only if all similar named properties should be kept after
613 parsing. See ``Serializer.prefs.keepAllProperties``
614 """
617
618
620 """
621 returns index of current value of this property
622 used by serializer and getValue and getPriority
623 """
624 importants = [i for (i, p) in enumerate(self)
625 if p.priority]
626 if importants:
627 return importants[-1]
628 else:
629 normals = [i for (i, p) in enumerate(self)
630 if not p.priority]
631 if normals:
632 return normals[-1]
633
634
635 if __name__ == '__main__':
636 cssutils.css.cssstyledeclaration.Property = Property
637
638 s = cssutils.parseString('''a {
639 color: red;
640 font-style: italic;
641 }''')
642 style = s.cssRules[0].style
643 print 1, style.fontStyle, style.getPropertyValue('font-style')
644 print 1, style.color, style.getPropertyValue('color')
645
646 style.color = 'green'
647 style.fontStyle = 'normal'
648
649 print 2, style.fontStyle, style.getPropertyValue('font-style')
650 print 2, style.color, style.getPropertyValue('color')
651
652 style.xxx = '1'
653