1
2
3 """serializer classes for CSS classes
4
5 """
6 __all__ = ['CSSSerializer']
7 __docformat__ = 'restructuredtext'
8 __author__ = '$LastChangedBy: cthedot $'
9 __date__ = '$LastChangedDate: 2007-11-25 19:09:44 +0100 (So, 25 Nov 2007) $'
10 __version__ = '$LastChangedRevision: 699 $'
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 6 digit hex code point (always
21 6 digits to make it easier not to have check if no hexdigit char is
22 following).
23 E.g. the german umlaut 'ä' is escaped as \0000E4
24 """
25 s = e.args[1][e.start:e.end]
26 return u''.join([ur'\%s' % str(hex(ord(x)))[2:]
27 .zfill(6).upper() for x in s]), e.end
28
29 codecs.register_error('escapecss', _escapecss)
30
31
33 """
34 controls output of CSSSerializer
35
36 defaultAtKeyword = True
37 Should the literal @keyword from src CSS be used or the default
38 form, e.g. if ``True``: ``@import`` else: ``@i\mport``
39 defaultPropertyName = True
40 Should the normalized propertyname be used or the one given in
41 the src file, e.g. if ``True``: ``color`` else: ``c\olor``
42
43 Only used if ``keepAllProperties==False``.
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 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 keepComments = True
54 If ``False`` removes all CSSComments
55 keepEmptyRules = False
56 defines if empty rules like e.g. ``a {}`` are kept in the resulting
57 serialized sheet
58 lineNumbers = False
59 Only used if a complete CSSStyleSheet is serialized.
60 lineSeparator = u'\\n'
61 How to end a line. This may be set to e.g. u'' for serializing of
62 CSSStyleDeclarations usable in HTML style attribute.
63 listItemSpacer = u' '
64 string which is used in ``css.SelectorList``, ``css.CSSValue`` and
65 ``stylesheets.MediaList`` after the comma
66 omitLastSemicolon = True
67 If ``True`` omits ; after last property of CSSStyleDeclaration
68 paranthesisSpacer = u' '
69 string which is used before an opening paranthesis like in a
70 ``css.CSSMediaRule`` or ``css.CSSStyleRule``
71 propertyNameSpacer = u' '
72 string which is used after a Property name colon
73
74 validOnly = False (**not anywhere used yet**)
75 if True only valid (Properties or Rules) are kept
76
77 A Property is valid if it is a known Property with a valid value.
78 Currently CSS 2.1 values as defined in cssproperties.py would be
79 valid.
80
81 wellformedOnly = True (**not anywhere used yet**)
82 only wellformed properties and rules are kept
83
84 **DEPRECATED**: removeInvalid = True
85 Omits invalid rules, replaced by ``validOnly`` which will be used
86 more cases
87
88 """
89 - def __init__(self, indent=None, lineSeparator=None):
90 """
91 Always use named instead of positional parameters
92 """
93 self.useDefaults()
94 if indent:
95 self.indent = indent
96 if lineSeparator:
97 self.lineSeparator = lineSeparator
98
100 "reset all preference options to the default value"
101 self.defaultAtKeyword = True
102 self.defaultPropertyName = True
103 self.importHrefFormat = None
104 self.indent = 4 * u' '
105 self.keepAllProperties = True
106 self.keepComments = True
107 self.keepEmptyRules = False
108 self.lineNumbers = False
109 self.lineSeparator = u'\n'
110 self.listItemSpacer = u' '
111 self.omitLastSemicolon = True
112 self.paranthesisSpacer = u' '
113 self.propertyNameSpacer = u' '
114 self.validOnly = False
115 self.wellformedOnly = True
116
117 self.removeInvalid = True
118
120 """
121 sets options to achive a minified stylesheet
122
123 you may want to set preferences with this convinience method
124 and set settings you want adjusted afterwards
125 """
126 self.importHrefFormat = 'string'
127 self.indent = u''
128 self.keepComments = False
129 self.keepEmptyRules = False
130 self.lineNumbers = False
131 self.lineSeparator = u''
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
141 """
142 Methods to serialize a CSSStylesheet and its parts
143
144 To use your own serializing method the easiest is to subclass CSS
145 Serializer and overwrite the methods you like to customize.
146 """
147 __notinurimatcher = re.compile(ur'''.*?[\)\s\;]''', re.U).match
148
149
151 """
152 prefs
153 instance of Preferences
154 """
155 if not prefs:
156 prefs = Preferences()
157 self.prefs = prefs
158 self._level = 0
159
161 if self.prefs.lineNumbers:
162 pad = len(str(text.count(self.prefs.lineSeparator)+1))
163 out = []
164 for i, line in enumerate(text.split(self.prefs.lineSeparator)):
165 out.append((u'%*i: %s') % (pad, i+1, line))
166 text = self.prefs.lineSeparator.join(out)
167 return text
168
170
171 if self.prefs.removeInvalid and \
172 hasattr(x, 'valid') and not x.valid:
173 return True
174 else:
175 return False
176
178 """
179 checks if valid items only and if yes it item is valid
180 """
181 return not self.prefs.validOnly or (self.prefs.validOnly and
182 hasattr(x, 'valid') and
183 x.valid)
184
191
193 """
194 escapes delim charaters in string s with \delim
195 """
196 return s.replace(delim, u'\\%s' % delim)
197
199 """
200 used by all @rules to get the keyword used
201 dependent on prefs setting defaultAtKeyword
202 """
203 if self.prefs.defaultAtKeyword:
204 return default
205 else:
206 return rule.atkeyword
207
209 """
210 used by all styledeclarations to get the propertyname used
211 dependent on prefs setting defaultPropertyName
212 """
213 if self.prefs.defaultPropertyName and \
214 not self.prefs.keepAllProperties:
215 return property.normalname
216 else:
217 return actual
218
220 """
221 indent a block like a CSSStyleDeclaration to the given level
222 which may be higher than self._level (e.g. for CSSStyleDeclaration)
223 """
224 if not self.prefs.lineSeparator:
225 return text
226 return self.prefs.lineSeparator.join(
227 [u'%s%s' % (level * self.prefs.indent, line)
228 for line in text.split(self.prefs.lineSeparator)]
229 )
230
231 - def _uri(self, uri):
239
257
271
287
296
298 """
299 serializes CSSCharsetRule
300 encoding: string
301
302 always @charset "encoding";
303 no comments or other things allowed!
304 """
305 if not rule.encoding or self._noinvalids(rule):
306 return u''
307 return u'@charset "%s";' % self._escapestring(rule.encoding)
308
310 """
311 serializes CSSFontFaceRule
312
313 style
314 CSSStyleDeclaration
315
316 + CSSComments
317 """
318 self._level += 1
319 try:
320 styleText = self.do_css_CSSStyleDeclaration(rule.style)
321 finally:
322 self._level -= 1
323
324 if not styleText or self._noinvalids(rule):
325 return u''
326
327 before = []
328 for x in rule.seq:
329 if hasattr(x, 'cssText'):
330 before.append(x.cssText)
331 else:
332
333 raise SyntaxErr('serializing CSSFontFaceRule: unexpected %r' % x)
334 if before:
335 before = u' '.join(before).strip()
336 if before:
337 before = u' %s' % before
338 else:
339 before = u''
340
341 return u'%s%s {%s%s%s%s}' % (
342 self._getatkeyword(rule, u'@font-face'),
343 before,
344 self.prefs.lineSeparator,
345 self._indentblock(styleText, self._level + 1),
346 self.prefs.lineSeparator,
347 (self._level + 1) * self.prefs.indent
348 )
349
351 """
352 serializes CSSImportRule
353
354 href
355 string
356 hreftype
357 'uri' or 'string'
358 media
359 cssutils.stylesheets.medialist.MediaList
360
361 + CSSComments
362 """
363 if not rule.href or self._noinvalids(rule):
364 return u''
365 out = [u'%s ' % self._getatkeyword(rule, u'@import')]
366 for part in rule.seq:
367 if rule.href == part:
368 if self.prefs.importHrefFormat == 'uri':
369 out.append(u'url(%s)' % self._uri(part))
370 elif self.prefs.importHrefFormat == 'string' or \
371 rule.hreftype == 'string':
372 out.append(u'"%s"' % self._escapestring(part))
373 else:
374 out.append(u'url(%s)' % self._uri(part))
375 elif isinstance(
376 part, cssutils.stylesheets.medialist.MediaList):
377 mediaText = self.do_stylesheets_medialist(part)
378 if mediaText and not mediaText == u'all':
379 out.append(u' %s' % mediaText)
380 elif hasattr(part, 'cssText'):
381 out.append(part.cssText)
382 return u'%s;' % u''.join(out)
383
385 """
386 serializes CSSNamespaceRule
387
388 uri
389 string
390 prefix
391 string
392
393 + CSSComments
394 """
395 if not rule.namespaceURI or self._noinvalids(rule):
396 return u''
397
398 out = [u'%s' % self._getatkeyword(rule, u'@namespace')]
399 for part in rule.seq:
400 if rule.prefix == part and part != u'':
401 out.append(u' %s' % part)
402 elif rule.namespaceURI == part:
403 out.append(u' "%s"' % self._escapestring(part))
404 elif hasattr(part, 'cssText'):
405 out.append(part.cssText)
406 return u'%s;' % u''.join(out)
407
437
439 """
440 serializes CSSPageRule
441
442 selectorText
443 string
444 style
445 CSSStyleDeclaration
446
447 + CSSComments
448 """
449 self._level += 1
450 try:
451 styleText = self.do_css_CSSStyleDeclaration(rule.style)
452 finally:
453 self._level -= 1
454
455 if not styleText or self._noinvalids(rule):
456 return u''
457
458 return u'%s%s {%s%s%s%s}' % (
459 self._getatkeyword(rule, u'@page'),
460 self.do_pageselector(rule.seq),
461 self.prefs.lineSeparator,
462 self._indentblock(styleText, self._level + 1),
463 self.prefs.lineSeparator,
464 (self._level + 1) * self.prefs.indent
465 )
466
467 - def do_pageselector(self, seq):
468 """
469 a selector of a CSSPageRule including comments
470 """
471 if len(seq) == 0 or self._noinvalids(seq):
472 return u''
473 else:
474 out = []
475 for part in seq:
476 if hasattr(part, 'cssText'):
477 out.append(part.cssText)
478 else:
479 out.append(part)
480 return u' %s' % u''.join(out)
481
483 """
484 serializes CSSUnknownRule
485 anything until ";" or "{...}"
486 + CSSComments
487 """
488 if rule.atkeyword and not self._noinvalids(rule):
489 out = [u'%s' % rule.atkeyword]
490 for part in rule.seq:
491 if isinstance(part, cssutils.css.csscomment.CSSComment):
492 if self.prefs.keepComments:
493 out.append(part.cssText)
494 else:
495 out.append(part)
496 if not (out[-1].endswith(u';') or out[-1].endswith(u'}')):
497 out.append(u';')
498 return u''.join(out)
499 else:
500 return u''
501
503 """
504 serializes CSSStyleRule
505
506 selectorList
507 style
508
509 + CSSComments
510 """
511 selectorText = self.do_css_SelectorList(rule.selectorList)
512 if not selectorText or self._noinvalids(rule):
513 return u''
514 self._level += 1
515 styleText = u''
516 try:
517 styleText = self.do_css_CSSStyleDeclaration(rule.style)
518 finally:
519 self._level -= 1
520 if not styleText:
521 if self.prefs.keepEmptyRules:
522 return u'%s%s{}' % (selectorText,
523 self.prefs.paranthesisSpacer)
524 else:
525 return u'%s%s{%s%s%s%s}' % (
526 selectorText,
527 self.prefs.paranthesisSpacer,
528 self.prefs.lineSeparator,
529 self._indentblock(styleText, self._level + 1),
530 self.prefs.lineSeparator,
531 (self._level + 1) * self.prefs.indent)
532
552
554 """
555 a single selector including comments
556
557 TODO: style combinators like + >
558 """
559 if len(selector.seq) == 0 or self._noinvalids(selector):
560 return u''
561 else:
562 out = []
563 for part in selector.seq:
564 if hasattr(part, 'cssText'):
565 out.append(part.cssText)
566 else:
567 if type(part) == dict:
568 out.append(part['value'])
569 else:
570 out.append(part)
571 return u''.join(out)
572
574 """
575 Style declaration of CSSStyleRule
576 """
577 if len(style.seq) > 0 and self._wellformed(style) and\
578 self._valid(style):
579 if separator is None:
580 separator = self.prefs.lineSeparator
581
582 if self.prefs.keepAllProperties:
583 parts = style.seq
584 else:
585
586 nnames = set()
587 for x in style.seq:
588 if isinstance(x, cssutils.css.Property):
589 nnames.add(x.normalname)
590
591 parts = []
592 for x in reversed(style.seq):
593 if isinstance(x, cssutils.css.Property):
594 if x.normalname in nnames:
595 parts.append(x)
596 nnames.remove(x.normalname)
597 else:
598 parts.append(x)
599 parts.reverse()
600
601 out = []
602 for (i, part) in enumerate(parts):
603 if isinstance(part, cssutils.css.CSSComment):
604
605 if self.prefs.keepComments:
606 out.append(part.cssText)
607 out.append(separator)
608 elif isinstance(part, cssutils.css.Property):
609
610 out.append(self.do_Property(part))
611 if not (self.prefs.omitLastSemicolon and i==len(parts)-1):
612 out.append(u';')
613 out.append(separator)
614 else:
615
616 out.append(part)
617
618 if out and out[-1] == separator:
619 del out[-1]
620
621 return u''.join(out)
622
623 else:
624 return u''
625
666
668 """
669 a Properties priority "!" S* "important"
670 """
671 out = []
672 for part in priorityseq:
673 if hasattr(part, 'cssText'):
674 out.append(u' ')
675 out.append(part.cssText)
676 out.append(u' ')
677 else:
678 out.append(part)
679 return u''.join(out).strip()
680
682 """
683 serializes a CSSValue
684 """
685 if not cssvalue:
686 return u''
687 else:
688 sep = u',%s' % self.prefs.listItemSpacer
689 out = []
690 for part in cssvalue.seq:
691 if hasattr(part, 'cssText'):
692
693 out.append(part.cssText)
694 elif isinstance(part, basestring) and part == u',':
695 out.append(sep)
696 else:
697 out.append(part)
698 return (u''.join(out)).strip()
699