1 """
2 CSSStyleSheet implements DOM Level 2 CSS CSSStyleSheet.
3
4 Partly also:
5 http://www.w3.org/TR/2006/WD-css3-namespace-20060828/
6
7 TODO:
8 - ownerRule and ownerNode
9 """
10 __all__ = ['CSSStyleSheet']
11 __docformat__ = 'restructuredtext'
12 __author__ = '$LastChangedBy: doerwalter $'
13 __date__ = '$LastChangedDate: 2007-08-27 12:07:28 +0200 (Mo, 27 Aug 2007) $'
14 __version__ = '$LastChangedRevision: 287 $'
15
16 import xml.dom
17
18 import cssutils.stylesheets
19 import cssutils.tokenize
20
21
23 """
24 The CSSStyleSheet interface is a concrete interface used to represent
25 a CSS style sheet i.e., a style sheet whose content type is
26 "text/css".
27
28 Properties
29 ==========
30 cssRules: of type CSSRuleList, (DOM readonly)
31 The list of all CSS rules contained within the style sheet. This
32 includes both rule sets and at-rules.
33 namespaces: set
34 A set of declared namespaces via @namespace rules. Each
35 CSSStyleRule is checked if it uses additional prefixes which are
36 not declared. If they are "invalidated".
37 ownerRule: of type CSSRule, readonly
38 If this style sheet comes from an @import rule, the ownerRule
39 attribute will contain the CSSImportRule. In that case, the
40 ownerNode attribute in the StyleSheet interface will be None. If
41 the style sheet comes from an element or a processing instruction,
42 the ownerRule attribute will be None and the ownerNode attribute
43 will contain the Node.
44
45 Inherits properties from stylesheet.StyleSheet
46
47 Format
48 ======
49 stylesheet
50 : [ CHARSET_SYM S* STRING S* ';' ]?
51 [S|CDO|CDC]* [ import [S|CDO|CDC]* ]*
52 [ namespace [S|CDO|CDC]* ]* # according to @namespace WD
53 [ [ ruleset | media | page ] [S|CDO|CDC]* ]*
54 """
55 type = 'text/css'
56
57 - def __init__(self, href=None, media=None,
58 title=u'', disabled=None,
59 ownerNode=None, parentStyleSheet=None,
60 readonly=False):
69
70
72 """
73 checks if all namespaces used in stylerule have been declared
74 """
75 notdeclared = set()
76 for s in stylerule.selectorList:
77 for prefix in s.namespaces:
78 if not prefix in namespaces:
79 notdeclared.add(prefix)
80 return notdeclared
81
82
83 - def _getCssText(self):
85
86 - def _setCssText(self, cssText):
87 """
88 (cssutils)
89 Parses cssText and overwrites the whole stylesheet.
90
91 cssText
92 textual text to set
93
94 DOMException on setting
95
96 - NO_MODIFICATION_ALLOWED_ERR: (self)
97 Raised if the rule is readonly.
98 - SYNTAX_ERR:
99 Raised if the specified CSS string value has a syntax error and
100 is unparsable.
101 - NAMESPACE_ERR:
102 If a namespace prefix is found which is not declared.
103 """
104 self._checkReadonly()
105
106 tokens = self._tokenize(cssText, _fullSheet=True)
107
108 newcssRules = cssutils.css.CSSRuleList()
109 newnamespaces = set()
110
111
112 if tokens and tokens[0].type == self._ttypes.CHARSET_SYM:
113 charsetruletokens, endi = self._tokensupto(tokens)
114 charsetrule = cssutils.css.CSSCharsetRule()
115 charsetrule.cssText = charsetruletokens
116 newcssRules.append(charsetrule)
117 i = endi + 1
118 else:
119 i = 0
120 expected = '@import'
121 imax = len(tokens)
122 while i < imax:
123 t = tokens[i]
124
125 if t.type == self._ttypes.EOF:
126 break
127
128
129 if t.type in (self._ttypes.S, self._ttypes.CDO, self._ttypes.CDC):
130 pass
131
132
133 elif t.type in (self._ttypes.SEMICOLON,
134 self._ttypes.LBRACE, self._ttypes.RBRACE):
135 self._log.error(u'CSSStyleSheet: Syntax Error',t)
136
137
138 elif self._ttypes.COMMENT == t.type:
139 newcssRules.append(cssutils.css.CSSComment(t))
140
141
142 elif self._ttypes.CHARSET_SYM == t.type:
143 atruletokens, endi = self._tokensupto(tokens[i:])
144 i += endi
145 self._log.error(u'CSSStylesheet: CSSCharsetRule only allowed at beginning of stylesheet.',
146 t, xml.dom.HierarchyRequestErr)
147
148
149 elif self._ttypes.IMPORT_SYM == t.type:
150 atruletokens, endi = self._tokensupto(tokens[i:])
151 if expected == '@import':
152 atrule = cssutils.css.CSSImportRule()
153 atrule.cssText = atruletokens
154 newcssRules.append(atrule)
155 else:
156 self._log.error(
157 u'CSSStylesheet: CSSImportRule not allowed here.',
158 t, xml.dom.HierarchyRequestErr)
159 i += endi
160
161
162
163 elif self._ttypes.NAMESPACE_SYM == t.type:
164 atruletokens, endi = self._tokensupto(tokens[i:])
165 if expected in ('@import', '@namespace'):
166 atrule = cssutils.css.CSSNamespaceRule()
167 atrule.cssText = atruletokens
168 newcssRules.append(atrule)
169 newnamespaces.add(atrule.prefix)
170 else:
171 self._log.error(
172 u'CSSStylesheet: CSSNamespaceRule not allowed here.',
173 t, xml.dom.HierarchyRequestErr)
174 i += endi
175 expected = '@namespace'
176
177
178 elif self._ttypes.MEDIA_SYM == t.type:
179 atruletokens, endi = self._tokensupto(tokens[i:])
180 atrule = cssutils.css.CSSMediaRule()
181 atrule.cssText = atruletokens
182 newcssRules.append(atrule)
183 i += endi
184 expected = 'any'
185
186
187 elif self._ttypes.PAGE_SYM == t.type:
188 atruletokens, endi = self._tokensupto(tokens[i:])
189 atrule = cssutils.css.CSSPageRule()
190 atrule.cssText = atruletokens
191 newcssRules.append(atrule)
192 i += endi
193 expected = 'any'
194
195
196 elif self._ttypes.ATKEYWORD == t.type:
197 atruletokens, endi = self._tokensupto(tokens[i:])
198 atrule = cssutils.css.CSSUnknownRule()
199 atrule.cssText = atruletokens
200 newcssRules.append(atrule)
201 i += endi
202
203 else:
204 ruletokens, endi = self._tokensupto(
205 tokens[i:], blockendonly=True)
206 rule = cssutils.css.CSSStyleRule()
207 rule.cssText = ruletokens
208 notdeclared = self.__checknamespaces(rule, newnamespaces)
209 if notdeclared:
210 rule.valid = False
211 self._log.error(
212 u'CSSStylesheet: CSSStyleRule uses undeclared namespace prefixes: %s.' %
213 ', '.join(notdeclared),
214 t, xml.dom.NamespaceErr)
215 newcssRules.append(rule)
216 i += endi
217 expected = 'any'
218
219 i += 1
220
221 self.cssRules = newcssRules
222 for r in self.cssRules:
223 r.parentStyleSheet = self
224 self.namespaces = newnamespaces
225
226 cssText = property(_getCssText, _setCssText,
227 "(cssutils) a textual representation of the stylesheet")
228
229
231 """
232 Used to delete a rule from the style sheet.
233
234 index
235 of the rule to remove in the StyleSheet's rule list
236
237 DOMException
238
239 - INDEX_SIZE_ERR: (self)
240 Raised if the specified index does not correspond to a rule in
241 the style sheet's rule list.
242 - NO_MODIFICATION_ALLOWED_ERR: (self)
243 Raised if this style sheet is readonly.
244 """
245 self._checkReadonly()
246
247 try:
248 self.cssRules[index].parentStyleSheet = None
249 del self.cssRules[index]
250 except IndexError:
251 raise xml.dom.IndexSizeErr(
252 u'CSSStyleSheet: %s is not a valid index in the rulelist of length %i' % (
253 index, self.cssRules.length))
254
255
257 """
258 Used to insert a new rule into the style sheet. The new rule now
259 becomes part of the cascade.
260
261 Rule may be a string or a valid CSSRule.
262
263 rule
264 a parsable DOMString (cssutils: or Rule object)
265 index
266 of the rule before the new rule will be inserted.
267 If the specified index is equal to the length of the
268 StyleSheet's rule collection, the rule will be added to the end
269 of the style sheet.
270 If index is not given or None rule will be appended to rule
271 list.
272
273 returns the index within the stylesheet's rule collection
274
275 DOMException
276
277 - HIERARCHY_REQUEST_ERR: (self)
278 Raised if the rule cannot be inserted at the specified index
279 e.g. if an @import rule is inserted after a standard rule set
280 or other at-rule.
281 - INDEX_SIZE_ERR: (not raised at all)
282 Raised if the specified index is not a valid insertion point.
283 - NO_MODIFICATION_ALLOWED_ERR: (self)
284 Raised if this style sheet is readonly.
285 - SYNTAX_ERR: (rule)
286 Raised if the specified rule has a syntax error and is
287 unparsable.
288 """
289 self._checkReadonly()
290
291
292 if index is None:
293 index = len(self.cssRules)
294 elif index < 0 or index > self.cssRules.length:
295 raise xml.dom.IndexSizeErr(
296 u'CSSStyleSheet: Invalid index %s for CSSRuleList with a length of %s.' % (
297 index, self.cssRules.length))
298
299
300 if isinstance(rule, basestring):
301 tempsheet = CSSStyleSheet()
302 tempsheet.cssText = rule
303 if len(tempsheet.cssRules) != 1 or (tempsheet.cssRules and
304 not isinstance(tempsheet.cssRules[0], cssutils.css.CSSRule)):
305 self._log.error(u'CSSStyleSheet: Invalid Rule: %s' % rule)
306 return
307 rule = tempsheet.cssRules[0]
308 elif not isinstance(rule, cssutils.css.CSSRule):
309 self._log.error(u'CSSStyleSheet: Not a CSSRule: %s' % rule)
310 return
311
312
313
314 if isinstance(rule, cssutils.css.CSSCharsetRule):
315 if index != 0 or (self.cssRules and
316 isinstance(self.cssRules[0], cssutils.css.CSSCharsetRule)):
317 self._log.error(
318 u'CSSStylesheet: @charset only allowed once at the beginning of a stylesheet.',
319 error=xml.dom.HierarchyRequestErr)
320 return
321 else:
322 self.cssRules.insert(index, rule)
323 rule.parentStyleSheet = self
324
325
326 elif isinstance(rule, cssutils.css.CSSUnknownRule) or \
327 isinstance(rule, cssutils.css.CSSComment):
328 if index == 0 and self.cssRules and \
329 isinstance(self.cssRules[0], cssutils.css.CSSCharsetRule):
330 self._log.error(
331 u'CSSStylesheet: @charset must be the first rule.',
332 error=xml.dom.HierarchyRequestErr)
333 return
334 else:
335 self.cssRules.insert(index, rule)
336 rule.parentStyleSheet = self
337
338
339 elif isinstance(rule, cssutils.css.CSSImportRule):
340
341 if index == 0 and self.cssRules and \
342 isinstance(self.cssRules[0], cssutils.css.CSSCharsetRule):
343 self._log.error(
344 u'CSSStylesheet: Found @charset at index 0.',
345 error=xml.dom.HierarchyRequestErr)
346 return
347
348 for r in self.cssRules[:index]:
349 if isinstance(r, cssutils.css.CSSNamespaceRule) or \
350 isinstance(r, cssutils.css.CSSMediaRule) or \
351 isinstance(r, cssutils.css.CSSPageRule) or \
352 isinstance(r, cssutils.css.CSSStyleRule):
353 self._log.error(
354 u'CSSStylesheet: Found @namespace, @media, @page or StyleRule before index %s.' %
355 index,
356 error=xml.dom.HierarchyRequestErr)
357 return
358 self.cssRules.insert(index, rule)
359 rule.parentStyleSheet = self
360
361
362 elif isinstance(rule, cssutils.css.CSSNamespaceRule):
363
364 for r in self.cssRules[index:]:
365 if isinstance(r, cssutils.css.CSSCharsetRule) or \
366 isinstance(r, cssutils.css.CSSImportRule):
367 self._log.error(
368 u'CSSStylesheet: Found @charset or @import after index %s.' %
369 index,
370 error=xml.dom.HierarchyRequestErr)
371 return
372
373 for r in self.cssRules[:index]:
374 if isinstance(r, cssutils.css.CSSMediaRule) or \
375 isinstance(r, cssutils.css.CSSPageRule) or \
376 isinstance(r, cssutils.css.CSSStyleRule):
377 self._log.error(
378 u'CSSStylesheet: Found @media, @page or StyleRule before index %s.' %
379 index,
380 error=xml.dom.HierarchyRequestErr)
381 return
382 self.cssRules.insert(index, rule)
383 rule.parentStyleSheet = self
384 self.namespaces.add(rule.prefix)
385
386
387 else:
388 for r in self.cssRules[index:]:
389 if isinstance(r, cssutils.css.CSSCharsetRule) or \
390 isinstance(r, cssutils.css.CSSImportRule) or \
391 isinstance(r, cssutils.css.CSSNamespaceRule):
392 self._log.error(
393 u'CSSStylesheet: Found @charset, @import or @namespace before index %s.' %
394 index,
395 error=xml.dom.HierarchyRequestErr)
396 return
397 self.cssRules.insert(index, rule)
398 rule.parentStyleSheet = self
399
400 return index
401
402
404 "DEPRECATED, use appendRule instead"
405 import warnings
406 warnings.warn(
407 '``addRule`` is deprecated: Use ``insertRule(rule)``.',
408 DeprecationWarning)
409 return self.insertRule(rule)
410
411
413 """
414 NOT IMPLEMENTED YET
415 If this style sheet comes from an @import rule, the ownerRule
416 attribute will contain the CSSImportRule. In that case, the
417 ownerNode attribute in the StyleSheet interface will be null. If
418 the style sheet comes from an element or a processing instruction,
419 the ownerRule attribute will be null and the ownerNode attribute
420 will contain the Node.
421 """
422 raise NotImplementedError()
423
424 ownerRule = property(_getsetOwnerRuleDummy, _getsetOwnerRuleDummy,
425 doc="(DOM attribute) NOT IMPLEMENTED YET")
426
428 """
429 **EXPERIMENTAL**
430
431 utility function to replace all url(urlstring) values in
432 CSSImportRules and CSSStyleDeclaration objects.
433
434 replacer must be a function which is called with a single
435 argument ``urlstring`` which is the current value of url()
436 excluding "url(" and ")". It still may have surrounding single or
437 double quotes
438 """
439 for importrule in [
440 r for r in self.cssRules if hasattr(r, 'href')]:
441 importrule.href = replacer(importrule.href)
442
443 def setProperty(v):
444 if v.CSS_PRIMITIVE_VALUE == v.cssValueType and\
445 v.CSS_URI == v.primitiveType:
446 v.setStringValue(v.CSS_URI,
447 replacer(v.getStringValue()))
448
449 def styleDeclarations(base):
450 "recurive function to find all CSSStyleDeclarations"
451 styles = []
452 if hasattr(base, 'cssRules'):
453 for rule in base.cssRules:
454 styles.extend(styleDeclarations(rule))
455 elif hasattr(base, 'style'):
456 styles.append(base.style)
457 return styles
458
459 for style in styleDeclarations(self):
460 snpl = [x for x in style.seq
461 if isinstance(x, cssutils.css.SameNamePropertyList)]
462 for pl in snpl:
463 for p in pl:
464 v = p.cssValue
465 if v.CSS_VALUE_LIST == v.cssValueType:
466 for item in v:
467 setProperty(item)
468 elif v.CSS_PRIMITIVE_VALUE == v.cssValueType:
469 setProperty(v)
470
472 """
473 Sets Serializer used for output of this stylesheet
474 """
475 if isinstance(cssserializer, cssutils.CSSSerializer):
476 cssutils.ser = cssserializer
477 else:
478 raise ValueError(u'Serializer must be an instance of cssutils.CSSSerializer.')
479
481 """
482 Sets Preference of CSSSerializer used for output of this
483 stylesheet. See cssutils.serialize.Preferences for possible
484 preferences to be set.
485 """
486 cssutils.ser.prefs.__setattr__(pref, value)
487
489 return "cssutils.css.%s(href=%r, title=%r)" % (
490 self.__class__.__name__, self.href, self.title)
491
493 return "<cssutils.css.%s object title=%r href=%r at 0x%x>" % (
494 self.__class__.__name__, self.title, self.href, id(self))
495
496
497 if __name__ == '__main__':
498 print "CSSStyleSheet"
499 c = CSSStyleSheet(href=None,
500
501 media="all",
502 title="test",
503 disabled=None,
504 ownerNode=None,
505 parentStyleSheet=None)
506 c.cssText = u'''@\\namespace n1 "utf-8";
507 |a[n1|b], n2|c {}
508 '''
509 print c.cssText
510
511
512
513
514