1
2
3 """serializer classes for CSS classes
4
5 """
6 __all__ = ['CSSSerializer']
7 __docformat__ = 'restructuredtext'
8 __author__ = '$LastChangedBy: cthedot $'
9 __date__ = '$LastChangedDate: 2008-01-12 22:36:38 +0100 (Sa, 12 Jan 2008) $'
10 __version__ = '$LastChangedRevision: 833 $'
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.args[1][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 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
56 defaultAtKeyword=%r,
57 defaultPropertyName=%r,
58 importHrefFormat=%r,
59 indent=%r,
60 keepAllProperties=%r,
61 keepComments=%r,
62 keepEmptyRules=%r,
63 lineNumbers=%r,
64 lineSeparator=%r,
65 listItemSpacer=%r,
66 omitLastSemicolon=%r,
67 paranthesisSpacer=%r,
68 propertyNameSpacer=%r,
69 validOnly=%r,
70 wellformedOnly=%r,
71
72
73 keepComments = True
74 If ``False`` removes all CSSComments
75 keepEmptyRules = False
76 defines if empty rules like e.g. ``a {}`` are kept in the resulting
77 serialized sheet
78
79 lineNumbers = False
80 Only used if a complete CSSStyleSheet is serialized.
81 lineSeparator = u'\\n'
82 How to end a line. This may be set to e.g. u'' for serializing of
83 CSSStyleDeclarations usable in HTML style attribute.
84 listItemSpacer = u' '
85 string which is used in ``css.SelectorList``, ``css.CSSValue`` and
86 ``stylesheets.MediaList`` after the comma
87 omitLastSemicolon = True
88 If ``True`` omits ; after last property of CSSStyleDeclaration
89 paranthesisSpacer = u' '
90 string which is used before an opening paranthesis like in a
91 ``css.CSSMediaRule`` or ``css.CSSStyleRule``
92 propertyNameSpacer = u' '
93 string which is used after a Property name colon
94
95 validOnly = False (**not anywhere used yet**)
96 if True only valid (Properties or Rules) are kept
97
98 A Property is valid if it is a known Property with a valid value.
99 Currently CSS 2.1 values as defined in cssproperties.py would be
100 valid.
101
102 wellformedOnly = True (**not anywhere used yet**)
103 only wellformed properties and rules are kept
104
105 **DEPRECATED**: removeInvalid = True
106 Omits invalid rules, replaced by ``validOnly`` which will be used
107 more cases
108
109 """
119
121 "reset all preference options to the default value"
122 self.defaultAtKeyword = True
123 self.defaultPropertyName = True
124 self.importHrefFormat = None
125 self.indent = 4 * u' '
126 self.indentSpecificities = False
127 self.keepAllProperties = True
128 self.keepComments = True
129 self.keepEmptyRules = False
130 self.lineNumbers = False
131 self.lineSeparator = u'\n'
132 self.listItemSpacer = u' '
133 self.omitLastSemicolon = True
134 self.paranthesisSpacer = u' '
135 self.propertyNameSpacer = u' '
136 self.validOnly = False
137 self.wellformedOnly = True
138
139 self.removeInvalid = True
140
142 """
143 sets options to achive a minified stylesheet
144
145 you may want to set preferences with this convinience method
146 and set settings you want adjusted afterwards
147 """
148 self.importHrefFormat = 'string'
149 self.indent = u''
150 self.keepComments = False
151 self.keepEmptyRules = False
152 self.lineNumbers = False
153 self.lineSeparator = u''
154 self.listItemSpacer = u''
155 self.omitLastSemicolon = True
156 self.paranthesisSpacer = u''
157 self.propertyNameSpacer = u''
158 self.validOnly = False
159 self.wellformedOnly = True
160
162 return u"cssutils.css.%s(%s)" % (self.__class__.__name__,
163 u', '.join(['\n %s=%r' % (p, self.__getattribute__(p)) for p in self.__dict__]
164 ))
165
167 return u"<cssutils.css.%s object %s at 0x%x" % (self.__class__.__name__,
168 u' '.join(['%s=%r' % (p, self.__getattribute__(p)) for p in self.__dict__]
169 ),
170 id(self))
171
172
174 """
175 Methods to serialize a CSSStylesheet and its parts
176
177 To use your own serializing method the easiest is to subclass CSS
178 Serializer and overwrite the methods you like to customize.
179 """
180 __notinurimatcher = re.compile(ur'''.*?[\)\s\;]''', re.U).match
181
182
184 """
185 prefs
186 instance of Preferences
187 """
188 if not prefs:
189 prefs = Preferences()
190 self.prefs = prefs
191 self._level = 0
192
193 self._selectors = []
194 self._selectorlevel = 0
195
197 if self.prefs.lineNumbers:
198 pad = len(str(text.count(self.prefs.lineSeparator)+1))
199 out = []
200 for i, line in enumerate(text.split(self.prefs.lineSeparator)):
201 out.append((u'%*i: %s') % (pad, i+1, line))
202 text = self.prefs.lineSeparator.join(out)
203 return text
204
206
207 if self.prefs.removeInvalid and \
208 hasattr(x, 'valid') and not x.valid:
209 return True
210 else:
211 return False
212
214 """
215 checks if valid items only and if yes it item is valid
216 """
217 return not self.prefs.validOnly or (self.prefs.validOnly and
218 hasattr(x, 'valid') and
219 x.valid)
220
227
229 """
230 escapes delim charaters in string s with \delim
231 s might not have " or ' around it!
232
233 escape line breaks \n \r and \f
234 """
235
236 s = s.replace('\n', '\\a ').replace(
237 '\r', '\\d ').replace(
238 '\f', '\\c ')
239 return s.replace(delim, u'\\%s' % delim)
240
242 """
243 escapes unescaped ", ' or \n in s if not escaped already
244 s always has "..." or '...' around
245 """
246 r = s[0]
247 out = [r]
248 for c in s[1:-1]:
249 if c == '\n':
250 out.append(u'\\a ')
251 continue
252 elif c == '\r':
253 out.append(u'\\d ')
254 continue
255 elif c == '\f':
256 out.append(u'\\c ')
257 continue
258 elif c == r and out[-1] != u'\\':
259 out.append(u'\\')
260 out.append(c)
261 out.append(r)
262 s = u''.join(out)
263 return s
264
266 """
267 used by all @rules to get the keyword used
268 dependent on prefs setting defaultAtKeyword
269 """
270 if self.prefs.defaultAtKeyword:
271 return default
272 else:
273 return rule.atkeyword
274
276 """
277 used by all styledeclarations to get the propertyname used
278 dependent on prefs setting defaultPropertyName
279 """
280 if self.prefs.defaultPropertyName and \
281 not self.prefs.keepAllProperties:
282 return property.name
283 else:
284 return actual
285
287 """
288 indent a block like a CSSStyleDeclaration to the given level
289 which may be higher than self._level (e.g. for CSSStyleDeclaration)
290 """
291 if not self.prefs.lineSeparator:
292 return text
293 return self.prefs.lineSeparator.join(
294 [u'%s%s' % (level * self.prefs.indent, line)
295 for line in text.split(self.prefs.lineSeparator)]
296 )
297
298 - def _uri(self, uri):
306
324
338
354
363
365 """
366 serializes CSSCharsetRule
367 encoding: string
368
369 always @charset "encoding";
370 no comments or other things allowed!
371 """
372 if not rule.encoding or self._noinvalids(rule):
373 return u''
374 return u'@charset "%s";' % self._escapestring(rule.encoding)
375
377 """
378 serializes CSSFontFaceRule
379
380 style
381 CSSStyleDeclaration
382
383 + CSSComments
384 """
385 self._level += 1
386 try:
387 styleText = self.do_css_CSSStyleDeclaration(rule.style)
388 finally:
389 self._level -= 1
390
391 if not styleText or self._noinvalids(rule):
392 return u''
393
394 before = []
395 for x in rule.seq:
396 if hasattr(x, 'cssText'):
397 before.append(x.cssText)
398 else:
399
400 raise SyntaxErr('serializing CSSFontFaceRule: unexpected %r' % x)
401 if before:
402 before = u' '.join(before).strip()
403 if before:
404 before = u' %s' % before
405 else:
406 before = u''
407
408 return u'%s%s {%s%s%s%s}' % (
409 self._getatkeyword(rule, u'@font-face'),
410 before,
411 self.prefs.lineSeparator,
412 self._indentblock(styleText, self._level + 1),
413 self.prefs.lineSeparator,
414 (self._level + 1) * self.prefs.indent
415 )
416
450
452 """
453 serializes CSSNamespaceRule
454
455 uri
456 string
457 prefix
458 string
459
460 + CSSComments
461 """
462 if not rule.namespaceURI or self._noinvalids(rule):
463 return u''
464
465 out = [u'%s' % self._getatkeyword(rule, u'@namespace')]
466 for part in rule.seq:
467 if rule.prefix == part and part != u'':
468 out.append(u' %s' % part)
469 elif rule.namespaceURI == part:
470 out.append(u' "%s"' % self._escapestring(part))
471 elif hasattr(part, 'cssText'):
472 out.append(part.cssText)
473 return u'%s;' % u''.join(out)
474
508
510 """
511 serializes CSSPageRule
512
513 selectorText
514 string
515 style
516 CSSStyleDeclaration
517
518 + CSSComments
519 """
520 self._level += 1
521 try:
522 styleText = self.do_css_CSSStyleDeclaration(rule.style)
523 finally:
524 self._level -= 1
525
526 if not styleText or self._noinvalids(rule):
527 return u''
528
529 return u'%s%s {%s%s%s%s}' % (
530 self._getatkeyword(rule, u'@page'),
531 self.do_pageselector(rule.seq),
532 self.prefs.lineSeparator,
533 self._indentblock(styleText, self._level + 1),
534 self.prefs.lineSeparator,
535 (self._level + 1) * self.prefs.indent
536 )
537
538 - def do_pageselector(self, seq):
539 """
540 a selector of a CSSPageRule including comments
541 """
542 if len(seq) == 0 or self._noinvalids(seq):
543 return u''
544 else:
545 out = []
546 for part in seq:
547 if hasattr(part, 'cssText'):
548 out.append(part.cssText)
549 else:
550 out.append(part)
551 return u' %s' % u''.join(out)
552
554 """
555 serializes CSSUnknownRule
556 anything until ";" or "{...}"
557 + CSSComments
558 """
559 if rule.atkeyword and not self._noinvalids(rule):
560 out = [u'%s' % rule.atkeyword]
561 for part in rule.seq:
562 if isinstance(part, cssutils.css.csscomment.CSSComment):
563 if self.prefs.keepComments:
564 out.append(part.cssText)
565 else:
566 out.append(part)
567 if not (out[-1].endswith(u';') or out[-1].endswith(u'}')):
568 out.append(u';')
569 return u''.join(out)
570 else:
571 return u''
572
574 """
575 serializes CSSStyleRule
576
577 selectorList
578 style
579
580 + CSSComments
581 """
582
583
584 if self.prefs.indentSpecificities:
585
586 elements = set([s.element for s in rule.selectorList])
587 specitivities = [s.specificity for s in rule.selectorList]
588 for selector in self._selectors:
589 lastelements = set([s.element for s in selector])
590 if elements.issubset(lastelements):
591
592 lastspecitivities = [s.specificity for s in selector]
593 if specitivities > lastspecitivities:
594 self._selectorlevel += 1
595 break
596 elif self._selectorlevel > 0:
597 self._selectorlevel -= 1
598 else:
599
600 self._selectors.append(rule.selectorList)
601 self._selectorlevel = 0
602
603
604
605 selectorText = self.do_css_SelectorList(rule.selectorList)
606 if not selectorText or self._noinvalids(rule):
607 return u''
608 self._level += 1
609 styleText = u''
610 try:
611 styleText = self.do_css_CSSStyleDeclaration(rule.style)
612 finally:
613 self._level -= 1
614 if not styleText:
615 if self.prefs.keepEmptyRules:
616 return u'%s%s{}' % (selectorText,
617 self.prefs.paranthesisSpacer)
618 else:
619 return self._indentblock(
620 u'%s%s{%s%s%s%s}' % (
621 selectorText,
622 self.prefs.paranthesisSpacer,
623 self.prefs.lineSeparator,
624 self._indentblock(styleText, self._level + 1),
625 self.prefs.lineSeparator,
626 (self._level + 1) * self.prefs.indent),
627 self._selectorlevel)
628
649
651 """
652 returns prefix of given nsuri as defined in given
653 dict (prefix: uri} or TODO: from CSSNamespaceRules?
654 """
655 if namespaceURI is None:
656 return u'*'
657 for prefix, uri in namespaces.items():
658 if uri == namespaceURI:
659 return prefix
660 return u''
661
694
696 """
697 Style declaration of CSSStyleRule
698 """
699 if len(style.seq) > 0 and self._wellformed(style) and\
700 self._valid(style):
701 if separator is None:
702 separator = self.prefs.lineSeparator
703
704 if self.prefs.keepAllProperties:
705 parts = style.seq
706 else:
707
708 nnames = set()
709 for x in style.seq:
710 if isinstance(x, cssutils.css.Property):
711 nnames.add(x.name)
712
713 parts = []
714 for x in reversed(style.seq):
715 if isinstance(x, cssutils.css.Property):
716 if x.name in nnames:
717 parts.append(x)
718 nnames.remove(x.name)
719 else:
720 parts.append(x)
721 parts.reverse()
722
723 out = []
724 for (i, part) in enumerate(parts):
725 if isinstance(part, cssutils.css.CSSComment):
726
727 if self.prefs.keepComments:
728 out.append(part.cssText)
729 out.append(separator)
730 elif isinstance(part, cssutils.css.Property):
731
732 out.append(self.do_Property(part))
733 if not (self.prefs.omitLastSemicolon and i==len(parts)-1):
734 out.append(u';')
735 out.append(separator)
736 else:
737
738 out.append(part)
739
740 if out and out[-1] == separator:
741 del out[-1]
742
743 return u''.join(out)
744
745 else:
746 return u''
747
788
790 """
791 a Properties priority "!" S* "important"
792 """
793 out = []
794 for part in priorityseq:
795 if hasattr(part, 'cssText'):
796 out.append(u' ')
797 out.append(part.cssText)
798 out.append(u' ')
799 else:
800 out.append(part)
801 return u''.join(out).strip()
802
804 """
805 serializes a CSSValue
806 """
807 if not cssvalue:
808 return u''
809 else:
810 sep = u',%s' % self.prefs.listItemSpacer
811 out = []
812 for part in cssvalue.seq:
813 if hasattr(part, 'cssText'):
814
815 out.append(part.cssText)
816 elif isinstance(part, basestring) and part == u',':
817 out.append(sep)
818 else:
819
820 if part and part[0] == part[-1] and part[0] in '\'"':
821 part = self._escapeSTRINGtype(part)
822 out.append(part)
823 return (u''.join(out)).strip()
824