1
2
3 """serializer classes for CSS classes
4
5 """
6 __all__ = ['CSSSerializer']
7 __docformat__ = 'restructuredtext'
8 __author__ = '$LastChangedBy: cthedot $'
9 __date__ = '$LastChangedDate: 2007-12-29 17:13:39 +0100 (Sa, 29 Dez 2007) $'
10 __version__ = '$LastChangedRevision: 760 $'
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
49 keepAllProperties = True
50 If ``True`` all properties set in the original CSSStylesheet
51 are kept meaning even properties set twice with the exact same
52 same name are kept!
53
54 defaultAtKeyword=%r,
55 defaultPropertyName=%r,
56 importHrefFormat=%r,
57 indent=%r,
58 keepAllProperties=%r,
59 keepComments=%r,
60 keepEmptyRules=%r,
61 lineNumbers=%r,
62 lineSeparator=%r,
63 listItemSpacer=%r,
64 omitLastSemicolon=%r,
65 paranthesisSpacer=%r,
66 propertyNameSpacer=%r,
67 validOnly=%r,
68 wellformedOnly=%r,
69
70
71 keepComments = True
72 If ``False`` removes all CSSComments
73 keepEmptyRules = False
74 defines if empty rules like e.g. ``a {}`` are kept in the resulting
75 serialized sheet
76
77 lineNumbers = False
78 Only used if a complete CSSStyleSheet is serialized.
79 lineSeparator = u'\\n'
80 How to end a line. This may be set to e.g. u'' for serializing of
81 CSSStyleDeclarations usable in HTML style attribute.
82 listItemSpacer = u' '
83 string which is used in ``css.SelectorList``, ``css.CSSValue`` and
84 ``stylesheets.MediaList`` after the comma
85 omitLastSemicolon = True
86 If ``True`` omits ; after last property of CSSStyleDeclaration
87 paranthesisSpacer = u' '
88 string which is used before an opening paranthesis like in a
89 ``css.CSSMediaRule`` or ``css.CSSStyleRule``
90 propertyNameSpacer = u' '
91 string which is used after a Property name colon
92
93 validOnly = False (**not anywhere used yet**)
94 if True only valid (Properties or Rules) are kept
95
96 A Property is valid if it is a known Property with a valid value.
97 Currently CSS 2.1 values as defined in cssproperties.py would be
98 valid.
99
100 wellformedOnly = True (**not anywhere used yet**)
101 only wellformed properties and rules are kept
102
103 **DEPRECATED**: removeInvalid = True
104 Omits invalid rules, replaced by ``validOnly`` which will be used
105 more cases
106
107 """
117
119 "reset all preference options to the default value"
120 self.defaultAtKeyword = True
121 self.defaultPropertyName = True
122 self.importHrefFormat = None
123 self.indent = 4 * u' '
124 self.keepAllProperties = True
125 self.keepComments = True
126 self.keepEmptyRules = False
127 self.lineNumbers = False
128 self.lineSeparator = u'\n'
129 self.listItemSpacer = u' '
130 self.omitLastSemicolon = True
131 self.paranthesisSpacer = u' '
132 self.propertyNameSpacer = u' '
133 self.validOnly = False
134 self.wellformedOnly = True
135
136 self.removeInvalid = True
137
139 """
140 sets options to achive a minified stylesheet
141
142 you may want to set preferences with this convinience method
143 and set settings you want adjusted afterwards
144 """
145 self.importHrefFormat = 'string'
146 self.indent = u''
147 self.keepComments = False
148 self.keepEmptyRules = False
149 self.lineNumbers = False
150 self.lineSeparator = u''
151 self.listItemSpacer = u''
152 self.omitLastSemicolon = True
153 self.paranthesisSpacer = u''
154 self.propertyNameSpacer = u''
155 self.validOnly = False
156 self.wellformedOnly = True
157
159 return u"cssutils.css.%s(%s)" % (self.__class__.__name__,
160 u', '.join(['\n %s=%r' % (p, self.__getattribute__(p)) for p in self.__dict__]
161 ))
162
164 return u"<cssutils.css.%s object %s at 0x%x" % (self.__class__.__name__,
165 u' '.join(['%s=%r' % (p, self.__getattribute__(p)) for p in self.__dict__]
166 ),
167 id(self))
168
169
171 """
172 Methods to serialize a CSSStylesheet and its parts
173
174 To use your own serializing method the easiest is to subclass CSS
175 Serializer and overwrite the methods you like to customize.
176 """
177 __notinurimatcher = re.compile(ur'''.*?[\)\s\;]''', re.U).match
178
179
181 """
182 prefs
183 instance of Preferences
184 """
185 if not prefs:
186 prefs = Preferences()
187 self.prefs = prefs
188 self._level = 0
189
191 if self.prefs.lineNumbers:
192 pad = len(str(text.count(self.prefs.lineSeparator)+1))
193 out = []
194 for i, line in enumerate(text.split(self.prefs.lineSeparator)):
195 out.append((u'%*i: %s') % (pad, i+1, line))
196 text = self.prefs.lineSeparator.join(out)
197 return text
198
200
201 if self.prefs.removeInvalid and \
202 hasattr(x, 'valid') and not x.valid:
203 return True
204 else:
205 return False
206
208 """
209 checks if valid items only and if yes it item is valid
210 """
211 return not self.prefs.validOnly or (self.prefs.validOnly and
212 hasattr(x, 'valid') and
213 x.valid)
214
221
223 """
224 escapes delim charaters in string s with \delim
225 """
226 return s.replace(delim, u'\\%s' % delim)
227
229 """
230 used by all @rules to get the keyword used
231 dependent on prefs setting defaultAtKeyword
232 """
233 if self.prefs.defaultAtKeyword:
234 return default
235 else:
236 return rule.atkeyword
237
239 """
240 used by all styledeclarations to get the propertyname used
241 dependent on prefs setting defaultPropertyName
242 """
243 if self.prefs.defaultPropertyName and \
244 not self.prefs.keepAllProperties:
245 return property.normalname
246 else:
247 return actual
248
250 """
251 indent a block like a CSSStyleDeclaration to the given level
252 which may be higher than self._level (e.g. for CSSStyleDeclaration)
253 """
254 if not self.prefs.lineSeparator:
255 return text
256 return self.prefs.lineSeparator.join(
257 [u'%s%s' % (level * self.prefs.indent, line)
258 for line in text.split(self.prefs.lineSeparator)]
259 )
260
261 - def _uri(self, uri):
269
287
301
317
326
328 """
329 serializes CSSCharsetRule
330 encoding: string
331
332 always @charset "encoding";
333 no comments or other things allowed!
334 """
335 if not rule.encoding or self._noinvalids(rule):
336 return u''
337 return u'@charset "%s";' % self._escapestring(rule.encoding)
338
340 """
341 serializes CSSFontFaceRule
342
343 style
344 CSSStyleDeclaration
345
346 + CSSComments
347 """
348 self._level += 1
349 try:
350 styleText = self.do_css_CSSStyleDeclaration(rule.style)
351 finally:
352 self._level -= 1
353
354 if not styleText or self._noinvalids(rule):
355 return u''
356
357 before = []
358 for x in rule.seq:
359 if hasattr(x, 'cssText'):
360 before.append(x.cssText)
361 else:
362
363 raise SyntaxErr('serializing CSSFontFaceRule: unexpected %r' % x)
364 if before:
365 before = u' '.join(before).strip()
366 if before:
367 before = u' %s' % before
368 else:
369 before = u''
370
371 return u'%s%s {%s%s%s%s}' % (
372 self._getatkeyword(rule, u'@font-face'),
373 before,
374 self.prefs.lineSeparator,
375 self._indentblock(styleText, self._level + 1),
376 self.prefs.lineSeparator,
377 (self._level + 1) * self.prefs.indent
378 )
379
381 """
382 serializes CSSImportRule
383
384 href
385 string
386 hreftype
387 'uri' or 'string'
388 media
389 cssutils.stylesheets.medialist.MediaList
390
391 + CSSComments
392 """
393 if not rule.href or self._noinvalids(rule):
394 return u''
395 out = [u'%s ' % self._getatkeyword(rule, u'@import')]
396 for part in rule.seq:
397 if rule.href == part:
398 if self.prefs.importHrefFormat == 'uri':
399 out.append(u'url(%s)' % self._uri(part))
400 elif self.prefs.importHrefFormat == 'string' or \
401 rule.hreftype == 'string':
402 out.append(u'"%s"' % self._escapestring(part))
403 else:
404 out.append(u'url(%s)' % self._uri(part))
405 elif isinstance(
406 part, cssutils.stylesheets.medialist.MediaList):
407 mediaText = self.do_stylesheets_medialist(part)
408 if mediaText and not mediaText == u'all':
409 out.append(u' %s' % mediaText)
410 elif hasattr(part, 'cssText'):
411 out.append(part.cssText)
412 return u'%s;' % u''.join(out)
413
415 """
416 serializes CSSNamespaceRule
417
418 uri
419 string
420 prefix
421 string
422
423 + CSSComments
424 """
425 if not rule.namespaceURI or self._noinvalids(rule):
426 return u''
427
428 out = [u'%s' % self._getatkeyword(rule, u'@namespace')]
429 for part in rule.seq:
430 if rule.prefix == part and part != u'':
431 out.append(u' %s' % part)
432 elif rule.namespaceURI == part:
433 out.append(u' "%s"' % self._escapestring(part))
434 elif hasattr(part, 'cssText'):
435 out.append(part.cssText)
436 return u'%s;' % u''.join(out)
437
467
469 """
470 serializes CSSPageRule
471
472 selectorText
473 string
474 style
475 CSSStyleDeclaration
476
477 + CSSComments
478 """
479 self._level += 1
480 try:
481 styleText = self.do_css_CSSStyleDeclaration(rule.style)
482 finally:
483 self._level -= 1
484
485 if not styleText or self._noinvalids(rule):
486 return u''
487
488 return u'%s%s {%s%s%s%s}' % (
489 self._getatkeyword(rule, u'@page'),
490 self.do_pageselector(rule.seq),
491 self.prefs.lineSeparator,
492 self._indentblock(styleText, self._level + 1),
493 self.prefs.lineSeparator,
494 (self._level + 1) * self.prefs.indent
495 )
496
497 - def do_pageselector(self, seq):
498 """
499 a selector of a CSSPageRule including comments
500 """
501 if len(seq) == 0 or self._noinvalids(seq):
502 return u''
503 else:
504 out = []
505 for part in seq:
506 if hasattr(part, 'cssText'):
507 out.append(part.cssText)
508 else:
509 out.append(part)
510 return u' %s' % u''.join(out)
511
513 """
514 serializes CSSUnknownRule
515 anything until ";" or "{...}"
516 + CSSComments
517 """
518 if rule.atkeyword and not self._noinvalids(rule):
519 out = [u'%s' % rule.atkeyword]
520 for part in rule.seq:
521 if isinstance(part, cssutils.css.csscomment.CSSComment):
522 if self.prefs.keepComments:
523 out.append(part.cssText)
524 else:
525 out.append(part)
526 if not (out[-1].endswith(u';') or out[-1].endswith(u'}')):
527 out.append(u';')
528 return u''.join(out)
529 else:
530 return u''
531
533 """
534 serializes CSSStyleRule
535
536 selectorList
537 style
538
539 + CSSComments
540 """
541 selectorText = self.do_css_SelectorList(rule.selectorList)
542 if not selectorText or self._noinvalids(rule):
543 return u''
544 self._level += 1
545 styleText = u''
546 try:
547 styleText = self.do_css_CSSStyleDeclaration(rule.style)
548 finally:
549 self._level -= 1
550 if not styleText:
551 if self.prefs.keepEmptyRules:
552 return u'%s%s{}' % (selectorText,
553 self.prefs.paranthesisSpacer)
554 else:
555 return u'%s%s{%s%s%s%s}' % (
556 selectorText,
557 self.prefs.paranthesisSpacer,
558 self.prefs.lineSeparator,
559 self._indentblock(styleText, self._level + 1),
560 self.prefs.lineSeparator,
561 (self._level + 1) * self.prefs.indent)
562
583
584
586 """
587 a single selector including comments
588
589 TODO: style combinators like + >
590 """
591 if selector.seq and self._wellformed(selector) and\
592 self._valid(selector):
593 out = []
594 for part in selector.seq:
595 if hasattr(part, 'cssText'):
596 out.append(part.cssText)
597 else:
598 if type(part) == dict:
599 out.append(part['value'])
600 else:
601 out.append(part)
602 return u''.join(out)
603 else:
604 return u''
605
607 """
608 Style declaration of CSSStyleRule
609 """
610 if len(style.seq) > 0 and self._wellformed(style) and\
611 self._valid(style):
612 if separator is None:
613 separator = self.prefs.lineSeparator
614
615 if self.prefs.keepAllProperties:
616 parts = style.seq
617 else:
618
619 nnames = set()
620 for x in style.seq:
621 if isinstance(x, cssutils.css.Property):
622 nnames.add(x.normalname)
623
624 parts = []
625 for x in reversed(style.seq):
626 if isinstance(x, cssutils.css.Property):
627 if x.normalname in nnames:
628 parts.append(x)
629 nnames.remove(x.normalname)
630 else:
631 parts.append(x)
632 parts.reverse()
633
634 out = []
635 for (i, part) in enumerate(parts):
636 if isinstance(part, cssutils.css.CSSComment):
637
638 if self.prefs.keepComments:
639 out.append(part.cssText)
640 out.append(separator)
641 elif isinstance(part, cssutils.css.Property):
642
643 out.append(self.do_Property(part))
644 if not (self.prefs.omitLastSemicolon and i==len(parts)-1):
645 out.append(u';')
646 out.append(separator)
647 else:
648
649 out.append(part)
650
651 if out and out[-1] == separator:
652 del out[-1]
653
654 return u''.join(out)
655
656 else:
657 return u''
658
699
701 """
702 a Properties priority "!" S* "important"
703 """
704 out = []
705 for part in priorityseq:
706 if hasattr(part, 'cssText'):
707 out.append(u' ')
708 out.append(part.cssText)
709 out.append(u' ')
710 else:
711 out.append(part)
712 return u''.join(out).strip()
713
715 """
716 serializes a CSSValue
717 """
718 if not cssvalue:
719 return u''
720 else:
721 sep = u',%s' % self.prefs.listItemSpacer
722 out = []
723 for part in cssvalue.seq:
724 if hasattr(part, 'cssText'):
725
726 out.append(part.cssText)
727 elif isinstance(part, basestring) and part == u',':
728 out.append(sep)
729 else:
730 out.append(part)
731 return (u''.join(out)).strip()
732