1
2
3 """serializer classes for CSS classes
4
5 """
6 __all__ = ['CSSSerializer', 'Preferences']
7 __docformat__ = 'restructuredtext'
8 __author__ = '$LastChangedBy: cthedot $'
9 __date__ = '$LastChangedDate: 2008-02-02 23:52:31 +0100 (Sa, 02 Feb 2008) $'
10 __version__ = '$LastChangedRevision: 972 $'
11
12 import codecs
13 import re
14 import cssutils
15 import util
16
18 """
19 Escapes characters not allowed in the current encoding the CSS way
20 with a backslash followed by a uppercase hex code point
21
22 E.g. the german umlaut 'ä' is escaped as \E4
23 """
24 s = e.object[e.start:e.end]
25 return u''.join([ur'\%s ' % str(hex(ord(x)))[2:]
26 .upper() for x in s]), e.end
27
28 codecs.register_error('escapecss', _escapecss)
29
30
32 """
33 controls output of CSSSerializer
34
35 defaultAtKeyword = True
36 Should the literal @keyword from src CSS be used or the default
37 form, e.g. if ``True``: ``@import`` else: ``@i\mport``
38 defaultPropertyName = True
39 Should the normalized propertyname be used or the one given in
40 the src file, e.g. if ``True``: ``color`` else: ``c\olor``
41
42 Only used if ``keepAllProperties==False``.
43
44 defaultPropertyPriority = True
45 Should the normalized or literal priority be used, e.g. '!important'
46 or u'!Im\portant'
47
48 importHrefFormat = None
49 Uses hreftype if ``None`` or explicit ``'string'`` or ``'uri'``
50 indent = 4 * ' '
51 Indentation of e.g Properties inside a CSSStyleDeclaration
52 indentSpecificities = False
53 Indent rules with subset of Selectors and higher Specitivity
54
55 keepAllProperties = True
56 If ``True`` all properties set in the original CSSStylesheet
57 are kept meaning even properties set twice with the exact same
58 same name are kept!
59 keepComments = True
60 If ``False`` removes all CSSComments
61 keepEmptyRules = False
62 defines if empty rules like e.g. ``a {}`` are kept in the resulting
63 serialized sheet
64 keepUsedNamespaceRulesOnly = False
65 if True only namespace rules which are actually used are kept
66
67 lineNumbers = False
68 Only used if a complete CSSStyleSheet is serialized.
69 lineSeparator = u'\\n'
70 How to end a line. This may be set to e.g. u'' for serializing of
71 CSSStyleDeclarations usable in HTML style attribute.
72 listItemSpacer = u' '
73 string which is used in ``css.SelectorList``, ``css.CSSValue`` and
74 ``stylesheets.MediaList`` after the comma
75 omitLastSemicolon = True
76 If ``True`` omits ; after last property of CSSStyleDeclaration
77 paranthesisSpacer = u' '
78 string which is used before an opening paranthesis like in a
79 ``css.CSSMediaRule`` or ``css.CSSStyleRule``
80 propertyNameSpacer = u' '
81 string which is used after a Property name colon
82 selectorCombinatorSpacer = u' '
83 string which is used before and after a Selector combinator like +, > or ~.
84 CSSOM defines a single space for this which is also the default in cssutils.
85
86 validOnly = False (**not anywhere used yet**)
87 if True only valid (Properties or Rules) are kept
88
89 A Property is valid if it is a known Property with a valid value.
90 Currently CSS 2.1 values as defined in cssproperties.py would be
91 valid.
92
93 wellformedOnly = True (**not anywhere used yet**)
94 only wellformed properties and rules are kept
95
96 **DEPRECATED**: removeInvalid = True
97 Omits invalid rules, replaced by ``validOnly`` which will be used
98 more cases
99
100 """
110
112 "reset all preference options to the default value"
113 self.defaultAtKeyword = True
114 self.defaultPropertyName = True
115 self.defaultPropertyPriority = True
116 self.importHrefFormat = None
117 self.indent = 4 * u' '
118 self.indentSpecificities = False
119 self.keepAllProperties = True
120 self.keepComments = True
121 self.keepEmptyRules = False
122 self.keepUsedNamespaceRulesOnly = False
123 self.lineNumbers = False
124 self.lineSeparator = u'\n'
125 self.listItemSpacer = u' '
126 self.omitLastSemicolon = True
127 self.paranthesisSpacer = u' '
128 self.propertyNameSpacer = u' '
129 self.selectorCombinatorSpacer = u' '
130 self.validOnly = False
131 self.wellformedOnly = True
132
133 self.removeInvalid = True
134
136 """
137 sets options to achive a minified stylesheet
138
139 you may want to set preferences with this convinience method
140 and set settings you want adjusted afterwards
141 """
142 self.importHrefFormat = 'string'
143 self.indent = u''
144 self.keepComments = False
145 self.keepEmptyRules = False
146 self.keepUsedNamespaceRulesOnly = True
147 self.lineNumbers = False
148 self.lineSeparator = u''
149 self.listItemSpacer = u''
150 self.omitLastSemicolon = True
151 self.paranthesisSpacer = u''
152 self.propertyNameSpacer = u''
153 self.selectorCombinatorSpacer = u''
154 self.validOnly = False
155 self.wellformedOnly = True
156
158 return u"cssutils.css.%s(%s)" % (self.__class__.__name__,
159 u', '.join(['\n %s=%r' % (p, self.__getattribute__(p)) for p in self.__dict__]
160 ))
161
163 return u"<cssutils.css.%s object %s at 0x%x" % (self.__class__.__name__,
164 u' '.join(['%s=%r' % (p, self.__getattribute__(p)) for p in self.__dict__]
165 ),
166 id(self))
167
168
170 """
171 Methods to serialize a CSSStylesheet and its parts
172
173 To use your own serializing method the easiest is to subclass CSS
174 Serializer and overwrite the methods you like to customize.
175 """
176 __notinurimatcher = re.compile(ur'''.*?[\)\s\;]''', re.U).match
177
178
180 """
181 prefs
182 instance of Preferences
183 """
184 if not prefs:
185 prefs = Preferences()
186 self.prefs = prefs
187 self._level = 0
188
189 self._selectors = []
190 self._selectorlevel = 0
191
193 if self.prefs.lineNumbers:
194 pad = len(str(text.count(self.prefs.lineSeparator)+1))
195 out = []
196 for i, line in enumerate(text.split(self.prefs.lineSeparator)):
197 out.append((u'%*i: %s') % (pad, i+1, line))
198 text = self.prefs.lineSeparator.join(out)
199 return text
200
202
203 if self.prefs.removeInvalid and \
204 hasattr(x, 'valid') and not x.valid:
205 return True
206 else:
207 return False
208
210 """
211 checks if valid items only and if yes it item is valid
212 """
213 return not self.prefs.validOnly or (self.prefs.validOnly and
214 hasattr(x, 'valid') and
215 x.valid)
216
223
225 """
226 escapes delim charaters in string s with delim
227 s might not have " or ' around it!
228
229 escape line breaks \\n \\r and \\f
230 """
231
232 s = s.replace('\n', '\\a ').replace(
233 '\r', '\\d ').replace(
234 '\f', '\\c ')
235 return s.replace(delim, u'\\%s' % delim)
236
238 """
239 escapes unescaped ", ' or \\n in s if not escaped already
240 s always has "..." or '...' around
241 """
242 r = s[0]
243 out = [r]
244 for c in s[1:-1]:
245 if c == '\n':
246 out.append(u'\\a ')
247 continue
248 elif c == '\r':
249 out.append(u'\\d ')
250 continue
251 elif c == '\f':
252 out.append(u'\\c ')
253 continue
254 elif c == r and out[-1] != u'\\':
255 out.append(u'\\')
256 out.append(c)
257 out.append(r)
258 s = u''.join(out)
259 return s
260
262 """
263 used by all @rules to get the keyword used
264 dependent on prefs setting defaultAtKeyword
265 """
266 if self.prefs.defaultAtKeyword:
267 return default
268 else:
269 return rule.atkeyword
270
272 """
273 used by all styledeclarations to get the propertyname used
274 dependent on prefs setting defaultPropertyName and
275 keepAllProperties
276 """
277 if self.prefs.defaultPropertyName and not self.prefs.keepAllProperties:
278 return property.name
279 else:
280 return actual
281
283 """
284 indent a block like a CSSStyleDeclaration to the given level
285 which may be higher than self._level (e.g. for CSSStyleDeclaration)
286 """
287 if not self.prefs.lineSeparator:
288 return text
289 return self.prefs.lineSeparator.join(
290 [u'%s%s' % (level * self.prefs.indent, line)
291 for line in text.split(self.prefs.lineSeparator)]
292 )
293
294 - def _uri(self, uri):
302
320
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 not rule.encoding or self._noinvalids(rule):
377 return u''
378 return u'@charset "%s";' % self._escapestring(rule.encoding)
379
381 """
382 serializes CSSFontFaceRule
383
384 style
385 CSSStyleDeclaration
386
387 + CSSComments
388 """
389 self._level += 1
390 try:
391 styleText = self.do_css_CSSStyleDeclaration(rule.style)
392 finally:
393 self._level -= 1
394
395 if not styleText or self._noinvalids(rule):
396 return u''
397
398 before = []
399 for x in rule.seq:
400 if hasattr(x, 'cssText'):
401 before.append(x.cssText)
402 else:
403
404 raise SyntaxErr('serializing CSSFontFaceRule: unexpected %r' % x)
405 if before:
406 before = u' '.join(before).strip()
407 if before:
408 before = u' %s' % before
409 else:
410 before = u''
411
412 return u'%s%s {%s%s%s%s}' % (
413 self._getatkeyword(rule, u'@font-face'),
414 before,
415 self.prefs.lineSeparator,
416 self._indentblock(styleText, self._level + 1),
417 self.prefs.lineSeparator,
418 (self._level + 1) * self.prefs.indent
419 )
420
454
484
518
520 """
521 serializes CSSPageRule
522
523 selectorText
524 string
525 style
526 CSSStyleDeclaration
527
528 + CSSComments
529 """
530 self._level += 1
531 try:
532 styleText = self.do_css_CSSStyleDeclaration(rule.style)
533 finally:
534 self._level -= 1
535
536 if not styleText or self._noinvalids(rule):
537 return u''
538
539 return u'%s%s {%s%s%s%s}' % (
540 self._getatkeyword(rule, u'@page'),
541 self.do_pageselector(rule.seq),
542 self.prefs.lineSeparator,
543 self._indentblock(styleText, self._level + 1),
544 self.prefs.lineSeparator,
545 (self._level + 1) * self.prefs.indent
546 )
547
548 - def do_pageselector(self, seq):
549 """
550 a selector of a CSSPageRule including comments
551 """
552 if len(seq) == 0 or self._noinvalids(seq):
553 return u''
554 else:
555 out = []
556 for part in seq:
557 if hasattr(part, 'cssText'):
558 out.append(part.cssText)
559 else:
560 out.append(part)
561 return u' %s' % u''.join(out)
562
564 """
565 serializes CSSUnknownRule
566 anything until ";" or "{...}"
567 + CSSComments
568 """
569 if rule.atkeyword and not self._noinvalids(rule):
570 out = [u'%s' % rule.atkeyword]
571 for part in rule.seq:
572 if isinstance(part, cssutils.css.csscomment.CSSComment):
573 if self.prefs.keepComments:
574 out.append(part.cssText)
575 else:
576 out.append(part)
577 if not (out[-1].endswith(u';') or out[-1].endswith(u'}')):
578 out.append(u';')
579 return u''.join(out)
580 else:
581 return u''
582
584 """
585 serializes CSSStyleRule
586
587 selectorList
588 style
589
590 + CSSComments
591 """
592
593
594 if self.prefs.indentSpecificities:
595
596 elements = set([s.element for s in rule.selectorList])
597 specitivities = [s.specificity for s in rule.selectorList]
598 for selector in self._selectors:
599 lastelements = set([s.element for s in selector])
600 if elements.issubset(lastelements):
601
602 lastspecitivities = [s.specificity for s in selector]
603 if specitivities > lastspecitivities:
604 self._selectorlevel += 1
605 break
606 elif self._selectorlevel > 0:
607 self._selectorlevel -= 1
608 else:
609
610 self._selectors.append(rule.selectorList)
611 self._selectorlevel = 0
612
613
614
615 selectorText = self.do_css_SelectorList(rule.selectorList)
616 if not selectorText or self._noinvalids(rule):
617 return u''
618 self._level += 1
619 styleText = u''
620 try:
621 styleText = self.do_css_CSSStyleDeclaration(rule.style)
622 finally:
623 self._level -= 1
624 if not styleText:
625 if self.prefs.keepEmptyRules:
626 return u'%s%s{}' % (selectorText,
627 self.prefs.paranthesisSpacer)
628 else:
629 return self._indentblock(
630 u'%s%s{%s%s%s%s}' % (
631 selectorText,
632 self.prefs.paranthesisSpacer,
633 self.prefs.lineSeparator,
634 self._indentblock(styleText, self._level + 1),
635 self.prefs.lineSeparator,
636 (self._level + 1) * self.prefs.indent),
637 self._selectorlevel)
638
657
659 """
660 a single Selector including comments
661
662 an element has syntax (namespaceURI, name) where namespaceURI may be:
663
664 - cssutils._ANYNS => ``*|name``
665 - None => ``name``
666 - u'' => ``|name``
667 - any other value: => ``prefix|name``
668 """
669 if selector.seq and self._wellformed(selector) and\
670 self._valid(selector):
671 out = []
672 for item in selector.seq:
673 typ, val = item.type, item.value
674 if hasattr(val, 'cssText'):
675
676 out.append(val.cssText)
677 elif type(val) == tuple:
678
679 namespaceURI, name = val
680 if namespaceURI is None:
681 out.append(name)
682 else:
683 if namespaceURI == cssutils._ANYNS:
684 prefix = u'*'
685 else:
686 try:
687 prefix = selector._namespaces.prefixForNamespaceURI(
688 namespaceURI)
689 except IndexError:
690 prefix = u''
691
692 if prefix == u'*' and u'' not in selector._namespaces:
693 out.append(name)
694 else:
695 out.append(u'%s|%s' % (prefix, name))
696
697 else:
698 if typ == 'string':
699 val = self._escapeSTRINGtype(val)
700 elif typ in ('child', 'adjacent-sibling', 'following-sibling'):
701
702 val = u'%s%s%s' % (self.prefs.selectorCombinatorSpacer,
703 val,
704 self.prefs.selectorCombinatorSpacer)
705 out.append(val)
706 return u''.join(out)
707 else:
708 return u''
709
711 """
712 Style declaration of CSSStyleRule
713 """
714 if len(style.seq) > 0 and self._wellformed(style) and\
715 self._valid(style):
716 if separator is None:
717 separator = self.prefs.lineSeparator
718
719 if self.prefs.keepAllProperties:
720
721 parts = style.seq
722 else:
723
724 _effective = style.getProperties()
725 parts = [x for x in style.seq
726 if (isinstance(x, cssutils.css.Property)
727 and x in _effective)
728 or not isinstance(x, cssutils.css.Property)]
729
730 out = []
731 for i, part in enumerate(parts):
732 if isinstance(part, cssutils.css.CSSComment):
733
734 if self.prefs.keepComments:
735 out.append(part.cssText)
736 out.append(separator)
737 elif isinstance(part, cssutils.css.Property):
738
739 out.append(self.do_Property(part))
740 if not (self.prefs.omitLastSemicolon and i==len(parts)-1):
741 out.append(u';')
742 out.append(separator)
743 else:
744
745 out.append(part)
746
747 if out and out[-1] == separator:
748 del out[-1]
749
750 return u''.join(out)
751
752 else:
753 return u''
754
799
801 """
802 a Properties priority "!" S* "important"
803 """
804 out = []
805 for part in priorityseq:
806 if hasattr(part, 'cssText'):
807 out.append(u' ')
808 out.append(part.cssText)
809 out.append(u' ')
810 else:
811 out.append(part)
812 return u''.join(out).strip()
813
815 """
816 serializes a CSSValue
817 """
818 if not cssvalue:
819 return u''
820 else:
821 sep = u',%s' % self.prefs.listItemSpacer
822 out = []
823 for part in cssvalue.seq:
824 if hasattr(part, 'cssText'):
825
826 out.append(part.cssText)
827 elif isinstance(part, basestring) and part == u',':
828 out.append(sep)
829 else:
830
831 if part and part[0] == part[-1] and part[0] in '\'"':
832 part = self._escapeSTRINGtype(part)
833 out.append(part)
834 return (u''.join(out)).strip()
835