1
2
3 """serializer classes for CSS classes
4
5 """
6 __all__ = ['CSSSerializer', 'Preferences']
7 __docformat__ = 'restructuredtext'
8 __author__ = '$LastChangedBy: cthedot $'
9 __date__ = '$LastChangedDate: 2008-02-22 19:03:20 +0100 (Fr, 22 Feb 2008) $'
10 __version__ = '$LastChangedRevision: 1070 $'
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.object[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 defaultPropertyPriority = True
45 Should the normalized or literal priority be used, e.g. '!important'
46 or u'!Im\portant'
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 indentSpecificities = False
53 Indent rules with subset of Selectors and higher Specitivity
54
55 keepAllProperties = True
56 If ``True`` all properties set in the original CSSStylesheet
57 are kept meaning even properties set twice with the exact same
58 same name are kept!
59 keepComments = True
60 If ``False`` removes all CSSComments
61 keepEmptyRules = False
62 defines if empty rules like e.g. ``a {}`` are kept in the resulting
63 serialized sheet
64 keepUsedNamespaceRulesOnly = False
65 if True only namespace rules which are actually used are kept
66
67 lineNumbers = False
68 Only used if a complete CSSStyleSheet is serialized.
69 lineSeparator = u'\\n'
70 How to end a line. This may be set to e.g. u'' for serializing of
71 CSSStyleDeclarations usable in HTML style attribute.
72 listItemSpacer = u' '
73 string which is used in ``css.SelectorList``, ``css.CSSValue`` and
74 ``stylesheets.MediaList`` after the comma
75 omitLastSemicolon = True
76 If ``True`` omits ; after last property of CSSStyleDeclaration
77 paranthesisSpacer = u' '
78 string which is used before an opening paranthesis like in a
79 ``css.CSSMediaRule`` or ``css.CSSStyleRule``
80 propertyNameSpacer = u' '
81 string which is used after a Property name colon
82 selectorCombinatorSpacer = u' '
83 string which is used before and after a Selector combinator like +, > or ~.
84 CSSOM defines a single space for this which is also the default in cssutils.
85 spacer = u' '
86 general spacer, used e.g. by CSSUnknownRule
87
88 validOnly = False **DO NOT CHANGE YET**
89 if True only valid (currently Properties) are kept
90
91 A Property is valid if it is a known Property with a valid value.
92 Currently CSS 2.1 values as defined in cssproperties.py would be
93 valid.
94
95 """
105
107 "reset all preference options to the default value"
108 self.defaultAtKeyword = True
109 self.defaultPropertyName = True
110 self.defaultPropertyPriority = True
111 self.importHrefFormat = None
112 self.indent = 4 * u' '
113 self.indentSpecificities = False
114 self.keepAllProperties = True
115 self.keepComments = True
116 self.keepEmptyRules = False
117 self.keepUsedNamespaceRulesOnly = False
118 self.lineNumbers = False
119 self.lineSeparator = u'\n'
120 self.listItemSpacer = u' '
121 self.omitLastSemicolon = True
122 self.paranthesisSpacer = u' '
123 self.propertyNameSpacer = u' '
124 self.selectorCombinatorSpacer = u' '
125 self.spacer = u' '
126 self.validOnly = False
127
129 """
130 sets options to achive a minified stylesheet
131
132 you may want to set preferences with this convinience method
133 and set settings you want adjusted afterwards
134 """
135 self.importHrefFormat = 'string'
136 self.indent = u''
137 self.keepComments = False
138 self.keepEmptyRules = False
139 self.keepUsedNamespaceRulesOnly = True
140 self.lineNumbers = False
141 self.lineSeparator = u''
142 self.listItemSpacer = u''
143 self.omitLastSemicolon = True
144 self.paranthesisSpacer = u''
145 self.propertyNameSpacer = u''
146 self.selectorCombinatorSpacer = u''
147 self.spacer = u''
148 self.validOnly = False
149
151 return u"cssutils.css.%s(%s)" % (self.__class__.__name__,
152 u', '.join(['\n %s=%r' % (p, self.__getattribute__(p)) for p in self.__dict__]
153 ))
154
156 return u"<cssutils.css.%s object %s at 0x%x" % (self.__class__.__name__,
157 u' '.join(['%s=%r' % (p, self.__getattribute__(p)) for p in self.__dict__]
158 ),
159 id(self))
160
161
163 """
164 a simple class which makes appended items available as a combined string
165 """
167 self.ser = ser
168 self.out = []
169
171 if self.out and not self.out[-1].strip():
172
173 del self.out[-1]
174
175 - def append(self, val, typ=None, space=True, keepS=False, indent=False):
176 """
177 Appends val. Adds a single S after each token except as follows:
178
179 - typ COMMENT
180 uses cssText depending on self.ser.prefs.keepComments
181 - typ cssutils.css.CSSRule.UNKNOWN_RULE
182 uses cssText
183 - typ STRING
184 escapes ser._string
185 - typ S
186 ignored except ``keepS=True``
187 - typ URI
188 calls ser_uri
189 - val {
190 adds \n after
191 - val ;
192 removes S before and adds \n after
193 - val , :
194 removes S before
195 - val + > ~
196 encloses in prefs.selectorCombinatorSpacer
197 - some other vals
198 add *spacer except ``space=False``
199 """
200 if val or 'STRING' == typ:
201
202 if 'COMMENT' == typ:
203 if self.ser.prefs.keepComments:
204 val = val.cssText
205 else:
206 return
207 elif cssutils.css.CSSRule.UNKNOWN_RULE == typ:
208 val = val.cssText
209 elif 'S' == typ and not keepS:
210 return
211 elif 'STRING' == typ:
212
213 if val is None:
214 return
215 val = self.ser._string(val)
216 elif 'URI' == typ:
217 val = self.ser._uri(val)
218 elif val in u'+>~,:{;)]':
219 self._remove_last_if_S()
220
221
222 if indent:
223 self.out.append(self.ser._indentblock(val, self.ser._level+1))
224 else:
225 self.out.append(val)
226
227
228 if 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
370
372 """
373 serializes CSSCharsetRule
374 encoding: string
375
376 always @charset "encoding";
377 no comments or other things allowed!
378 """
379
380 if rule.wellformed:
381 return u'@charset %s;' % self._string(rule.encoding)
382 else:
383 return u''
384
408
410 """
411 serializes CSSImportRule
412
413 href
414 string
415 media
416 optional cssutils.stylesheets.medialist.MediaList
417 name
418 optional string
419
420 + CSSComments
421 """
422 if rule.wellformed:
423 out = Out(self)
424 out.append(self._atkeyword(rule, u'@import'))
425
426 for item in rule.seq:
427 typ, val = item.type, item.value
428 if 'href' == typ:
429
430 if self.prefs.importHrefFormat == 'string' or (
431 self.prefs.importHrefFormat != 'uri' and
432 rule.hreftype == 'string'):
433 out.append(val, 'STRING')
434 else:
435 out.append(val, 'URI')
436 elif 'media' == typ:
437
438 mediaText = self.do_stylesheets_medialist(val)
439 if mediaText and mediaText != u'all':
440 out.append(mediaText)
441 elif 'name' == typ:
442 out.append(val, 'STRING')
443 else:
444 out.append(val, typ)
445
446 return out.value(end=u';')
447 else:
448 return u''
449
451 """
452 serializes CSSNamespaceRule
453
454 uri
455 string
456 prefix
457 string
458
459 + CSSComments
460 """
461 if rule.wellformed:
462 out = Out(self)
463 out.append(self._atkeyword(rule, u'@namespace'))
464
465 for item in rule.seq:
466 typ, val = item.type, item.value
467 if 'namespaceURI' == typ:
468 out.append(val, 'STRING')
469 else:
470 out.append(val, typ)
471
472 return out.value(end=u';')
473 else:
474 return u''
475
523
525 """
526 serializes CSSPageRule
527
528 selectorText
529 string
530 style
531 CSSStyleDeclaration
532
533 + CSSComments
534 """
535 styleText = self.do_css_CSSStyleDeclaration(rule.style)
536
537 if styleText and rule.wellformed:
538 out = Out(self)
539 out.append(self._atkeyword(rule, u'@page'))
540
541 for item in rule.seq:
542 out.append(item.value, item.type)
543
544 out.append(u'{')
545 out.append(u'%s%s}' % (styleText, self.prefs.lineSeparator),
546 indent=1)
547 return out.value()
548 else:
549 return u''
550
552 """
553 serializes CSSUnknownRule
554 anything until ";" or "{...}"
555 + CSSComments
556 """
557 if rule.wellformed:
558 out = Out(self)
559 out.append(rule.atkeyword)
560 stacks = []
561 for item in rule.seq:
562 typ, val = item.type, item.value
563
564
565 if u'}' == val:
566
567 stackblock = stacks.pop().value()
568 if stackblock:
569 val = self._indentblock(
570 stackblock + self.prefs.lineSeparator + val,
571 min(1, len(stacks)+1))
572
573 if stacks:
574 stacks[-1].append(val, typ)
575 else:
576 out.append(val, typ)
577
578
579 if u'{' == val:
580
581 stacks.append(Out(self))
582
583 return out.value()
584 else:
585 return u''
586
588 """
589 serializes CSSStyleRule
590
591 selectorList
592 style
593
594 + CSSComments
595 """
596
597
598
599
600 if self.prefs.indentSpecificities:
601
602 elements = set([s.element for s in rule.selectorList])
603 specitivities = [s.specificity for s in rule.selectorList]
604 for selector in self._selectors:
605 lastelements = set([s.element for s in selector])
606 if elements.issubset(lastelements):
607
608 lastspecitivities = [s.specificity for s in selector]
609 if specitivities > lastspecitivities:
610 self._selectorlevel += 1
611 break
612 elif self._selectorlevel > 0:
613 self._selectorlevel -= 1
614 else:
615
616 self._selectors.append(rule.selectorList)
617 self._selectorlevel = 0
618
619
620
621 selectorText = self.do_css_SelectorList(rule.selectorList)
622 if not selectorText or not rule.wellformed:
623 return u''
624 self._level += 1
625 styleText = u''
626 try:
627 styleText = self.do_css_CSSStyleDeclaration(rule.style)
628 finally:
629 self._level -= 1
630 if not styleText:
631 if self.prefs.keepEmptyRules:
632 return u'%s%s{}' % (selectorText,
633 self.prefs.paranthesisSpacer)
634 else:
635 return self._indentblock(
636 u'%s%s{%s%s%s%s}' % (
637 selectorText,
638 self.prefs.paranthesisSpacer,
639 self.prefs.lineSeparator,
640 self._indentblock(styleText, self._level + 1),
641 self.prefs.lineSeparator,
642 (self._level + 1) * self.prefs.indent),
643 self._selectorlevel)
644
659
661 """
662 a single Selector including comments
663
664 an element has syntax (namespaceURI, name) where namespaceURI may be:
665
666 - cssutils._ANYNS => ``*|name``
667 - None => ``name``
668 - u'' => ``|name``
669 - any other value: => ``prefix|name``
670 """
671 if selector.wellformed:
672 out = Out(self)
673
674 DEFAULTURI = selector._namespaces.get('', None)
675 for item in selector.seq:
676 typ, val = item.type, item.value
677 if type(val) == tuple:
678
679 namespaceURI, name = val
680 if DEFAULTURI == namespaceURI or (not DEFAULTURI and
681 namespaceURI is None):
682 out.append(name, typ, space=False)
683 else:
684 if namespaceURI == cssutils._ANYNS:
685 prefix = u'*'
686 else:
687 try:
688 prefix = selector._namespaces.prefixForNamespaceURI(
689 namespaceURI)
690 except IndexError:
691 prefix = u''
692
693 out.append(u'%s|%s' % (prefix, name), typ, space=False)
694 else:
695 out.append(val, typ, space=False, keepS=True)
696
697 return out.value()
698 else:
699 return u''
700
702 """
703 Style declaration of CSSStyleRule
704 """
705
706
707
708 if len(style.seq) > 0:
709 if separator is None:
710 separator = self.prefs.lineSeparator
711
712 if self.prefs.keepAllProperties:
713
714 parts = style.seq
715 else:
716
717 _effective = style.getProperties()
718 parts = [x for x in style.seq
719 if (isinstance(x, cssutils.css.Property)
720 and x in _effective)
721 or not isinstance(x, cssutils.css.Property)]
722
723 out = []
724 for i, part in enumerate(parts):
725 if isinstance(part, cssutils.css.CSSComment):
726
727 if self.prefs.keepComments:
728 out.append(part.cssText)
729 out.append(separator)
730 elif isinstance(part, cssutils.css.Property):
731
732 out.append(self.do_Property(part))
733 if not (self.prefs.omitLastSemicolon and i==len(parts)-1):
734 out.append(u';')
735 out.append(separator)
736 else:
737
738 out.append(part)
739
740 if out and out[-1] == separator:
741 del out[-1]
742
743 return u''.join(out)
744
745 else:
746 return u''
747
793
795 """
796 a Properties priority "!" S* "important"
797 """
798
799
800 out = []
801 for part in priorityseq:
802 if hasattr(part, 'cssText'):
803 out.append(u' ')
804 out.append(part.cssText)
805 out.append(u' ')
806 else:
807 out.append(part)
808 return u''.join(out).strip()
809
811 """
812 serializes a CSSValue
813 """
814
815
816
817 if not cssvalue:
818 return u''
819 else:
820 sep = u',%s' % self.prefs.listItemSpacer
821 out = []
822 for part in cssvalue.seq:
823 if hasattr(part, 'cssText'):
824
825 out.append(part.cssText)
826 elif isinstance(part, basestring) and part == u',':
827 out.append(sep)
828 else:
829
830 if part and part[0] == part[-1] and part[0] in '\'"':
831
832 part = self._string(part[1:-1])
833 out.append(part)
834 return (u''.join(out)).strip()
835
848
866