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