1
2
3 """serializer classes for CSS classes
4
5 """
6 __all__ = ['CSSSerializer', 'Preferences']
7 __docformat__ = 'restructuredtext'
8 __version__ = '$Id: serialize.py 1329 2008-07-09 12:28:02Z cthedot $'
9 import codecs
10 import re
11 import cssutils
12 import util
13
15 """
16 Escapes characters not allowed in the current encoding the CSS way
17 with a backslash followed by a uppercase hex code point
18
19 E.g. the german umlaut 'ä' is escaped as \E4
20 """
21 s = e.object[e.start:e.end]
22 return u''.join([ur'\%s ' % str(hex(ord(x)))[2:]
23 .upper() for x in s]), e.end
24
25 codecs.register_error('escapecss', _escapecss)
26
27
29 """
30 controls output of CSSSerializer
31
32 defaultAtKeyword = True
33 Should the literal @keyword from src CSS be used or the default
34 form, e.g. if ``True``: ``@import`` else: ``@i\mport``
35 defaultPropertyName = True
36 Should the normalized propertyname be used or the one given in
37 the src file, e.g. if ``True``: ``color`` else: ``c\olor``
38
39 Only used if ``keepAllProperties==False``.
40
41 defaultPropertyPriority = True
42 Should the normalized or literal priority be used, e.g. '!important'
43 or u'!Im\portant'
44
45 importHrefFormat = None
46 Uses hreftype if ``None`` or explicit ``'string'`` or ``'uri'``
47 indent = 4 * ' '
48 Indentation of e.g Properties inside a CSSStyleDeclaration
49 indentSpecificities = False
50 Indent rules with subset of Selectors and higher Specitivity
51
52 keepAllProperties = True
53 If ``True`` all properties set in the original CSSStylesheet
54 are kept meaning even properties set twice with the exact same
55 same name are kept!
56 keepComments = True
57 If ``False`` removes all CSSComments
58 keepEmptyRules = False
59 defines if empty rules like e.g. ``a {}`` are kept in the resulting
60 serialized sheet
61 keepUsedNamespaceRulesOnly = False
62 if True only namespace rules which are actually used are kept
63
64 lineNumbers = False
65 Only used if a complete CSSStyleSheet is serialized.
66 lineSeparator = u'\\n'
67 How to end a line. This may be set to e.g. u'' for serializing of
68 CSSStyleDeclarations usable in HTML style attribute.
69 listItemSpacer = u' '
70 string which is used in ``css.SelectorList``, ``css.CSSValue`` and
71 ``stylesheets.MediaList`` after the comma
72 omitLastSemicolon = True
73 If ``True`` omits ; after last property of CSSStyleDeclaration
74 paranthesisSpacer = u' '
75 string which is used before an opening paranthesis like in a
76 ``css.CSSMediaRule`` or ``css.CSSStyleRule``
77 propertyNameSpacer = u' '
78 string which is used after a Property name colon
79 selectorCombinatorSpacer = u' '
80 string which is used before and after a Selector combinator like +, > or ~.
81 CSSOM defines a single space for this which is also the default in cssutils.
82 spacer = u' '
83 general spacer, used e.g. by CSSUnknownRule
84
85 validOnly = False **DO NOT CHANGE YET**
86 if True only valid (currently Properties) are kept
87
88 A Property is valid if it is a known Property with a valid value.
89 Currently CSS 2.1 values as defined in cssproperties.py would be
90 valid.
91
92 """
102
104 "reset all preference options to the default value"
105 self.defaultAtKeyword = True
106 self.defaultPropertyName = True
107 self.defaultPropertyPriority = True
108 self.importHrefFormat = None
109 self.indent = 4 * u' '
110 self.indentSpecificities = False
111 self.keepAllProperties = True
112 self.keepComments = True
113 self.keepEmptyRules = False
114 self.keepUsedNamespaceRulesOnly = False
115 self.lineNumbers = False
116 self.lineSeparator = u'\n'
117 self.listItemSpacer = u' '
118 self.omitLastSemicolon = True
119 self.paranthesisSpacer = u' '
120 self.propertyNameSpacer = u' '
121 self.selectorCombinatorSpacer = u' '
122 self.spacer = u' '
123 self.validOnly = False
124
126 """
127 sets options to achive a minified stylesheet
128
129 you may want to set preferences with this convenience method
130 and set settings you want adjusted afterwards
131 """
132 self.importHrefFormat = 'string'
133 self.indent = u''
134 self.keepComments = False
135 self.keepEmptyRules = False
136 self.keepUsedNamespaceRulesOnly = True
137 self.lineNumbers = False
138 self.lineSeparator = u''
139 self.listItemSpacer = u''
140 self.omitLastSemicolon = True
141 self.paranthesisSpacer = u''
142 self.propertyNameSpacer = u''
143 self.selectorCombinatorSpacer = u''
144 self.spacer = u''
145 self.validOnly = False
146
148 return u"cssutils.css.%s(%s)" % (self.__class__.__name__,
149 u', '.join(['\n %s=%r' % (p, self.__getattribute__(p)) for p in self.__dict__]
150 ))
151
153 return u"<cssutils.css.%s object %s at 0x%x" % (self.__class__.__name__,
154 u' '.join(['%s=%r' % (p, self.__getattribute__(p)) for p in self.__dict__]
155 ),
156 id(self))
157
158
160 """
161 a simple class which makes appended items available as a combined string
162 """
164 self.ser = ser
165 self.out = []
166
168 if self.out and not self.out[-1].strip():
169
170 del self.out[-1]
171
172 - def append(self, val, typ=None, space=True, keepS=False, indent=False,
173 lineSeparator=False):
174 """Appends val. Adds a single S after each token except as follows:
175
176 - typ COMMENT
177 uses cssText depending on self.ser.prefs.keepComments
178 - typ "Property", cssutils.css.CSSRule.UNKNOWN_RULE
179 uses cssText
180 - typ STRING
181 escapes ser._string
182 - typ S
183 ignored except ``keepS=True``
184 - typ URI
185 calls ser_uri
186 - val ``{``
187 adds LF after
188 - val ``;``
189 removes S before and adds LF after
190 - val ``, :``
191 removes S before
192 - val ``+ > ~``
193 encloses in prefs.selectorCombinatorSpacer
194 - some other vals
195 add ``*spacer`` except ``space=False``
196 """
197 if val or 'STRING' == typ:
198
199 if 'COMMENT' == typ:
200 if self.ser.prefs.keepComments:
201 val = val.cssText
202 else:
203 return
204 elif typ in ('Property', cssutils.css.CSSRule.UNKNOWN_RULE):
205 val = val.cssText
206 elif 'S' == typ and not keepS:
207 return
208 elif 'STRING' == typ:
209
210 if val is None:
211 return
212 val = self.ser._string(val)
213 elif 'URI' == typ:
214 val = self.ser._uri(val)
215 elif val in u'+>~,:{;)]':
216 self._remove_last_if_S()
217
218
219 if indent:
220 self.out.append(self.ser._indentblock(val, self.ser._level+1))
221 else:
222 self.out.append(val)
223
224 if lineSeparator:
225
226 pass
227 elif val in u'+>~':
228 self.out.insert(-1, self.ser.prefs.selectorCombinatorSpacer)
229 self.out.append(self.ser.prefs.selectorCombinatorSpacer)
230 elif u',' == val:
231 self.out.append(self.ser.prefs.listItemSpacer)
232 elif u':' == val:
233 self.out.append(self.ser.prefs.propertyNameSpacer)
234 elif u'{' == val:
235 self.out.insert(-1, self.ser.prefs.paranthesisSpacer)
236 self.out.append(self.ser.prefs.lineSeparator)
237 elif u';' == val:
238 self.out.append(self.ser.prefs.lineSeparator)
239 elif val not in u'}[]()' and space:
240 self.out.append(self.ser.prefs.spacer)
241
242 - def value(self, delim=u'', end=None):
243 "returns all items joined by delim"
244 self._remove_last_if_S()
245 if end:
246 self.out.append(end)
247 return delim.join(self.out)
248
249
251 """
252 Methods to serialize a CSSStylesheet and its parts
253
254 To use your own serializing method the easiest is to subclass CSS
255 Serializer and overwrite the methods you like to customize.
256 """
257
258 __forbidden_in_uri_matcher = re.compile(ur'''.*?[\)\s\;]''', re.U).match
259
261 """
262 prefs
263 instance of Preferences
264 """
265 if not prefs:
266 prefs = Preferences()
267 self.prefs = prefs
268 self._level = 0
269
270
271 self._selectors = []
272 self._selectorlevel = 0
273
275 "returns default or source atkeyword depending on prefs"
276 if self.prefs.defaultAtKeyword:
277 return default
278 else:
279 return rule.atkeyword
280
282 """
283 indent a block like a CSSStyleDeclaration to the given level
284 which may be higher than self._level (e.g. for CSSStyleDeclaration)
285 """
286 if not self.prefs.lineSeparator:
287 return text
288 return self.prefs.lineSeparator.join(
289 [u'%s%s' % (level * self.prefs.indent, line)
290 for line in text.split(self.prefs.lineSeparator)]
291 )
292
294 """
295 used by all styledeclarations to get the propertyname used
296 dependent on prefs setting defaultPropertyName and
297 keepAllProperties
298 """
299 if self.prefs.defaultPropertyName and not self.prefs.keepAllProperties:
300 return property.name
301 else:
302 return actual
303
305 if self.prefs.lineNumbers:
306 pad = len(str(text.count(self.prefs.lineSeparator)+1))
307 out = []
308 for i, line in enumerate(text.split(self.prefs.lineSeparator)):
309 out.append((u'%*i: %s') % (pad, i+1, line))
310 text = self.prefs.lineSeparator.join(out)
311 return text
312
314 """
315 returns s encloded between "..." and escaped delim charater ",
316 escape line breaks \\n \\r and \\f
317 """
318
319 s = s.replace('\n', '\\a ').replace(
320 '\r', '\\d ').replace(
321 '\f', '\\c ')
322 return u'"%s"' % s.replace('"', u'\\"')
323
324 - def _uri(self, uri):
330
332 "checks items valid property and prefs.validOnly"
333 return not self.prefs.validOnly or (self.prefs.validOnly and
334 x.valid)
335
359
368
370 """
371 serializes CSSCharsetRule
372 encoding: string
373
374 always @charset "encoding";
375 no comments or other things allowed!
376 """
377 if rule.wellformed:
378 return u'@charset %s;' % self._string(rule.encoding)
379 else:
380 return u''
381
405
407 """
408 serializes CSSImportRule
409
410 href
411 string
412 media
413 optional cssutils.stylesheets.medialist.MediaList
414 name
415 optional string
416
417 + CSSComments
418 """
419 if rule.wellformed:
420 out = Out(self)
421 out.append(self._atkeyword(rule, u'@import'))
422
423 for item in rule.seq:
424 typ, val = item.type, item.value
425 if 'href' == typ:
426
427 if self.prefs.importHrefFormat == 'string' or (
428 self.prefs.importHrefFormat != 'uri' and
429 rule.hreftype == 'string'):
430 out.append(val, 'STRING')
431 else:
432 if not len(self.prefs.spacer):
433 out.append(u' ')
434 out.append(val, 'URI')
435 elif 'media' == typ:
436
437 mediaText = self.do_stylesheets_medialist(val)
438 if mediaText and mediaText != u'all':
439 out.append(mediaText)
440 elif 'name' == typ:
441 out.append(val, 'STRING')
442 else:
443 out.append(val, typ)
444
445 return out.value(end=u';')
446 else:
447 return u''
448
450 """
451 serializes CSSNamespaceRule
452
453 uri
454 string
455 prefix
456 string
457
458 + CSSComments
459 """
460 if rule.wellformed:
461 out = Out(self)
462 out.append(self._atkeyword(rule, u'@namespace'))
463 if not len(self.prefs.spacer):
464 out.append(u' ')
465
466 for item in rule.seq:
467 typ, val = item.type, item.value
468 if 'namespaceURI' == typ:
469 out.append(val, 'STRING')
470 else:
471 out.append(val, typ)
472
473 return out.value(end=u';')
474 else:
475 return u''
476
529
531 """
532 serializes CSSPageRule
533
534 selectorText
535 string
536 style
537 CSSStyleDeclaration
538
539 + CSSComments
540 """
541 styleText = self.do_css_CSSStyleDeclaration(rule.style)
542
543 if styleText and rule.wellformed:
544 out = Out(self)
545 out.append(self._atkeyword(rule, u'@page'))
546 if not len(self.prefs.spacer):
547 out.append(u' ')
548
549 for item in rule.seq:
550 out.append(item.value, item.type)
551
552 out.append(u'{')
553 out.append(u'%s%s}' % (styleText, self.prefs.lineSeparator),
554 indent=1)
555 return out.value()
556 else:
557 return u''
558
560 """
561 serializes CSSUnknownRule
562 anything until ";" or "{...}"
563 + CSSComments
564 """
565 if rule.wellformed:
566 out = Out(self)
567 out.append(rule.atkeyword)
568 if not len(self.prefs.spacer):
569 out.append(u' ')
570
571 stacks = []
572 for item in rule.seq:
573 typ, val = item.type, item.value
574
575 if u'}' == val:
576
577 stackblock = stacks.pop().value()
578 if stackblock:
579 val = self._indentblock(
580 stackblock + self.prefs.lineSeparator + val,
581 min(1, len(stacks)+1))
582 else:
583 val = self._indentblock(val, min(1, len(stacks)+1))
584
585 if stacks:
586 stacks[-1].append(val, typ)
587 else:
588 out.append(val, typ)
589
590
591 if u'{' == val:
592
593 stacks.append(Out(self))
594
595 return out.value()
596 else:
597 return u''
598
600 """
601 serializes CSSStyleRule
602
603 selectorList
604 style
605
606 + CSSComments
607 """
608
609
610
611
612 if self.prefs.indentSpecificities:
613
614 elements = set([s.element for s in rule.selectorList])
615 specitivities = [s.specificity for s in rule.selectorList]
616 for selector in self._selectors:
617 lastelements = set([s.element for s in selector])
618 if elements.issubset(lastelements):
619
620 lastspecitivities = [s.specificity for s in selector]
621 if specitivities > lastspecitivities:
622 self._selectorlevel += 1
623 break
624 elif self._selectorlevel > 0:
625 self._selectorlevel -= 1
626 else:
627
628 self._selectors.append(rule.selectorList)
629 self._selectorlevel = 0
630
631
632
633 selectorText = self.do_css_SelectorList(rule.selectorList)
634 if not selectorText or not rule.wellformed:
635 return u''
636 self._level += 1
637 styleText = u''
638 try:
639 styleText = self.do_css_CSSStyleDeclaration(rule.style)
640 finally:
641 self._level -= 1
642 if not styleText:
643 if self.prefs.keepEmptyRules:
644 return u'%s%s{}' % (selectorText,
645 self.prefs.paranthesisSpacer)
646 else:
647 return self._indentblock(
648 u'%s%s{%s%s%s%s}' % (
649 selectorText,
650 self.prefs.paranthesisSpacer,
651 self.prefs.lineSeparator,
652 self._indentblock(styleText, self._level + 1),
653 self.prefs.lineSeparator,
654 (self._level + 1) * self.prefs.indent),
655 self._selectorlevel)
656
671
673 """
674 a single Selector including comments
675
676 an element has syntax (namespaceURI, name) where namespaceURI may be:
677
678 - cssutils._ANYNS => ``*|name``
679 - None => ``name``
680 - u'' => ``|name``
681 - any other value: => ``prefix|name``
682 """
683 if selector.wellformed:
684 out = Out(self)
685
686 DEFAULTURI = selector._namespaces.get('', None)
687 for item in selector.seq:
688 typ, val = item.type, item.value
689 if type(val) == tuple:
690
691 namespaceURI, name = val
692 if DEFAULTURI == namespaceURI or (not DEFAULTURI and
693 namespaceURI is None):
694 out.append(name, typ, space=False)
695 else:
696 if namespaceURI == cssutils._ANYNS:
697 prefix = u'*'
698 else:
699 try:
700 prefix = selector._namespaces.prefixForNamespaceURI(
701 namespaceURI)
702 except IndexError:
703 prefix = u''
704
705 out.append(u'%s|%s' % (prefix, name), typ, space=False)
706 else:
707 out.append(val, typ, space=False, keepS=True)
708
709 return out.value()
710 else:
711 return u''
712
765
811
813 """
814 a Properties priority "!" S* "important"
815 """
816
817
818 out = []
819 for part in priorityseq:
820 if hasattr(part, 'cssText'):
821 out.append(u' ')
822 out.append(part.cssText)
823 out.append(u' ')
824 else:
825 out.append(part)
826 return u''.join(out).strip()
827
829 """
830 serializes a CSSValue
831 """
832
833
834
835 if not cssvalue:
836 return u''
837 else:
838 sep = u',%s' % self.prefs.listItemSpacer
839 out = []
840 for part in cssvalue.seq:
841 if hasattr(part, 'cssText'):
842
843 out.append(part.cssText)
844 elif isinstance(part, basestring) and part == u',':
845 out.append(sep)
846 else:
847
848 if part and part[0] == part[-1] and part[0] in '\'"':
849
850 part = self._string(part[1:-1])
851 out.append(part)
852 return (u''.join(out)).strip()
853
866
884