1
2
3 """serializer classes for CSS classes
4
5 """
6 __all__ = ['CSSSerializer', 'Preferences']
7 __docformat__ = 'restructuredtext'
8 __version__ = '$Id: serialize.py 1284 2008-06-05 16:29:17Z cthedot $'
9 import codecs
10 import re
11 import cssutils
12 import util
13
15 """
16 Escapes characters not allowed in the current encoding the CSS way
17 with a backslash followed by a uppercase hex code point
18
19 E.g. the german umlaut 'ä' is escaped as \E4
20 """
21 s = e.object[e.start:e.end]
22 return u''.join([ur'\%s ' % str(hex(ord(x)))[2:]
23 .upper() for x in s]), e.end
24
25 codecs.register_error('escapecss', _escapecss)
26
27
29 """
30 controls output of CSSSerializer
31
32 defaultAtKeyword = True
33 Should the literal @keyword from src CSS be used or the default
34 form, e.g. if ``True``: ``@import`` else: ``@i\mport``
35 defaultPropertyName = True
36 Should the normalized propertyname be used or the one given in
37 the src file, e.g. if ``True``: ``color`` else: ``c\olor``
38
39 Only used if ``keepAllProperties==False``.
40
41 defaultPropertyPriority = True
42 Should the normalized or literal priority be used, e.g. '!important'
43 or u'!Im\portant'
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 indentSpecificities = False
50 Indent rules with subset of Selectors and higher Specitivity
51
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 keepUsedNamespaceRulesOnly = False
62 if True only namespace rules which are actually used are kept
63
64 lineNumbers = False
65 Only used if a complete CSSStyleSheet is serialized.
66 lineSeparator = u'\\n'
67 How to end a line. This may be set to e.g. u'' for serializing of
68 CSSStyleDeclarations usable in HTML style attribute.
69 listItemSpacer = u' '
70 string which is used in ``css.SelectorList``, ``css.CSSValue`` and
71 ``stylesheets.MediaList`` after the comma
72 omitLastSemicolon = True
73 If ``True`` omits ; after last property of CSSStyleDeclaration
74 paranthesisSpacer = u' '
75 string which is used before an opening paranthesis like in a
76 ``css.CSSMediaRule`` or ``css.CSSStyleRule``
77 propertyNameSpacer = u' '
78 string which is used after a Property name colon
79 selectorCombinatorSpacer = u' '
80 string which is used before and after a Selector combinator like +, > or ~.
81 CSSOM defines a single space for this which is also the default in cssutils.
82 spacer = u' '
83 general spacer, used e.g. by CSSUnknownRule
84
85 validOnly = False **DO NOT CHANGE YET**
86 if True only valid (currently Properties) are kept
87
88 A Property is valid if it is a known Property with a valid value.
89 Currently CSS 2.1 values as defined in cssproperties.py would be
90 valid.
91
92 """
102
104 "reset all preference options to the default value"
105 self.defaultAtKeyword = True
106 self.defaultPropertyName = True
107 self.defaultPropertyPriority = True
108 self.importHrefFormat = None
109 self.indent = 4 * u' '
110 self.indentSpecificities = False
111 self.keepAllProperties = True
112 self.keepComments = True
113 self.keepEmptyRules = False
114 self.keepUsedNamespaceRulesOnly = False
115 self.lineNumbers = False
116 self.lineSeparator = u'\n'
117 self.listItemSpacer = u' '
118 self.omitLastSemicolon = True
119 self.paranthesisSpacer = u' '
120 self.propertyNameSpacer = u' '
121 self.selectorCombinatorSpacer = u' '
122 self.spacer = u' '
123 self.validOnly = False
124
126 """
127 sets options to achive a minified stylesheet
128
129 you may want to set preferences with this convenience method
130 and set settings you want adjusted afterwards
131 """
132 self.importHrefFormat = 'string'
133 self.indent = u''
134 self.keepComments = False
135 self.keepEmptyRules = False
136 self.keepUsedNamespaceRulesOnly = True
137 self.lineNumbers = False
138 self.lineSeparator = u''
139 self.listItemSpacer = u''
140 self.omitLastSemicolon = True
141 self.paranthesisSpacer = u''
142 self.propertyNameSpacer = u''
143 self.selectorCombinatorSpacer = u''
144 self.spacer = u''
145 self.validOnly = False
146
148 return u"cssutils.css.%s(%s)" % (self.__class__.__name__,
149 u', '.join(['\n %s=%r' % (p, self.__getattribute__(p)) for p in self.__dict__]
150 ))
151
153 return u"<cssutils.css.%s object %s at 0x%x" % (self.__class__.__name__,
154 u' '.join(['%s=%r' % (p, self.__getattribute__(p)) for p in self.__dict__]
155 ),
156 id(self))
157
158
160 """
161 a simple class which makes appended items available as a combined string
162 """
164 self.ser = ser
165 self.out = []
166
168 if self.out and not self.out[-1].strip():
169
170 del self.out[-1]
171
172 - def append(self, val, typ=None, space=True, keepS=False, indent=False,
173 lineSeparator=False):
174 """
175 Appends val. Adds a single S after each token except as follows:
176
177 - typ COMMENT
178 uses cssText depending on self.ser.prefs.keepComments
179 - typ "Property", cssutils.css.CSSRule.UNKNOWN_RULE
180 uses cssText
181 - typ STRING
182 escapes ser._string
183 - typ S
184 ignored except ``keepS=True``
185 - typ URI
186 calls ser_uri
187 - val {
188 adds \n after
189 - val ;
190 removes S before and adds \n after
191 - val , :
192 removes S before
193 - val + > ~
194 encloses in prefs.selectorCombinatorSpacer
195 - some other vals
196 add *spacer except ``space=False``
197 """
198 if val or 'STRING' == typ:
199
200 if 'COMMENT' == typ:
201 if self.ser.prefs.keepComments:
202 val = val.cssText
203 else:
204 return
205 elif typ in ('Property', cssutils.css.CSSRule.UNKNOWN_RULE):
206 val = val.cssText
207 elif 'S' == typ and not keepS:
208 return
209 elif 'STRING' == typ:
210
211 if val is None:
212 return
213 val = self.ser._string(val)
214 elif 'URI' == typ:
215 val = self.ser._uri(val)
216 elif val in u'+>~,:{;)]':
217 self._remove_last_if_S()
218
219
220 if indent:
221 self.out.append(self.ser._indentblock(val, self.ser._level+1))
222 else:
223 self.out.append(val)
224
225 if lineSeparator:
226
227 pass
228 elif val in u'+>~':
229 self.out.insert(-1, self.ser.prefs.selectorCombinatorSpacer)
230 self.out.append(self.ser.prefs.selectorCombinatorSpacer)
231 elif u',' == val:
232 self.out.append(self.ser.prefs.listItemSpacer)
233 elif u':' == val:
234 self.out.append(self.ser.prefs.propertyNameSpacer)
235 elif u'{' == val:
236 self.out.insert(-1, self.ser.prefs.paranthesisSpacer)
237 self.out.append(self.ser.prefs.lineSeparator)
238 elif u';' == val:
239 self.out.append(self.ser.prefs.lineSeparator)
240 elif val not in u'}[]()' and space:
241 self.out.append(self.ser.prefs.spacer)
242
243 - def value(self, delim=u'', end=None):
244 "returns all items joined by delim"
245 self._remove_last_if_S()
246 if end:
247 self.out.append(end)
248 return delim.join(self.out)
249
250
252 """
253 Methods to serialize a CSSStylesheet and its parts
254
255 To use your own serializing method the easiest is to subclass CSS
256 Serializer and overwrite the methods you like to customize.
257 """
258
259 __forbidden_in_uri_matcher = re.compile(ur'''.*?[\)\s\;]''', re.U).match
260
262 """
263 prefs
264 instance of Preferences
265 """
266 if not prefs:
267 prefs = Preferences()
268 self.prefs = prefs
269 self._level = 0
270
271
272 self._selectors = []
273 self._selectorlevel = 0
274
276 "returns default or source atkeyword depending on prefs"
277 if self.prefs.defaultAtKeyword:
278 return default
279 else:
280 return rule.atkeyword
281
283 """
284 indent a block like a CSSStyleDeclaration to the given level
285 which may be higher than self._level (e.g. for CSSStyleDeclaration)
286 """
287 if not self.prefs.lineSeparator:
288 return text
289 return self.prefs.lineSeparator.join(
290 [u'%s%s' % (level * self.prefs.indent, line)
291 for line in text.split(self.prefs.lineSeparator)]
292 )
293
295 """
296 used by all styledeclarations to get the propertyname used
297 dependent on prefs setting defaultPropertyName and
298 keepAllProperties
299 """
300 if self.prefs.defaultPropertyName and not self.prefs.keepAllProperties:
301 return property.name
302 else:
303 return actual
304
306 if self.prefs.lineNumbers:
307 pad = len(str(text.count(self.prefs.lineSeparator)+1))
308 out = []
309 for i, line in enumerate(text.split(self.prefs.lineSeparator)):
310 out.append((u'%*i: %s') % (pad, i+1, line))
311 text = self.prefs.lineSeparator.join(out)
312 return text
313
315 """
316 returns s encloded between "..." and escaped delim charater ",
317 escape line breaks \\n \\r and \\f
318 """
319
320 s = s.replace('\n', '\\a ').replace(
321 '\r', '\\d ').replace(
322 '\f', '\\c ')
323 return u'"%s"' % s.replace('"', u'\\"')
324
325 - def _uri(self, uri):
331
333 "checks items valid property and prefs.validOnly"
334 return not self.prefs.validOnly or (self.prefs.validOnly and
335 x.valid)
336
360
369
371 """
372 serializes CSSCharsetRule
373 encoding: string
374
375 always @charset "encoding";
376 no comments or other things allowed!
377 """
378 if rule.wellformed:
379 return u'@charset %s;' % self._string(rule.encoding)
380 else:
381 return u''
382
406
408 """
409 serializes CSSImportRule
410
411 href
412 string
413 media
414 optional cssutils.stylesheets.medialist.MediaList
415 name
416 optional string
417
418 + CSSComments
419 """
420 if rule.wellformed:
421 out = Out(self)
422 out.append(self._atkeyword(rule, u'@import'))
423
424 for item in rule.seq:
425 typ, val = item.type, item.value
426 if 'href' == typ:
427
428 if self.prefs.importHrefFormat == 'string' or (
429 self.prefs.importHrefFormat != 'uri' and
430 rule.hreftype == 'string'):
431 out.append(val, 'STRING')
432 else:
433 out.append(val, 'URI')
434 elif 'media' == typ:
435
436 mediaText = self.do_stylesheets_medialist(val)
437 if mediaText and mediaText != u'all':
438 out.append(mediaText)
439 elif 'name' == typ:
440 out.append(val, 'STRING')
441 else:
442 out.append(val, typ)
443
444 return out.value(end=u';')
445 else:
446 return u''
447
449 """
450 serializes CSSNamespaceRule
451
452 uri
453 string
454 prefix
455 string
456
457 + CSSComments
458 """
459 if rule.wellformed:
460 out = Out(self)
461 out.append(self._atkeyword(rule, u'@namespace'))
462
463 for item in rule.seq:
464 typ, val = item.type, item.value
465 if 'namespaceURI' == typ:
466 out.append(val, 'STRING')
467 else:
468 out.append(val, typ)
469
470 return out.value(end=u';')
471 else:
472 return u''
473
521
523 """
524 serializes CSSPageRule
525
526 selectorText
527 string
528 style
529 CSSStyleDeclaration
530
531 + CSSComments
532 """
533 styleText = self.do_css_CSSStyleDeclaration(rule.style)
534
535 if styleText and rule.wellformed:
536 out = Out(self)
537 out.append(self._atkeyword(rule, u'@page'))
538
539 for item in rule.seq:
540 out.append(item.value, item.type)
541
542 out.append(u'{')
543 out.append(u'%s%s}' % (styleText, self.prefs.lineSeparator),
544 indent=1)
545 return out.value()
546 else:
547 return u''
548
550 """
551 serializes CSSUnknownRule
552 anything until ";" or "{...}"
553 + CSSComments
554 """
555 if rule.wellformed:
556 out = Out(self)
557 out.append(rule.atkeyword)
558 stacks = []
559 for item in rule.seq:
560 typ, val = item.type, item.value
561
562 if u'}' == val:
563
564 stackblock = stacks.pop().value()
565 if stackblock:
566 val = self._indentblock(
567 stackblock + self.prefs.lineSeparator + val,
568 min(1, len(stacks)+1))
569 else:
570 val = self._indentblock(val, min(1, len(stacks)+1))
571
572 if stacks:
573 stacks[-1].append(val, typ)
574 else:
575 out.append(val, typ)
576
577
578 if u'{' == val:
579
580 stacks.append(Out(self))
581
582 return out.value()
583 else:
584 return u''
585
587 """
588 serializes CSSStyleRule
589
590 selectorList
591 style
592
593 + CSSComments
594 """
595
596
597
598
599 if self.prefs.indentSpecificities:
600
601 elements = set([s.element for s in rule.selectorList])
602 specitivities = [s.specificity for s in rule.selectorList]
603 for selector in self._selectors:
604 lastelements = set([s.element for s in selector])
605 if elements.issubset(lastelements):
606
607 lastspecitivities = [s.specificity for s in selector]
608 if specitivities > lastspecitivities:
609 self._selectorlevel += 1
610 break
611 elif self._selectorlevel > 0:
612 self._selectorlevel -= 1
613 else:
614
615 self._selectors.append(rule.selectorList)
616 self._selectorlevel = 0
617
618
619
620 selectorText = self.do_css_SelectorList(rule.selectorList)
621 if not selectorText or not rule.wellformed:
622 return u''
623 self._level += 1
624 styleText = u''
625 try:
626 styleText = self.do_css_CSSStyleDeclaration(rule.style)
627 finally:
628 self._level -= 1
629 if not styleText:
630 if self.prefs.keepEmptyRules:
631 return u'%s%s{}' % (selectorText,
632 self.prefs.paranthesisSpacer)
633 else:
634 return self._indentblock(
635 u'%s%s{%s%s%s%s}' % (
636 selectorText,
637 self.prefs.paranthesisSpacer,
638 self.prefs.lineSeparator,
639 self._indentblock(styleText, self._level + 1),
640 self.prefs.lineSeparator,
641 (self._level + 1) * self.prefs.indent),
642 self._selectorlevel)
643
658
660 """
661 a single Selector including comments
662
663 an element has syntax (namespaceURI, name) where namespaceURI may be:
664
665 - cssutils._ANYNS => ``*|name``
666 - None => ``name``
667 - u'' => ``|name``
668 - any other value: => ``prefix|name``
669 """
670 if selector.wellformed:
671 out = Out(self)
672
673 DEFAULTURI = selector._namespaces.get('', None)
674 for item in selector.seq:
675 typ, val = item.type, item.value
676 if type(val) == tuple:
677
678 namespaceURI, name = val
679 if DEFAULTURI == namespaceURI or (not DEFAULTURI and
680 namespaceURI is None):
681 out.append(name, typ, space=False)
682 else:
683 if namespaceURI == cssutils._ANYNS:
684 prefix = u'*'
685 else:
686 try:
687 prefix = selector._namespaces.prefixForNamespaceURI(
688 namespaceURI)
689 except IndexError:
690 prefix = u''
691
692 out.append(u'%s|%s' % (prefix, name), typ, space=False)
693 else:
694 out.append(val, typ, space=False, keepS=True)
695
696 return out.value()
697 else:
698 return u''
699
752
798
800 """
801 a Properties priority "!" S* "important"
802 """
803
804
805 out = []
806 for part in priorityseq:
807 if hasattr(part, 'cssText'):
808 out.append(u' ')
809 out.append(part.cssText)
810 out.append(u' ')
811 else:
812 out.append(part)
813 return u''.join(out).strip()
814
816 """
817 serializes a CSSValue
818 """
819
820
821
822 if not cssvalue:
823 return u''
824 else:
825 sep = u',%s' % self.prefs.listItemSpacer
826 out = []
827 for part in cssvalue.seq:
828 if hasattr(part, 'cssText'):
829
830 out.append(part.cssText)
831 elif isinstance(part, basestring) and part == u',':
832 out.append(sep)
833 else:
834
835 if part and part[0] == part[-1] and part[0] in '\'"':
836
837 part = self._string(part[1:-1])
838 out.append(part)
839 return (u''.join(out)).strip()
840
853
871