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', 'SameNamePropertyList']
52 __docformat__ = 'restructuredtext'
53 __author__ = '$LastChangedBy: cthedot $'
54 __date__ = '$LastChangedDate: 2007-08-29 13:51:12 +0200 (Mi, 29 Aug 2007) $'
55 __version__ = '$LastChangedRevision: 296 $'
56
57 import xml.dom
58
59 import cssutils
60 from cssproperties import CSS2Properties
61 from property import _Property as 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: of type DOMString
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;]* Property: Value?
116 """
117 - def __init__(self, parentRule=None, cssText=u'', readonly=False):
118 """
119 parentRule
120 The CSS rule that contains this declaration block or
121 None if this CSSStyleDeclaration is not attached to a CSSRule.
122 readonly
123 defaults to False
124 """
125 super(CSSStyleDeclaration, self).__init__()
126
127 self.valid = True
128
129 self.seq = []
130 self.parentRule = parentRule
131 self.cssText = cssText
132 self._readonly = readonly
133
134
136 """
137 Prevent setting of unknown properties on CSSStyleDeclaration
138 which would not work anyway. For these
139 ``CSSStyleDeclaration.setProperty`` MUST be called explicitly!
140
141 TODO:
142 implementation of known is not really nice, any alternative?
143 """
144 known = ['_tokenizer', '_log', '_ttypes',
145 'valid', 'seq', 'parentRule', '_parentRule', 'cssText',
146 '_readonly']
147 known.extend(CSS2Properties._properties)
148 if n in known:
149 super(CSSStyleDeclaration, self).__setattr__(n, v)
150 else:
151 raise AttributeError(
152 'Unknown CSS Property, ``CSSStyleDeclaration.setProperty("%s")`` MUST be used.'
153 % n)
154
158
160 """
161 the iterator
162
163 returns in contrast to calling item(index) property objects
164 """
165 props = []
166 for x in self.seq:
167 if isinstance(x, SameNamePropertyList):
168 for y in x:
169 props.append(y)
170 le = len(props)
171 for i in range (0, le):
172 yield props[i]
173
174
175 - def _getP(self, CSSName):
176 """
177 (DOM CSS2Properties)
178 Overwritten here and effectively the same as
179 ``self.getPropertyValue(CSSname)``.
180
181 Parameter is in CSSname format ('font-style'), see CSS2Properties.
182
183 Example::
184
185 >>> style = CSSStyleDeclaration(cssText='font-style:italic;')
186 >>> print style.fontStyle
187 italic
188
189 """
190 return self.getPropertyValue(CSSName)
191
192 - def _setP(self, CSSName, value):
193 """
194 (DOM CSS2Properties)
195 Overwritten here and effectively the same as
196 ``self.setProperty(CSSname, value)``.
197
198 Only known CSS2Properties may be set this way, otherwise an
199 AttributeError is raised.
200 For these unknown properties ``setPropertyValue(CSSname, value)``
201 has to be called explicitly.
202 Also setting the priority of properties needs to be done with a
203 call like ``setPropertyValue(CSSname, value, priority)``.
204
205 Example::
206
207 >>> style = CSSStyleDeclaration()
208 >>> style.fontStyle = 'italic'
209 >>> # or
210 >>> style.setProperty('font-style', 'italic', '!important')
211
212 """
213 if 'background-image' == CSSName:
214
215
216
217 self.setProperty(CSSName, value)
218 else:
219 self.setProperty(CSSName, value)
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
237 - def _getCssText(self):
238 """
239 returns serialized property cssText
240 """
241 return cssutils.ser.do_css_CSSStyleDeclaration(self)
242
243 - def _setCssText(self, cssText):
244 """
245 Setting this attribute will result in the parsing of the new value
246 and resetting of all the properties in the declaration block
247 including the removal or addition of properties.
248
249 DOMException on setting
250
251 - NO_MODIFICATION_ALLOWED_ERR: (self)
252 Raised if this declaration is readonly or a property is readonly.
253 - SYNTAX_ERR: (self)
254 Raised if the specified CSS string value has a syntax error and
255 is unparsable.
256 """
257 def ignoreuptopropend(i, tokens):
258 "returns position of ending ;"
259 ignoredtokens, endi = self._tokensupto(
260 tokens[i:], propertypriorityendonly=True)
261 if ignoredtokens:
262 ignored = ''.join([x.value for x in ignoredtokens])
263 self._log.error(u'CSSStyleDeclaration: Ignored: "%s".' %
264 self._valuestr(ignoredtokens), t)
265 return endi + 1
266
267 self._checkReadonly()
268 tokens = self._tokenize(cssText)
269
270 newseq = []
271 i, imax = 0, len(tokens)
272 while i < imax:
273 t = tokens[i]
274 if self._ttypes.S == t.type:
275 pass
276 elif self._ttypes.COMMENT == t.type:
277 newseq.append(cssutils.css.CSSComment(t))
278 else:
279
280 nametokens, endi = self._tokensupto(
281 tokens[i:], propertynameendonly=True)
282 i += endi
283
284 shouldbecolon = nametokens.pop()
285 if shouldbecolon.value == u':':
286 i += 1
287 elif shouldbecolon.value == u';':
288 self._log.error(
289 u'CSSStyleDeclaration: Incomplete Property starting here: %s.' %
290 self._valuestr(tokens[i-1:]), t)
291 i += 1
292 continue
293 else:
294 self._log.error(
295 u'CSSStyleDeclaration: No Propertyname and/or ":" found: %s.' %
296 self._valuestr(tokens[i:]), t)
297 i += ignoreuptopropend(i, tokens)
298 continue
299
300 for x in nametokens:
301 if x.type == self._ttypes.IDENT:
302 break
303 else:
304 self._log.error(
305 u'CSSStyleDeclaration: No Propertyname found: %s.'
306 % self._valuestr(tokens[i-1:]), t)
307 i += ignoreuptopropend(i, tokens)
308 continue
309
310
311 valuetokens, endi = self._tokensupto(
312 tokens[i:], propertyvalueendonly=True)
313 i += endi
314 if valuetokens and \
315 valuetokens[-1].type == self._ttypes.SEMICOLON:
316 del valuetokens[-1]
317 prioritytokens = None
318 elif valuetokens and \
319 valuetokens[-1].type == self._ttypes.IMPORTANT_SYM:
320 del valuetokens[-1]
321
322
323 prioritytokens, endi = self._tokensupto(
324 tokens[i:], propertypriorityendonly=True)
325 i += endi
326
327 if prioritytokens and prioritytokens[-1].type == \
328 self._ttypes.SEMICOLON:
329 del prioritytokens[-1]
330 elif not valuetokens:
331 self._log.error(u'CSSStyleDeclaration: No property value: %s'
332 % self._valuestr(cssText))
333 i += ignoreuptopropend(i, tokens)
334 continue
335 else:
336 prioritytokens = None
337
338 self.setProperty(nametokens, valuetokens, prioritytokens,
339 overwrite=False, _seq=newseq)
340 i += 1
341
342 self.seq = newseq
343
344 cssText = property(_getCssText, _setCssText,
345 doc="(DOM) The parsable textual representation of the declaration\
346 block excluding the surrounding curly braces.")
347
348 - def getCssText(self, separator=None):
349 """
350 returns serialized property cssText, each property separated by
351 given ``separator`` which may e.g. be u'' to be able to use
352 cssText directly in an HTML style attribute. ";" is always part of
353 each property (except the last one) and can **not** be set with
354 separator!
355 """
356 return cssutils.ser.do_css_CSSStyleDeclaration(self, separator)
357
360
361 length = property(_getLength,
362 doc="(DOM) the number of properties that have been explicitly set\
363 in this declaration block. The range of valid indices is 0 to\
364 length-1 inclusive.")
365
367 return self._parentRule
368
371
372 parentRule = property(_getParentRule, _setParentRule,
373 doc="(DOM) The CSS rule that contains this declaration block or\
374 None if this CSSStyleDeclaration is not attached to a CSSRule.")
375
377 """
378 (DOM)
379 Used to retrieve the object representation of the value of a CSS
380 property if it has been explicitly set within this declaration
381 block. This method returns None if the property is a shorthand
382 property. Shorthand property values can only be accessed and
383 modified as strings, using the getPropertyValue and setProperty
384 methods.
385
386 name
387 of the CSS property
388
389 The name will be normalized (lowercase, no simple escapes) so
390 "color", "COLOR" or "C\olor" are all equivalent
391
392 returns CSSValue, the value of the property if it has been
393 explicitly set for this declaration block. Returns None if the
394 property has not been set.
395
396 for more on shorthand properties see
397 http://www.dustindiaz.com/css-shorthand/
398 """
399 SHORTHAND = [
400 u'background',
401 u'border',
402 u'border-left', u'border-right',
403 u'border-top', u'border-bottom',
404 u'border-color', u'border-style', u'border-width',
405 u'cue',
406 u'font',
407 u'list-style',
408 u'margin',
409 u'outline',
410 u'padding',
411 u'pause']
412
413 normalname = self._normalize(name)
414
415 if normalname in SHORTHAND:
416 self._log.debug(
417 u'CSSValue for shorthand property "%s" should be None, this may be implemented later.' %
418 normalname, neverraise=True)
419
420 for pl in self.seq:
421 if isinstance(pl, SameNamePropertyList) and \
422 pl.name == normalname:
423 return pl[pl._currentIndex()].cssValue
424
426 """
427 (DOM)
428 Used to retrieve the value of a CSS property if it has been
429 explicitly set within this declaration block.
430
431 name
432 of the CSS property
433
434 The name will be normalized (lowercase, no simple escapes) so
435 "color", "COLOR" or "C\olor" are all equivalent
436
437 returns the value of the property if it has been explicitly set
438 for this declaration block. Returns the empty string if the
439 property has not been set.
440 """
441 normalname = self._normalize(name)
442
443 for pl in self.seq:
444 if isinstance(pl, SameNamePropertyList) and \
445 pl.name == normalname:
446 return pl[pl._currentIndex()].cssValue._value
447 return u''
448
450 """
451 (DOM)
452 Used to retrieve the priority of a CSS property (e.g. the
453 "important" qualifier) if the property has been explicitly set in
454 this declaration block.
455
456 name
457 of the CSS property
458
459 The name will be normalized (lowercase, no simple escapes) so
460 "color", "COLOR" or "C\olor" are all equivalent
461
462 returns a string representing the priority (e.g. "important") if
463 one exists. The empty string if none exists.
464 """
465 normalname = self._normalize(name)
466
467 for pl in self.seq:
468 if isinstance(pl, SameNamePropertyList) and \
469 pl.name == normalname:
470 return pl[pl._currentIndex()].priority
471 return u''
472
474 """
475 (cssutils) EXPERIMENTAL
476 Used to retrieve all properties set with this name. For cases where
477 a property is set multiple times with different values or
478 priorities for different UAs::
479
480 background: url(1.gif) fixed;
481 background: url(2.gif) scroll;
482
483 name
484 of the CSS property
485
486 The name will be normalized (lowercase, no simple escapes) so
487 "color", "COLOR" or "C\olor" are all equivalent
488
489 Returns the SameNamePropertyList object if available for the given
490 property name, else returns ``None``.
491 """
492 normalname = self._normalize(name)
493
494 for pl in self.seq:
495 if isinstance(pl, SameNamePropertyList) and \
496 pl.name == normalname:
497 return pl
498
499 - def item(self, index):
500 """
501 (DOM)
502 Used to retrieve the properties that have been explicitly set in
503 this declaration block. The order of the properties retrieved using
504 this method does not have to be the order in which they were set.
505 This method can be used to iterate over all properties in this
506 declaration block.
507
508 index
509 of the property to retrieve, negative values behave like
510 negative indexes on Python lists, so -1 is the last element
511
512 returns the name of the property at this ordinal position. The
513 empty string if no property exists at this position.
514 """
515 properties = [x.name for x in self.seq
516 if isinstance(x, SameNamePropertyList)]
517 try:
518 return properties[index]
519 except IndexError:
520 return u''
521
523 """
524 (DOM)
525 Used to remove a CSS property if it has been explicitly set within
526 this declaration block.
527
528 name
529 of the CSS property to remove
530
531 The name will be normalized (lowercase, no simple escapes) so
532 "color", "COLOR" or "C\olor" are all equivalent
533
534 returns the value of the property if it has been explicitly set for
535 this declaration block. Returns the empty string if the property
536 has not been set or the property name does not correspond to a
537 known CSS property
538
539 raises DOMException
540
541 - NO_MODIFICATION_ALLOWED_ERR: (self)
542 Raised if this declaration is readonly or the property is
543 readonly.
544 """
545 self._checkReadonly()
546
547 normalname = self._normalize(name)
548
549 r = u''
550 for i, pl in enumerate(self.seq):
551 if isinstance(pl, SameNamePropertyList) and \
552 pl.name == normalname:
553 r = pl[pl._currentIndex()].cssValue._value
554 del self.seq[i]
555 break
556
557 return r
558
559 - def setProperty(self, name, value, priority=None, overwrite=True,
560 _seq=None):
561 """
562 (DOM)
563 Used to set a property value and priority within this declaration
564 block.
565
566 name
567 of the CSS property to set
568 (in W3C DOM the parameter is called "propertyName")
569 will be normalized in the Property
570
571 value
572 the new value of the property
573 priority
574 the optional priority of the property (e.g. "important")
575 _seq
576 used by self._setCssText only as in temp seq
577
578 DOMException on setting
579
580 - SYNTAX_ERR: (self)
581 Raised if the specified value has a syntax error and is
582 unparsable.
583 - NO_MODIFICATION_ALLOWED_ERR: (self)
584 Raised if this declaration is readonly or the property is
585 readonly.
586 """
587 self._checkReadonly()
588
589 if _seq is None:
590 _seq = self.seq
591
592 newp = Property(name, value, priority)
593 if newp.name and newp.cssValue:
594 newnormalname = newp.normalname
595
596
597 index = -1
598 for i, pl in enumerate(_seq):
599 if isinstance(pl, SameNamePropertyList) and \
600 pl.name == newnormalname:
601 index = i
602 break
603
604 if index == -1:
605
606 newpl = SameNamePropertyList(newnormalname)
607 newpl.append(newp)
608 _seq.append(newpl)
609 else:
610 if overwrite:
611
612 del _seq[index][:]
613 _seq[index].append(newp)
614 else:
615
616 _seq[index].append(newp)
617
619 return "cssutils.css.%s()" % (
620 self.__class__.__name__)
621
623 return "<cssutils.css.%s object length=%r at 0x%x>" % (
624 self.__class__.__name__, self.length, id(self))
625
626
628 """
629 (cssutils) EXPERIMENTAL
630
631 A list of properties with the same normalname. Used for equivelant
632 properties like color, c\olor, co\lor.
633 To get the actual Property (latest with the highest priority) use
634 SameNamePropertyList[SameNamePropertyList._currentIndex()].
635
636 Properties
637 ==========
638 name
639 normalized Property name (e.g. ``color``)
640
641 see ``token.Token`` for details on "normalized"
642
643 Useful only if all similar named properties should be kept after
644 parsing. See ``Serializer.prefs.keepAllProperties``
645 """
648
650 """
651 returns index of current value of this property
652 used by serializer and getValue and getPriority
653 """
654 importants = [i for (i, p) in enumerate(self)
655 if p.priority]
656 if importants:
657 return importants[-1]
658 else:
659 normals = [i for (i, p) in enumerate(self)
660 if not p.priority]
661 if normals:
662 return normals[-1]
663
665 return "cssutils.css.%s(name=%r)" % (
666 self.__class__.__name__, self.name)
667
669 return "<cssutils.css.%s object name=%r at 0x%x>" % (
670 self.__class__.__name__, self.name, id(self))
671
672
673 if __name__ == '__main__':
674 pass
675