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-14 23:21:12 +0100 (Mo, 14 Jan 2008) $'
10 __version__ = '$LastChangedRevision: 850 $'
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
706 parts = style.seq
707 else:
708
709 _effective = style.getProperties()
710 parts = [x for x in style.seq
711 if (isinstance(x, cssutils.css.Property)
712 and x in _effective)
713 or not isinstance(x, cssutils.css.Property)]
714
715 out = []
716 for i, part in enumerate(parts):
717 if isinstance(part, cssutils.css.CSSComment):
718
719 if self.prefs.keepComments:
720 out.append(part.cssText)
721 out.append(separator)
722 elif isinstance(part, cssutils.css.Property):
723
724 out.append(self.do_Property(part))
725 if not (self.prefs.omitLastSemicolon and i==len(parts)-1):
726 out.append(u';')
727 out.append(separator)
728 else:
729
730 out.append(part)
731
732 if out and out[-1] == separator:
733 del out[-1]
734
735 return u''.join(out)
736
737 else:
738 return u''
739
780
782 """
783 a Properties priority "!" S* "important"
784 """
785 out = []
786 for part in priorityseq:
787 if hasattr(part, 'cssText'):
788 out.append(u' ')
789 out.append(part.cssText)
790 out.append(u' ')
791 else:
792 out.append(part)
793 return u''.join(out).strip()
794
796 """
797 serializes a CSSValue
798 """
799 if not cssvalue:
800 return u''
801 else:
802 sep = u',%s' % self.prefs.listItemSpacer
803 out = []
804 for part in cssvalue.seq:
805 if hasattr(part, 'cssText'):
806
807 out.append(part.cssText)
808 elif isinstance(part, basestring) and part == u',':
809 out.append(sep)
810 else:
811
812 if part and part[0] == part[-1] and part[0] in '\'"':
813 part = self._escapeSTRINGtype(part)
814 out.append(part)
815 return (u''.join(out)).strip()
816