1
2
3 """serializer classes for CSS classes
4
5 """
6 __all__ = ['CSSSerializer', 'Preferences']
7 __docformat__ = 'restructuredtext'
8 __version__ = '$Id: serialize.py 1116 2008-03-05 13:52:23Z 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 """
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 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 \n after
188 - val ;
189 removes S before and adds \n 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 cssutils.css.CSSRule.UNKNOWN_RULE == typ:
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
225 if val in u'+>~':
226 self.out.insert(-1, self.ser.prefs.selectorCombinatorSpacer)
227 self.out.append(self.ser.prefs.selectorCombinatorSpacer)
228 elif u',' == val:
229 self.out.append(self.ser.prefs.listItemSpacer)
230 elif u':' == val:
231 self.out.append(self.ser.prefs.propertyNameSpacer)
232 elif u'{' == val:
233 self.out.insert(-1, self.ser.prefs.paranthesisSpacer)
234 self.out.append(self.ser.prefs.lineSeparator)
235 elif u';' == val:
236 self.out.append(self.ser.prefs.lineSeparator)
237 elif val not in u'}[]()' and space:
238 self.out.append(self.ser.prefs.spacer)
239
240 - def value(self, delim=u'', end=None):
241 "returns all items joined by delim"
242 self._remove_last_if_S()
243 if end:
244 self.out.append(end)
245 return delim.join(self.out)
246
247
249 """
250 Methods to serialize a CSSStylesheet and its parts
251
252 To use your own serializing method the easiest is to subclass CSS
253 Serializer and overwrite the methods you like to customize.
254 """
255
256 __forbidden_in_uri_matcher = re.compile(ur'''.*?[\)\s\;]''', re.U).match
257
259 """
260 prefs
261 instance of Preferences
262 """
263 if not prefs:
264 prefs = Preferences()
265 self.prefs = prefs
266 self._level = 0
267
268
269 self._selectors = []
270 self._selectorlevel = 0
271
273 "returns default or source atkeyword depending on prefs"
274 if self.prefs.defaultAtKeyword:
275 return default
276 else:
277 return rule.atkeyword
278
280 """
281 indent a block like a CSSStyleDeclaration to the given level
282 which may be higher than self._level (e.g. for CSSStyleDeclaration)
283 """
284 if not self.prefs.lineSeparator:
285 return text
286 return self.prefs.lineSeparator.join(
287 [u'%s%s' % (level * self.prefs.indent, line)
288 for line in text.split(self.prefs.lineSeparator)]
289 )
290
292 """
293 used by all styledeclarations to get the propertyname used
294 dependent on prefs setting defaultPropertyName and
295 keepAllProperties
296 """
297 if self.prefs.defaultPropertyName and not self.prefs.keepAllProperties:
298 return property.name
299 else:
300 return actual
301
303 if self.prefs.lineNumbers:
304 pad = len(str(text.count(self.prefs.lineSeparator)+1))
305 out = []
306 for i, line in enumerate(text.split(self.prefs.lineSeparator)):
307 out.append((u'%*i: %s') % (pad, i+1, line))
308 text = self.prefs.lineSeparator.join(out)
309 return text
310
312 """
313 returns s encloded between "..." and escaped delim charater ",
314 escape line breaks \\n \\r and \\f
315 """
316
317 s = s.replace('\n', '\\a ').replace(
318 '\r', '\\d ').replace(
319 '\f', '\\c ')
320 return u'"%s"' % s.replace('"', u'\\"')
321
322 - def _uri(self, uri):
328
330 "checks items valid property and prefs.validOnly"
331 return not self.prefs.validOnly or (self.prefs.validOnly and
332 x.valid)
333
357
367
369 """
370 serializes CSSCharsetRule
371 encoding: string
372
373 always @charset "encoding";
374 no comments or other things allowed!
375 """
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 out.append(val, 'URI')
433 elif 'media' == typ:
434
435 mediaText = self.do_stylesheets_medialist(val)
436 if mediaText and mediaText != u'all':
437 out.append(mediaText)
438 elif 'name' == typ:
439 out.append(val, 'STRING')
440 else:
441 out.append(val, typ)
442
443 return out.value(end=u';')
444 else:
445 return u''
446
448 """
449 serializes CSSNamespaceRule
450
451 uri
452 string
453 prefix
454 string
455
456 + CSSComments
457 """
458 if rule.wellformed:
459 out = Out(self)
460 out.append(self._atkeyword(rule, u'@namespace'))
461
462 for item in rule.seq:
463 typ, val = item.type, item.value
464 if 'namespaceURI' == typ:
465 out.append(val, 'STRING')
466 else:
467 out.append(val, typ)
468
469 return out.value(end=u';')
470 else:
471 return u''
472
520
522 """
523 serializes CSSPageRule
524
525 selectorText
526 string
527 style
528 CSSStyleDeclaration
529
530 + CSSComments
531 """
532 styleText = self.do_css_CSSStyleDeclaration(rule.style)
533
534 if styleText and rule.wellformed:
535 out = Out(self)
536 out.append(self._atkeyword(rule, u'@page'))
537
538 for item in rule.seq:
539 out.append(item.value, item.type)
540
541 out.append(u'{')
542 out.append(u'%s%s}' % (styleText, self.prefs.lineSeparator),
543 indent=1)
544 return out.value()
545 else:
546 return u''
547
549 """
550 serializes CSSUnknownRule
551 anything until ";" or "{...}"
552 + CSSComments
553 """
554 if rule.wellformed:
555 out = Out(self)
556 out.append(rule.atkeyword)
557 stacks = []
558 for item in rule.seq:
559 typ, val = item.type, item.value
560
561
562 if u'}' == val:
563
564 stackblock = stacks.pop().value()
565 if stackblock:
566 val = self._indentblock(
567 stackblock + self.prefs.lineSeparator + val,
568 min(1, len(stacks)+1))
569
570 if stacks:
571 stacks[-1].append(val, typ)
572 else:
573 out.append(val, typ)
574
575
576 if u'{' == val:
577
578 stacks.append(Out(self))
579
580 return out.value()
581 else:
582 return u''
583
585 """
586 serializes CSSStyleRule
587
588 selectorList
589 style
590
591 + CSSComments
592 """
593
594
595
596
597 if self.prefs.indentSpecificities:
598
599 elements = set([s.element for s in rule.selectorList])
600 specitivities = [s.specificity for s in rule.selectorList]
601 for selector in self._selectors:
602 lastelements = set([s.element for s in selector])
603 if elements.issubset(lastelements):
604
605 lastspecitivities = [s.specificity for s in selector]
606 if specitivities > lastspecitivities:
607 self._selectorlevel += 1
608 break
609 elif self._selectorlevel > 0:
610 self._selectorlevel -= 1
611 else:
612
613 self._selectors.append(rule.selectorList)
614 self._selectorlevel = 0
615
616
617
618 selectorText = self.do_css_SelectorList(rule.selectorList)
619 if not selectorText or not rule.wellformed:
620 return u''
621 self._level += 1
622 styleText = u''
623 try:
624 styleText = self.do_css_CSSStyleDeclaration(rule.style)
625 finally:
626 self._level -= 1
627 if not styleText:
628 if self.prefs.keepEmptyRules:
629 return u'%s%s{}' % (selectorText,
630 self.prefs.paranthesisSpacer)
631 else:
632 return self._indentblock(
633 u'%s%s{%s%s%s%s}' % (
634 selectorText,
635 self.prefs.paranthesisSpacer,
636 self.prefs.lineSeparator,
637 self._indentblock(styleText, self._level + 1),
638 self.prefs.lineSeparator,
639 (self._level + 1) * self.prefs.indent),
640 self._selectorlevel)
641
656
658 """
659 a single Selector including comments
660
661 an element has syntax (namespaceURI, name) where namespaceURI may be:
662
663 - cssutils._ANYNS => ``*|name``
664 - None => ``name``
665 - u'' => ``|name``
666 - any other value: => ``prefix|name``
667 """
668 if selector.wellformed:
669 out = Out(self)
670
671 DEFAULTURI = selector._namespaces.get('', None)
672 for item in selector.seq:
673 typ, val = item.type, item.value
674 if type(val) == tuple:
675
676 namespaceURI, name = val
677 if DEFAULTURI == namespaceURI or (not DEFAULTURI and
678 namespaceURI is None):
679 out.append(name, typ, space=False)
680 else:
681 if namespaceURI == cssutils._ANYNS:
682 prefix = u'*'
683 else:
684 try:
685 prefix = selector._namespaces.prefixForNamespaceURI(
686 namespaceURI)
687 except IndexError:
688 prefix = u''
689
690 out.append(u'%s|%s' % (prefix, name), typ, space=False)
691 else:
692 out.append(val, typ, space=False, keepS=True)
693
694 return out.value()
695 else:
696 return u''
697
699 """
700 Style declaration of CSSStyleRule
701 """
702
703
704
705 if len(style.seq) > 0:
706 if separator is None:
707 separator = self.prefs.lineSeparator
708
709 if self.prefs.keepAllProperties:
710
711 parts = style.seq
712 else:
713
714 _effective = style.getProperties()
715 parts = [x for x in style.seq
716 if (isinstance(x, cssutils.css.Property)
717 and x in _effective)
718 or not isinstance(x, cssutils.css.Property)]
719
720 out = []
721 for i, part in enumerate(parts):
722 if isinstance(part, cssutils.css.CSSComment):
723
724 if self.prefs.keepComments:
725 out.append(part.cssText)
726 out.append(separator)
727 elif isinstance(part, cssutils.css.Property):
728
729 out.append(self.do_Property(part))
730 if not (self.prefs.omitLastSemicolon and i==len(parts)-1):
731 out.append(u';')
732 out.append(separator)
733 else:
734
735 out.append(part)
736
737 if out and out[-1] == separator:
738 del out[-1]
739
740 return u''.join(out)
741
742 else:
743 return u''
744
790
792 """
793 a Properties priority "!" S* "important"
794 """
795
796
797 out = []
798 for part in priorityseq:
799 if hasattr(part, 'cssText'):
800 out.append(u' ')
801 out.append(part.cssText)
802 out.append(u' ')
803 else:
804 out.append(part)
805 return u''.join(out).strip()
806
808 """
809 serializes a CSSValue
810 """
811
812
813
814 if not cssvalue:
815 return u''
816 else:
817 sep = u',%s' % self.prefs.listItemSpacer
818 out = []
819 for part in cssvalue.seq:
820 if hasattr(part, 'cssText'):
821
822 out.append(part.cssText)
823 elif isinstance(part, basestring) and part == u',':
824 out.append(sep)
825 else:
826
827 if part and part[0] == part[-1] and part[0] in '\'"':
828
829 part = self._string(part[1:-1])
830 out.append(part)
831 return (u''.join(out)).strip()
832
845
863