1
2
3 """serializer classes for CSS classes
4
5 TODO:
6 - indent subrules if selector is subselector
7
8 """
9 __all__ = ['CSSSerializer']
10 __docformat__ = 'restructuredtext'
11 __author__ = '$LastChangedBy: cthedot $'
12 __date__ = '$LastChangedDate: 2007-11-03 22:43:55 +0100 (Sa, 03 Nov 2007) $'
13 __version__ = '$LastChangedRevision: 633 $'
14
15 import codecs
16 import re
17 import cssutils
18 import util
19
21 """
22 Escapes characters not allowed in the current encoding the CSS way
23 with a backslash followed by a uppercase 6 digit hex code point (always
24 6 digits to make it easier not to have check if no hexdigit char is
25 following).
26 E.g. the german umlaut 'ä' is escaped as \0000E4
27 """
28 s = e.args[1][e.start:e.end]
29 return u''.join([ur'\%s' % str(hex(ord(x)))[2:]
30 .zfill(6).upper() for x in s]), e.end
31
32 codecs.register_error('escapecss', _escapecss)
33
34
36 """
37 controls output of CSSSerializer
38
39 defaultAtKeyword = True
40 Should the literal @keyword from src CSS be used or the default
41 form, e.g. if ``True``: ``@import`` else: ``@i\mport``
42 defaultPropertyName = True
43 Should the normalized propertyname be used or the one given in
44 the src file, e.g. if ``True``: ``color`` else: ``c\olor``
45
46 Only used if ``keepAllProperties==False``.
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 keepAllProperties = True
53 If ``True`` all properties set in the original CSSStylesheet
54 are kept meaning even properties set twice with the exact same
55 same name are kept!
56 keepComments = True
57 If ``False`` removes all CSSComments
58 keepEmptyRules = False
59 defines if empty rules like e.g. ``a {}`` are kept in the resulting
60 serialized sheet
61 lineNumbers = False
62 Only used if a complete CSSStyleSheet is serialized.
63 lineSeparator = u'\\n'
64 How to end a line. This may be set to e.g. u'' for serializing of
65 CSSStyleDeclarations usable in HTML style attribute.
66 listItemSpacer = u' '
67 string which is used in ``css.SelectorList``, ``css.CSSValue`` and
68 ``stylesheets.MediaList`` after the comma
69 omitLastSemicolon = True
70 If ``True`` omits ; after last property of CSSStyleDeclaration
71 paranthesisSpacer = u' '
72 string which is used before an opening paranthesis like in a
73 ``css.CSSMediaRule`` or ``css.CSSStyleRule``
74 propertyNameSpacer = u' '
75 string which is used after a Property name colon
76
77 validOnly = False (**not anywhere used yet**)
78 if True only valid (Properties or Rules) are kept
79
80 A Property is valid if it is a known Property with a valid value.
81 Currently CSS 2.1 values as defined in cssproperties.py would be
82 valid.
83
84 wellformedOnly = True (**not anywhere used yet**)
85 only wellformed properties and rules are kept
86
87 **DEPRECATED**: removeInvalid = True
88 Omits invalid rules, replaced by ``validOnly`` which will be used
89 more cases
90
91 """
92 - def __init__(self, indent=None, lineSeparator=None):
93 """
94 Always use named instead of positional parameters
95 """
96 self.useDefaults()
97 if indent:
98 self.indent = indent
99 if lineSeparator:
100 self.lineSeparator = lineSeparator
101
103 "reset all preference options to a default value"
104 self.defaultAtKeyword = True
105 self.defaultPropertyName = True
106 self.importHrefFormat = None
107 self.indent = 4 * u' '
108 self.keepAllProperties = True
109 self.keepComments = True
110 self.keepEmptyRules = False
111 self.lineNumbers = False
112 self.lineSeparator = u'\n'
113 self.listItemSpacer = u' '
114 self.omitLastSemicolon = True
115 self.paranthesisSpacer = u' '
116 self.propertyNameSpacer = u' '
117 self.validOnly = False
118 self.wellformedOnly = True
119
120 self.removeInvalid = True
121
123 """
124 sets options to achive a minified stylesheet
125
126 you may want to set preferences with this convinience method
127 and set settings you want adjusted afterwards
128 """
129 self.importHrefFormat = 'string'
130 self.indent = u''
131 self.keepComments = False
132 self.keepEmptyRules = False
133 self.lineNumbers = False
134 self.lineSeparator = u''
135 self.listItemSpacer = u''
136 self.omitLastSemicolon = True
137 self.paranthesisSpacer = u''
138 self.propertyNameSpacer = u''
139 self.validOnly = False
140 self.wellformedOnly = True
141
142
144 """
145 Methods to serialize a CSSStylesheet and its parts
146
147 To use your own serializing method the easiest is to subclass CSS
148 Serializer and overwrite the methods you like to customize.
149 """
150 __notinurimatcher = re.compile(ur'''.*?[\)\s\;]''', re.U).match
151
152
154 """
155 prefs
156 instance of Preferences
157 """
158 if not prefs:
159 prefs = Preferences()
160 self.prefs = prefs
161 self._level = 0
162
164 if self.prefs.lineNumbers:
165 pad = len(str(text.count(self.prefs.lineSeparator)+1))
166 out = []
167 for i, line in enumerate(text.split(self.prefs.lineSeparator)):
168 out.append((u'%*i: %s') % (pad, i+1, line))
169 text = self.prefs.lineSeparator.join(out)
170 return text
171
173
174 if self.prefs.removeInvalid and \
175 hasattr(x, 'valid') and not x.valid:
176 return True
177 else:
178 return False
179
181 """
182 checks if valid items only and if yes it item is valid
183 """
184 return not self.prefs.validOnly or (self.prefs.validOnly and
185 hasattr(x, 'valid') and
186 x.valid)
187
194
196 """
197 escapes delim charaters in string s with \delim
198 """
199 return s.replace(delim, u'\\%s' % delim)
200
202 """
203 used by all @rules to get the keyword used
204 dependent on prefs setting defaultAtKeyword
205 """
206 if self.prefs.defaultAtKeyword:
207 return default
208 else:
209 return rule.atkeyword
210
212 """
213 used by all styledeclarations to get the propertyname used
214 dependent on prefs setting defaultPropertyName
215 """
216 if self.prefs.defaultPropertyName and \
217 not self.prefs.keepAllProperties:
218 return property.normalname
219 else:
220 return actual
221
223 """
224 indent a block like a CSSStyleDeclaration to the given level
225 which may be higher than self._level (e.g. for CSSStyleDeclaration)
226 """
227 if not self.prefs.lineSeparator:
228 return text
229 return self.prefs.lineSeparator.join(
230 [u'%s%s' % (level * self.prefs.indent, line)
231 for line in text.split(self.prefs.lineSeparator)]
232 )
233
234 - def _uri(self, uri):
242
260
274
290
299
301 """
302 serializes CSSCharsetRule
303 encoding: string
304
305 always @charset "encoding";
306 no comments or other things allowed!
307 """
308 if not rule.encoding or self._noinvalids(rule):
309 return u''
310 return u'@charset "%s";' % self._escapestring(rule.encoding)
311
313 """
314 serializes CSSImportRule
315
316 href
317 string
318 hreftype
319 'uri' or 'string'
320 media
321 cssutils.stylesheets.medialist.MediaList
322
323 + CSSComments
324 """
325 if not rule.href or self._noinvalids(rule):
326 return u''
327 out = [u'%s ' % self._getatkeyword(rule, u'@import')]
328 for part in rule.seq:
329 if rule.href == part:
330 if self.prefs.importHrefFormat == 'uri':
331 out.append(u'url(%s)' % self._uri(part))
332 elif self.prefs.importHrefFormat == 'string' or \
333 rule.hreftype == 'string':
334 out.append(u'"%s"' % self._escapestring(part))
335 else:
336 out.append(u'url(%s)' % self._uri(part))
337 elif isinstance(
338 part, cssutils.stylesheets.medialist.MediaList):
339 mediaText = self.do_stylesheets_medialist(part).strip()
340 if mediaText and not mediaText == u'all':
341 out.append(u' %s' % mediaText)
342 elif hasattr(part, 'cssText'):
343 out.append(part.cssText)
344 return u'%s;' % u''.join(out)
345
347 """
348 serializes CSSNamespaceRule
349
350 uri
351 string
352 prefix
353 string
354
355 + CSSComments
356 """
357 if not rule.namespaceURI or self._noinvalids(rule):
358 return u''
359
360 out = [u'%s' % self._getatkeyword(rule, u'@namespace')]
361 for part in rule.seq:
362 if rule.prefix == part and part != u'':
363 out.append(u' %s' % part)
364 elif rule.namespaceURI == part:
365 out.append(u' "%s"' % self._escapestring(part))
366 elif hasattr(part, 'cssText'):
367 out.append(part.cssText)
368 return u'%s;' % u''.join(out)
369
399
401 """
402 serializes CSSPageRule
403
404 selectorText
405 string
406 style
407 CSSStyleDeclaration
408
409 + CSSComments
410 """
411 self._level += 1
412 try:
413 styleText = self.do_css_CSSStyleDeclaration(rule.style)
414 finally:
415 self._level -= 1
416
417 if not styleText or self._noinvalids(rule):
418 return u''
419
420 return u'%s%s {%s%s%s%s}' % (
421 self._getatkeyword(rule, u'@page'),
422 self.do_pageselector(rule.seq),
423 self.prefs.lineSeparator,
424 self._indentblock(styleText, self._level + 1),
425 self.prefs.lineSeparator,
426 (self._level + 1) * self.prefs.indent
427 )
428
429 - def do_pageselector(self, seq):
430 """
431 a selector of a CSSPageRule including comments
432 """
433 if len(seq) == 0 or self._noinvalids(seq):
434 return u''
435 else:
436 out = []
437 for part in seq:
438 if hasattr(part, 'cssText'):
439 out.append(part.cssText)
440 else:
441 out.append(part)
442 return u' %s' % u''.join(out)
443
445 """
446 serializes CSSUnknownRule
447 anything until ";" or "{...}"
448 + CSSComments
449 """
450 if rule.atkeyword and not self._noinvalids(rule):
451 out = [u'%s' % rule.atkeyword]
452 for part in rule.seq:
453 if isinstance(part, cssutils.css.csscomment.CSSComment):
454 if self.prefs.keepComments:
455 out.append(part.cssText)
456 else:
457 out.append(part)
458 if not (out[-1].endswith(u';') or out[-1].endswith(u'}')):
459 out.append(u';')
460 return u''.join(out)
461 else:
462 return u''
463
465 """
466 serializes CSSStyleRule
467
468 selectorList
469 style
470
471 + CSSComments
472 """
473 selectorText = self.do_css_SelectorList(rule.selectorList)
474 if not selectorText or self._noinvalids(rule):
475 return u''
476 self._level += 1
477 styleText = u''
478 try:
479 styleText = self.do_css_CSSStyleDeclaration(rule.style)
480 finally:
481 self._level -= 1
482 if not styleText:
483 if self.prefs.keepEmptyRules:
484 return u'%s%s{}' % (selectorText,
485 self.prefs.paranthesisSpacer)
486 else:
487 return u'%s%s{%s%s%s%s}' % (
488 selectorText,
489 self.prefs.paranthesisSpacer,
490 self.prefs.lineSeparator,
491 self._indentblock(styleText, self._level + 1),
492 self.prefs.lineSeparator,
493 (self._level + 1) * self.prefs.indent)
494
514
516 """
517 a single selector including comments
518
519 TODO: style combinators like + >
520 """
521 if len(selector.seq) == 0 or self._noinvalids(selector):
522 return u''
523 else:
524 out = []
525 for part in selector.seq:
526 if hasattr(part, 'cssText'):
527 out.append(part.cssText)
528 else:
529 if type(part) == dict:
530 out.append(part['value'])
531 else:
532 out.append(part)
533 return u''.join(out)
534
536 """
537 Style declaration of CSSStyleRule
538 """
539 if len(style.seq) > 0 and self._wellformed(style) and\
540 self._valid(style):
541 if separator is None:
542 separator = self.prefs.lineSeparator
543
544 if self.prefs.keepAllProperties:
545 parts = style.seq
546 else:
547
548 nnames = set()
549 for x in style.seq:
550 if isinstance(x, cssutils.css.Property):
551 nnames.add(x.normalname)
552
553 parts = []
554 for x in reversed(style.seq):
555 if isinstance(x, cssutils.css.Property):
556 if x.normalname in nnames:
557 parts.append(x)
558 nnames.remove(x.normalname)
559 else:
560 parts.append(x)
561 parts.reverse()
562
563 out = []
564 for (i, part) in enumerate(parts):
565 if isinstance(part, cssutils.css.CSSComment):
566
567 if self.prefs.keepComments:
568 out.append(part.cssText)
569 out.append(separator)
570 elif isinstance(part, cssutils.css.Property):
571
572 out.append(self.do_Property(part))
573 if not (self.prefs.omitLastSemicolon and i==len(parts)-1):
574 out.append(u';')
575 out.append(separator)
576 else:
577
578 out.append(part)
579
580 if out and out[-1] == separator:
581 del out[-1]
582
583 return u''.join(out)
584
585 else:
586 return u''
587
628
630 """
631 a Properties priority "!" S* "important"
632 """
633 out = []
634 for part in priorityseq:
635 if hasattr(part, 'cssText'):
636 out.append(u' ')
637 out.append(part.cssText)
638 out.append(u' ')
639 else:
640 out.append(part)
641 return u''.join(out).strip()
642
644 """
645 serializes a CSSValue
646 """
647 if not cssvalue:
648 return u''
649 else:
650 sep = u',%s' % self.prefs.listItemSpacer
651 out = []
652 for part in cssvalue.seq:
653 if hasattr(part, 'cssText'):
654
655 out.append(part.cssText)
656 elif isinstance(part, basestring) and part == u',':
657 out.append(sep)
658 else:
659 out.append(part)
660 return (u''.join(out)).strip()
661