1
2 """serializer classes for CSS classes
3
4 TODO:
5 - indent subrules if selector is subselector
6
7 """
8 __all__ = ['CSSSerializer']
9 __docformat__ = 'restructuredtext'
10 __author__ = '$LastChangedBy: cthedot $'
11 __date__ = '$LastChangedDate: 2007-10-27 21:33:45 +0200 (Sa, 27 Okt 2007) $'
12 __version__ = '$LastChangedRevision: 583 $'
13
14 import re
15 import cssutils
16 import util
17
19 """
20 controls output of CSSSerializer
21
22 defaultAtKeyword = True
23 Should the literal @keyword from src CSS be used or the default
24 form, e.g. if ``True``: ``@import`` else: ``@i\mport``
25 defaultPropertyName = True
26 Should the normalized propertyname be used or the one given in
27 the src file, e.g. if ``True``: ``color`` else: ``c\olor``
28
29 Only used if ``keepAllProperties==False``.
30
31 importHrefFormat = None
32 Uses hreftype if ``None`` or explicit ``'string'`` or ``'uri'``
33 indent = 4 * ' '
34 Indentation of e.g Properties inside a CSSStyleDeclaration
35 keepAllProperties = True
36 If ``True`` all properties set in the original CSSStylesheet
37 are kept meaning even properties set twice with the exact same
38 same name are kept!
39 keepComments = True
40 If ``False`` removes all CSSComments
41 keepEmptyRules = False
42 defines if empty rules like e.g. ``a {}`` are kept in the resulting
43 serialized sheet
44 lineNumbers = False
45 Only used if a complete CSSStyleSheet is serialized.
46 lineSeparator = u'\\n'
47 How to end a line. This may be set to e.g. u'' for serializing of
48 CSSStyleDeclarations usable in HTML style attribute.
49 listItemSpacer = u' '
50 string which is used in ``css.SelectorList``, ``css.CSSValue`` and
51 ``stylesheets.MediaList`` after the comma
52 omitLastSemicolon = True
53 If ``True`` omits ; after last property of CSSStyleDeclaration
54 paranthesisSpacer = u' '
55 string which is used before an opening paranthesis like in a
56 ``css.CSSMediaRule`` or ``css.CSSStyleRule``
57 propertyNameSpacer = u' '
58 string which is used after a Property name colon
59
60 validOnly = False (**not anywhere used yet**)
61 if True only valid (Properties or Rules) are kept
62
63 A Property is valid if it is a known Property with a valid value.
64 Currently CSS 2.1 values as defined in cssproperties.py would be
65 valid.
66
67 wellformedOnly = True (**not anywhere used yet**)
68 only wellformed properties and rules are kept
69
70 **DEPRECATED**: removeInvalid = True
71 Omits invalid rules, replaced by ``validOnly`` which will be used
72 more cases
73
74 """
75 - def __init__(self, indent=None, lineSeparator=None):
76 """
77 Always use named instead of positional parameters
78 """
79 self.useDefaults()
80 if indent:
81 self.indent = indent
82 if lineSeparator:
83 self.lineSeparator = lineSeparator
84
86 "reset all preference options to a default value"
87 self.defaultAtKeyword = True
88 self.defaultPropertyName = True
89 self.importHrefFormat = None
90 self.indent = 4 * u' '
91 self.keepAllProperties = True
92 self.keepComments = True
93 self.keepEmptyRules = False
94 self.lineNumbers = False
95 self.lineSeparator = u'\n'
96 self.listItemSpacer = u' '
97 self.omitLastSemicolon = True
98 self.paranthesisSpacer = u' '
99 self.propertyNameSpacer = u' '
100 self.validOnly = False
101 self.wellformedOnly = True
102
103 self.removeInvalid = True
104
106 """
107 sets options to achive a minified stylesheet
108
109 you may want to set preferences with this convinience method
110 and set settings you want adjusted afterwards
111 """
112 self.importHrefFormat = 'string'
113 self.indent = u''
114 self.keepComments = False
115 self.keepEmptyRules = False
116 self.lineNumbers = False
117 self.lineSeparator = u''
118 self.listItemSpacer = u''
119 self.omitLastSemicolon = True
120 self.paranthesisSpacer = u''
121 self.propertyNameSpacer = u''
122 self.validOnly = False
123 self.wellformedOnly = True
124
125
127 """
128 Methods to serialize a CSSStylesheet and its parts
129
130 To use your own serializing method the easiest is to subclass CSS
131 Serializer and overwrite the methods you like to customize.
132 """
133 __notinurimatcher = re.compile(ur'''.*?[\)\s\;]''', re.U).match
134
135
137 """
138 prefs
139 instance of Preferences
140 """
141 if not prefs:
142 prefs = Preferences()
143 self.prefs = prefs
144 self._level = 0
145
147 if self.prefs.lineNumbers:
148 pad = len(str(text.count(self.prefs.lineSeparator)+1))
149 out = []
150 for i, line in enumerate(text.split(self.prefs.lineSeparator)):
151 out.append((u'%*i: %s') % (pad, i+1, line))
152 text = self.prefs.lineSeparator.join(out)
153 return text
154
156
157 if self.prefs.removeInvalid and \
158 hasattr(x, 'valid') and not x.valid:
159 return True
160 else:
161 return False
162
164 """
165 checks if valid items only and if yes it item is valid
166 """
167 return not self.prefs.validOnly or (self.prefs.validOnly and
168 hasattr(x, 'valid') and
169 x.valid)
170
177
179 """
180 escapes delim charaters in string s with \delim
181 """
182 return s.replace(delim, u'\\%s' % delim)
183
185 """
186 used by all @rules to get the keyword used
187 dependent on prefs setting defaultAtKeyword
188 """
189 if self.prefs.defaultAtKeyword:
190 return default
191 else:
192 return rule.atkeyword
193
195 """
196 used by all styledeclarations to get the propertyname used
197 dependent on prefs setting defaultPropertyName
198 """
199 if self.prefs.defaultPropertyName and \
200 not self.prefs.keepAllProperties:
201 return property.normalname
202 else:
203 return actual
204
206 """
207 indent a block like a CSSStyleDeclaration to the given level
208 which may be higher than self._level (e.g. for CSSStyleDeclaration)
209 """
210 if not self.prefs.lineSeparator:
211 return text
212 return self.prefs.lineSeparator.join(
213 [u'%s%s' % (level * self.prefs.indent, line)
214 for line in text.split(self.prefs.lineSeparator)]
215 )
216
217 - def _uri(self, uri):
225
243
257
265
274
276 """
277 serializes CSSCharsetRule
278 encoding: string
279
280 always @charset "encoding";
281 no comments or other things allowed!
282 """
283 if not rule.encoding or self._noinvalids(rule):
284 return u''
285 return u'@charset "%s";' % self._escapestring(rule.encoding)
286
288 """
289 serializes CSSImportRule
290
291 href
292 string
293 hreftype
294 'uri' or 'string'
295 media
296 cssutils.stylesheets.medialist.MediaList
297
298 + CSSComments
299 """
300 if not rule.href or self._noinvalids(rule):
301 return u''
302 out = [u'%s ' % self._getatkeyword(rule, u'@import')]
303 for part in rule.seq:
304 if rule.href == part:
305 if self.prefs.importHrefFormat == 'uri':
306 out.append(u'url(%s)' % self._uri(part))
307 elif self.prefs.importHrefFormat == 'string' or \
308 rule.hreftype == 'string':
309 out.append(u'"%s"' % self._escapestring(part))
310 else:
311 out.append(u'url(%s)' % self._uri(part))
312 elif isinstance(
313 part, cssutils.stylesheets.medialist.MediaList):
314 mediaText = self.do_stylesheets_medialist(part).strip()
315 if mediaText and not mediaText == u'all':
316 out.append(u' %s' % mediaText)
317 elif hasattr(part, 'cssText'):
318 out.append(part.cssText)
319 return u'%s;' % u''.join(out)
320
322 """
323 serializes CSSNamespaceRule
324
325 uri
326 string
327 prefix
328 string
329
330 + CSSComments
331 """
332 if not rule.namespaceURI or self._noinvalids(rule):
333 return u''
334
335 out = [u'%s' % self._getatkeyword(rule, u'@namespace')]
336 for part in rule.seq:
337 if rule.prefix == part and part != u'':
338 out.append(u' %s' % part)
339 elif rule.namespaceURI == part:
340 out.append(u' "%s"' % self._escapestring(part))
341 elif hasattr(part, 'cssText'):
342 out.append(part.cssText)
343 return u'%s;' % u''.join(out)
344
374
376 """
377 serializes CSSPageRule
378
379 selectorText
380 string
381 style
382 CSSStyleDeclaration
383
384 + CSSComments
385 """
386 self._level += 1
387 try:
388 styleText = self.do_css_CSSStyleDeclaration(rule.style)
389 finally:
390 self._level -= 1
391
392 if not styleText or self._noinvalids(rule):
393 return u''
394
395 return u'%s%s {%s%s%s%s}' % (
396 self._getatkeyword(rule, u'@page'),
397 self.do_pageselector(rule.seq),
398 self.prefs.lineSeparator,
399 self._indentblock(styleText, self._level + 1),
400 self.prefs.lineSeparator,
401 (self._level + 1) * self.prefs.indent
402 )
403
404 - def do_pageselector(self, seq):
405 """
406 a selector of a CSSPageRule including comments
407 """
408 if len(seq) == 0 or self._noinvalids(seq):
409 return u''
410 else:
411 out = []
412 for part in seq:
413 if hasattr(part, 'cssText'):
414 out.append(part.cssText)
415 else:
416 out.append(part)
417 return u' %s' % u''.join(out)
418
420 """
421 serializes CSSUnknownRule
422 anything until ";" or "{...}"
423 + CSSComments
424 """
425 if rule.atkeyword and not self._noinvalids(rule):
426 out = [u'%s' % rule.atkeyword]
427 for part in rule.seq:
428 if isinstance(part, cssutils.css.csscomment.CSSComment):
429 if self.prefs.keepComments:
430 out.append(part.cssText)
431 else:
432 out.append(part)
433 if not (out[-1].endswith(u';') or out[-1].endswith(u'}')):
434 out.append(u';')
435 return u''.join(out)
436 else:
437 return u''
438
440 """
441 serializes CSSStyleRule
442
443 selectorList
444 style
445
446 + CSSComments
447 """
448 selectorText = self.do_css_SelectorList(rule.selectorList)
449 if not selectorText or self._noinvalids(rule):
450 return u''
451 self._level += 1
452 styleText = u''
453 try:
454 styleText = self.do_css_CSSStyleDeclaration(rule.style)
455 finally:
456 self._level -= 1
457 if not styleText:
458 if self.prefs.keepEmptyRules:
459 return u'%s%s{}' % (selectorText,
460 self.prefs.paranthesisSpacer)
461 else:
462 return u'%s%s{%s%s%s%s}' % (
463 selectorText,
464 self.prefs.paranthesisSpacer,
465 self.prefs.lineSeparator,
466 self._indentblock(styleText, self._level + 1),
467 self.prefs.lineSeparator,
468 (self._level + 1) * self.prefs.indent)
469
489
491 """
492 a single selector including comments
493
494 TODO: style combinators like + >
495 """
496 if len(selector.seq) == 0 or self._noinvalids(selector):
497 return u''
498 else:
499 out = []
500 for part in selector.seq:
501 if hasattr(part, 'cssText'):
502 out.append(part.cssText)
503 else:
504 if type(part) == dict:
505 out.append(part['value'])
506 else:
507 out.append(part)
508 return u''.join(out)
509
511 """
512 Style declaration of CSSStyleRule
513 """
514 if len(style.seq) == 0 or self._noinvalids(style):
515 return u''
516 else:
517 if separator is None:
518 separator = self.prefs.lineSeparator
519
520 if self.prefs.keepAllProperties:
521 parts = style.seq
522 else:
523
524 nnames = set()
525 for x in style.seq:
526 if isinstance(x, cssutils.css.Property):
527 nnames.add(x.normalname)
528
529 parts = []
530 for x in reversed(style.seq):
531 if isinstance(x, cssutils.css.Property):
532 if x.normalname in nnames:
533 parts.append(x)
534 nnames.remove(x.normalname)
535 else:
536 parts.append(x)
537 parts.reverse()
538
539 out = []
540 for (i, part) in enumerate(parts):
541 if isinstance(part, cssutils.css.CSSComment):
542
543 if self.prefs.keepComments:
544 out.append(part.cssText)
545 out.append(separator)
546 elif isinstance(part, cssutils.css.Property):
547
548 out.append(self.do_Property(part))
549 if not (self.prefs.omitLastSemicolon and i==len(parts)-1):
550 out.append(u';')
551 out.append(separator)
552 else:
553
554 out.append(part)
555
556 if out and out[-1] == separator:
557 del out[-1]
558
559 return u''.join(out)
560
601
603 """
604 a Properties priority "!" S* "important"
605 """
606 out = []
607 for part in priorityseq:
608 if hasattr(part, 'cssText'):
609 out.append(u' ')
610 out.append(part.cssText)
611 out.append(u' ')
612 else:
613 out.append(part)
614 return u''.join(out).strip()
615
617 """
618 serializes a CSSValue
619 """
620 if not cssvalue:
621 return u''
622 else:
623 sep = u',%s' % self.prefs.listItemSpacer
624 out = []
625 for part in cssvalue.seq:
626 if hasattr(part, 'cssText'):
627
628 out.append(part.cssText)
629 elif isinstance(part, basestring) and part == u',':
630 out.append(sep)
631 else:
632 out.append(part)
633 return (u''.join(out)).strip()
634