Package cssutils :: Module serialize
[hide private]
[frames] | no frames]

Source Code for Module cssutils.serialize

  1  #!/usr/bin/env python 
  2  # -*- coding: utf-8 -*- 
  3  """serializer classes for CSS classes 
  4   
  5  """ 
  6  __all__ = ['CSSSerializer'] 
  7  __docformat__ = 'restructuredtext' 
  8  __author__ = '$LastChangedBy: cthedot $' 
  9  __date__ = '$LastChangedDate: 2007-12-29 17:13:39 +0100 (Sa, 29 Dez 2007) $' 
 10  __version__ = '$LastChangedRevision: 760 $' 
 11   
 12  import codecs 
 13  import re 
 14  import cssutils 
 15  import util 
 16   
17 -def _escapecss(e):
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.args[1][e.start:e.end] 25 return u''.join([ur'\%s ' % str(hex(ord(x)))[2:] # remove 0x from hex 26 .upper() for x in s]), e.end
27 28 codecs.register_error('escapecss', _escapecss) 29 30
31 -class Preferences(object):
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 importHrefFormat = None 45 Uses hreftype if ``None`` or explicit ``'string'`` or ``'uri'`` 46 indent = 4 * ' ' 47 Indentation of e.g Properties inside a CSSStyleDeclaration 48 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 54 defaultAtKeyword=%r, 55 defaultPropertyName=%r, 56 importHrefFormat=%r, 57 indent=%r, 58 keepAllProperties=%r, 59 keepComments=%r, 60 keepEmptyRules=%r, 61 lineNumbers=%r, 62 lineSeparator=%r, 63 listItemSpacer=%r, 64 omitLastSemicolon=%r, 65 paranthesisSpacer=%r, 66 propertyNameSpacer=%r, 67 validOnly=%r, 68 wellformedOnly=%r, 69 70 71 keepComments = True 72 If ``False`` removes all CSSComments 73 keepEmptyRules = False 74 defines if empty rules like e.g. ``a {}`` are kept in the resulting 75 serialized sheet 76 77 lineNumbers = False 78 Only used if a complete CSSStyleSheet is serialized. 79 lineSeparator = u'\\n' 80 How to end a line. This may be set to e.g. u'' for serializing of 81 CSSStyleDeclarations usable in HTML style attribute. 82 listItemSpacer = u' ' 83 string which is used in ``css.SelectorList``, ``css.CSSValue`` and 84 ``stylesheets.MediaList`` after the comma 85 omitLastSemicolon = True 86 If ``True`` omits ; after last property of CSSStyleDeclaration 87 paranthesisSpacer = u' ' 88 string which is used before an opening paranthesis like in a 89 ``css.CSSMediaRule`` or ``css.CSSStyleRule`` 90 propertyNameSpacer = u' ' 91 string which is used after a Property name colon 92 93 validOnly = False (**not anywhere used yet**) 94 if True only valid (Properties or Rules) are kept 95 96 A Property is valid if it is a known Property with a valid value. 97 Currently CSS 2.1 values as defined in cssproperties.py would be 98 valid. 99 100 wellformedOnly = True (**not anywhere used yet**) 101 only wellformed properties and rules are kept 102 103 **DEPRECATED**: removeInvalid = True 104 Omits invalid rules, replaced by ``validOnly`` which will be used 105 more cases 106 107 """
108 - def __init__(self, **initials):
109 """ 110 Always use named instead of positional parameters 111 """ 112 self.useDefaults() 113 114 for key, value in initials.items(): 115 if value: 116 self.__setattr__(key, value)
117
118 - def useDefaults(self):
119 "reset all preference options to the default value" 120 self.defaultAtKeyword = True 121 self.defaultPropertyName = True 122 self.importHrefFormat = None 123 self.indent = 4 * u' ' 124 self.keepAllProperties = True 125 self.keepComments = True 126 self.keepEmptyRules = False 127 self.lineNumbers = False 128 self.lineSeparator = u'\n' 129 self.listItemSpacer = u' ' 130 self.omitLastSemicolon = True 131 self.paranthesisSpacer = u' ' 132 self.propertyNameSpacer = u' ' 133 self.validOnly = False 134 self.wellformedOnly = True 135 # DEPRECATED 136 self.removeInvalid = True
137
138 - def useMinified(self):
139 """ 140 sets options to achive a minified stylesheet 141 142 you may want to set preferences with this convinience method 143 and set settings you want adjusted afterwards 144 """ 145 self.importHrefFormat = 'string' 146 self.indent = u'' 147 self.keepComments = False 148 self.keepEmptyRules = False 149 self.lineNumbers = False 150 self.lineSeparator = u'' 151 self.listItemSpacer = u'' 152 self.omitLastSemicolon = True 153 self.paranthesisSpacer = u'' 154 self.propertyNameSpacer = u'' 155 self.validOnly = False 156 self.wellformedOnly = True
157
158 - def __repr__(self):
159 return u"cssutils.css.%s(%s)" % (self.__class__.__name__, 160 u', '.join(['\n %s=%r' % (p, self.__getattribute__(p)) for p in self.__dict__] 161 ))
162
163 - def __str__(self):
164 return u"<cssutils.css.%s object %s at 0x%x" % (self.__class__.__name__, 165 u' '.join(['%s=%r' % (p, self.__getattribute__(p)) for p in self.__dict__] 166 ), 167 id(self))
168 169
170 -class CSSSerializer(object):
171 """ 172 Methods to serialize a CSSStylesheet and its parts 173 174 To use your own serializing method the easiest is to subclass CSS 175 Serializer and overwrite the methods you like to customize. 176 """ 177 __notinurimatcher = re.compile(ur'''.*?[\)\s\;]''', re.U).match 178 # chars not in URI without quotes around 179
180 - def __init__(self, prefs=None):
181 """ 182 prefs 183 instance of Preferences 184 """ 185 if not prefs: 186 prefs = Preferences() 187 self.prefs = prefs 188 self._level = 0 # current nesting level
189
190 - def _serialize(self, text):
191 if self.prefs.lineNumbers: 192 pad = len(str(text.count(self.prefs.lineSeparator)+1)) 193 out = [] 194 for i, line in enumerate(text.split(self.prefs.lineSeparator)): 195 out.append((u'%*i: %s') % (pad, i+1, line)) 196 text = self.prefs.lineSeparator.join(out) 197 return text
198
199 - def _noinvalids(self, x):
200 # DEPRECATED: REMOVE! 201 if self.prefs.removeInvalid and \ 202 hasattr(x, 'valid') and not x.valid: 203 return True 204 else: 205 return False
206
207 - def _valid(self, x):
208 """ 209 checks if valid items only and if yes it item is valid 210 """ 211 return not self.prefs.validOnly or (self.prefs.validOnly and 212 hasattr(x, 'valid') and 213 x.valid)
214
215 - def _wellformed(self, x):
216 """ 217 checks if wellformed items only and if yes it item is wellformed 218 """ 219 return self.prefs.wellformedOnly and hasattr(x, 'wellformed') and\ 220 x.wellformed
221
222 - def _escapestring(self, s, delim=u'"'):
223 """ 224 escapes delim charaters in string s with \delim 225 """ 226 return s.replace(delim, u'\\%s' % delim)
227
228 - def _getatkeyword(self, rule, default):
229 """ 230 used by all @rules to get the keyword used 231 dependent on prefs setting defaultAtKeyword 232 """ 233 if self.prefs.defaultAtKeyword: 234 return default 235 else: 236 return rule.atkeyword
237
238 - def _getpropertyname(self, property, actual):
239 """ 240 used by all styledeclarations to get the propertyname used 241 dependent on prefs setting defaultPropertyName 242 """ 243 if self.prefs.defaultPropertyName and \ 244 not self.prefs.keepAllProperties: 245 return property.normalname 246 else: 247 return actual
248
249 - def _indentblock(self, text, level):
250 """ 251 indent a block like a CSSStyleDeclaration to the given level 252 which may be higher than self._level (e.g. for CSSStyleDeclaration) 253 """ 254 if not self.prefs.lineSeparator: 255 return text 256 return self.prefs.lineSeparator.join( 257 [u'%s%s' % (level * self.prefs.indent, line) 258 for line in text.split(self.prefs.lineSeparator)] 259 )
260
261 - def _uri(self, uri):
262 """ 263 returns uri enclosed in " if necessary 264 """ 265 if CSSSerializer.__notinurimatcher(uri): 266 return '"%s"' % uri 267 else: 268 return uri
269
270 - def do_stylesheets_mediaquery(self, mediaquery):
271 """ 272 a single media used in medialist 273 """ 274 if len(mediaquery.seq) == 0: 275 return u'' 276 else: 277 out = [] 278 for part in mediaquery.seq: 279 if isinstance(part, cssutils.css.Property): # Property 280 out.append(u'(%s)' % part.cssText) 281 elif hasattr(part, 'cssText'): # comments 282 out.append(part.cssText) 283 else: 284 # TODO: media queries! 285 out.append(part) 286 return u' '.join(out)
287
288 - def do_stylesheets_medialist(self, medialist):
289 """ 290 comma-separated list of media, default is 'all' 291 292 If "all" is in the list, every other media *except* "handheld" will 293 be stripped. This is because how Opera handles CSS for PDAs. 294 """ 295 if len(medialist) == 0: 296 return u'all' 297 else: 298 sep = u',%s' % self.prefs.listItemSpacer 299 return sep.join( 300 (mq.mediaText for mq in medialist))
301
302 - def do_CSSStyleSheet(self, stylesheet):
303 out = [] 304 for rule in stylesheet.cssRules: 305 cssText = rule.cssText 306 if cssText: 307 out.append(cssText) 308 text = self._serialize(self.prefs.lineSeparator.join(out)) 309 310 # get encoding of sheet, defaults to UTF-8 311 try: 312 encoding = stylesheet.cssRules[0].encoding 313 except (IndexError, AttributeError): 314 encoding = 'UTF-8' 315 316 return text.encode(encoding, 'escapecss')
317
318 - def do_CSSComment(self, rule):
319 """ 320 serializes CSSComment which consists only of commentText 321 """ 322 if self.prefs.keepComments and rule._cssText: 323 return rule._cssText 324 else: 325 return u''
326
327 - def do_CSSCharsetRule(self, rule):
328 """ 329 serializes CSSCharsetRule 330 encoding: string 331 332 always @charset "encoding"; 333 no comments or other things allowed! 334 """ 335 if not rule.encoding or self._noinvalids(rule): 336 return u'' 337 return u'@charset "%s";' % self._escapestring(rule.encoding)
338
339 - def do_CSSFontFaceRule(self, rule):
340 """ 341 serializes CSSFontFaceRule 342 343 style 344 CSSStyleDeclaration 345 346 + CSSComments 347 """ 348 self._level += 1 349 try: 350 styleText = self.do_css_CSSStyleDeclaration(rule.style) 351 finally: 352 self._level -= 1 353 354 if not styleText or self._noinvalids(rule): 355 return u'' 356 357 before = [] 358 for x in rule.seq: 359 if hasattr(x, 'cssText'): 360 before.append(x.cssText) 361 else: 362 # TODO: resolve 363 raise SyntaxErr('serializing CSSFontFaceRule: unexpected %r' % x) 364 if before: 365 before = u' '.join(before).strip() 366 if before: 367 before = u' %s' % before 368 else: 369 before = u'' 370 371 return u'%s%s {%s%s%s%s}' % ( 372 self._getatkeyword(rule, u'@font-face'), 373 before, 374 self.prefs.lineSeparator, 375 self._indentblock(styleText, self._level + 1), 376 self.prefs.lineSeparator, 377 (self._level + 1) * self.prefs.indent 378 )
379
380 - def do_CSSImportRule(self, rule):
381 """ 382 serializes CSSImportRule 383 384 href 385 string 386 hreftype 387 'uri' or 'string' 388 media 389 cssutils.stylesheets.medialist.MediaList 390 391 + CSSComments 392 """ 393 if not rule.href or self._noinvalids(rule): 394 return u'' 395 out = [u'%s ' % self._getatkeyword(rule, u'@import')] 396 for part in rule.seq: 397 if rule.href == part: 398 if self.prefs.importHrefFormat == 'uri': 399 out.append(u'url(%s)' % self._uri(part)) 400 elif self.prefs.importHrefFormat == 'string' or \ 401 rule.hreftype == 'string': 402 out.append(u'"%s"' % self._escapestring(part)) 403 else: 404 out.append(u'url(%s)' % self._uri(part)) 405 elif isinstance( 406 part, cssutils.stylesheets.medialist.MediaList): 407 mediaText = self.do_stylesheets_medialist(part)#.strip() 408 if mediaText and not mediaText == u'all': 409 out.append(u' %s' % mediaText) 410 elif hasattr(part, 'cssText'): # comments 411 out.append(part.cssText) 412 return u'%s;' % u''.join(out)
413
414 - def do_CSSNamespaceRule(self, rule):
415 """ 416 serializes CSSNamespaceRule 417 418 uri 419 string 420 prefix 421 string 422 423 + CSSComments 424 """ 425 if not rule.namespaceURI or self._noinvalids(rule): 426 return u'' 427 428 out = [u'%s' % self._getatkeyword(rule, u'@namespace')] 429 for part in rule.seq: 430 if rule.prefix == part and part != u'': 431 out.append(u' %s' % part) 432 elif rule.namespaceURI == part: 433 out.append(u' "%s"' % self._escapestring(part)) 434 elif hasattr(part, 'cssText'): # comments 435 out.append(part.cssText) 436 return u'%s;' % u''.join(out)
437
438 - def do_CSSMediaRule(self, rule):
439 """ 440 serializes CSSMediaRule 441 442 + CSSComments 443 """ 444 rulesout = [] 445 for r in rule.cssRules: 446 rtext = r.cssText 447 if rtext: 448 # indent each line of cssText 449 rulesout.append(self._indentblock(rtext, self._level + 1)) 450 rulesout.append(self.prefs.lineSeparator) 451 452 if not self.prefs.keepEmptyRules and not u''.join(rulesout).strip() or\ 453 self._noinvalids(rule.media): 454 return u'' 455 456 # if len(rule.cssRules) == 0 and not self.prefs.keepEmptyRules or\ 457 # self._noinvalids(rule.media): 458 # return u'' 459 mediaText = self.do_stylesheets_medialist(rule.media)#.strip() 460 out = [u'%s %s%s{%s' % (self._getatkeyword(rule, u'@media'), 461 mediaText, 462 self.prefs.paranthesisSpacer, 463 self.prefs.lineSeparator)] 464 out.extend(rulesout) 465 return u'%s%s}' % (u''.join(out), 466 (self._level + 1) * self.prefs.indent)
467
468 - def do_CSSPageRule(self, rule):
469 """ 470 serializes CSSPageRule 471 472 selectorText 473 string 474 style 475 CSSStyleDeclaration 476 477 + CSSComments 478 """ 479 self._level += 1 480 try: 481 styleText = self.do_css_CSSStyleDeclaration(rule.style) 482 finally: 483 self._level -= 1 484 485 if not styleText or self._noinvalids(rule): 486 return u'' 487 488 return u'%s%s {%s%s%s%s}' % ( 489 self._getatkeyword(rule, u'@page'), 490 self.do_pageselector(rule.seq), 491 self.prefs.lineSeparator, 492 self._indentblock(styleText, self._level + 1), 493 self.prefs.lineSeparator, 494 (self._level + 1) * self.prefs.indent 495 )
496
497 - def do_pageselector(self, seq):
498 """ 499 a selector of a CSSPageRule including comments 500 """ 501 if len(seq) == 0 or self._noinvalids(seq): 502 return u'' 503 else: 504 out = [] 505 for part in seq: 506 if hasattr(part, 'cssText'): 507 out.append(part.cssText) 508 else: 509 out.append(part) 510 return u' %s' % u''.join(out)
511
512 - def do_CSSUnknownRule(self, rule):
513 """ 514 serializes CSSUnknownRule 515 anything until ";" or "{...}" 516 + CSSComments 517 """ 518 if rule.atkeyword and not self._noinvalids(rule): 519 out = [u'%s' % rule.atkeyword] 520 for part in rule.seq: 521 if isinstance(part, cssutils.css.csscomment.CSSComment): 522 if self.prefs.keepComments: 523 out.append(part.cssText) 524 else: 525 out.append(part) 526 if not (out[-1].endswith(u';') or out[-1].endswith(u'}')): 527 out.append(u';') 528 return u''.join(out) 529 else: 530 return u''
531
532 - def do_CSSStyleRule(self, rule):
533 """ 534 serializes CSSStyleRule 535 536 selectorList 537 style 538 539 + CSSComments 540 """ 541 selectorText = self.do_css_SelectorList(rule.selectorList) 542 if not selectorText or self._noinvalids(rule): 543 return u'' 544 self._level += 1 545 styleText = u'' 546 try: 547 styleText = self.do_css_CSSStyleDeclaration(rule.style) 548 finally: 549 self._level -= 1 550 if not styleText: 551 if self.prefs.keepEmptyRules: 552 return u'%s%s{}' % (selectorText, 553 self.prefs.paranthesisSpacer) 554 else: 555 return u'%s%s{%s%s%s%s}' % ( 556 selectorText, 557 self.prefs.paranthesisSpacer, 558 self.prefs.lineSeparator, 559 self._indentblock(styleText, self._level + 1), 560 self.prefs.lineSeparator, 561 (self._level + 1) * self.prefs.indent)
562
563 - def do_css_SelectorList(self, selectorlist):
564 """ 565 comma-separated list of Selectors 566 """ 567 if selectorlist.seq and self._wellformed(selectorlist) and\ 568 self._valid(selectorlist): 569 out = [] 570 sep = u',%s' % self.prefs.listItemSpacer 571 for part in selectorlist.seq: 572 if hasattr(part, 'cssText'): 573 out.append(part.cssText) 574 # elif u',' == part: 575 # out.append(sep) 576 elif isinstance(part, cssutils.css.Selector): 577 out.append(self.do_css_Selector(part)) 578 else: 579 out.append(part) # ? 580 return sep.join(out) 581 else: 582 return u''
583 584
585 - def do_css_Selector(self, selector):
586 """ 587 a single selector including comments 588 589 TODO: style combinators like + > 590 """ 591 if selector.seq and self._wellformed(selector) and\ 592 self._valid(selector): 593 out = [] 594 for part in selector.seq: 595 if hasattr(part, 'cssText'): 596 out.append(part.cssText) 597 else: 598 if type(part) == dict: 599 out.append(part['value']) 600 else: 601 out.append(part) 602 return u''.join(out) 603 else: 604 return u''
605
606 - def do_css_CSSStyleDeclaration(self, style, separator=None):
607 """ 608 Style declaration of CSSStyleRule 609 """ 610 if len(style.seq) > 0 and self._wellformed(style) and\ 611 self._valid(style): 612 if separator is None: 613 separator = self.prefs.lineSeparator 614 615 if self.prefs.keepAllProperties: 616 parts = style.seq 617 else: 618 # find distinct names 619 nnames = set() 620 for x in style.seq: 621 if isinstance(x, cssutils.css.Property): 622 nnames.add(x.normalname) 623 # filter list 624 parts = [] 625 for x in reversed(style.seq): 626 if isinstance(x, cssutils.css.Property): 627 if x.normalname in nnames: 628 parts.append(x) 629 nnames.remove(x.normalname) 630 else: 631 parts.append(x) 632 parts.reverse() 633 634 out = [] 635 for (i, part) in enumerate(parts): 636 if isinstance(part, cssutils.css.CSSComment): 637 # CSSComment 638 if self.prefs.keepComments: 639 out.append(part.cssText) 640 out.append(separator) 641 elif isinstance(part, cssutils.css.Property): 642 # PropertySimilarNameList 643 out.append(self.do_Property(part)) 644 if not (self.prefs.omitLastSemicolon and i==len(parts)-1): 645 out.append(u';') 646 out.append(separator) 647 else: 648 # other? 649 out.append(part) 650 651 if out and out[-1] == separator: 652 del out[-1] 653 654 return u''.join(out) 655 656 else: 657 return u''
658
659 - def do_Property(self, property):
660 """ 661 Style declaration of CSSStyleRule 662 663 Property has a seqs attribute which contains seq lists for 664 name, a CSSvalue and a seq list for priority 665 """ 666 out = [] 667 if property.seqs[0] and self._wellformed(property) and\ 668 self._valid(property): 669 nameseq, cssvalue, priorityseq = property.seqs 670 671 #name 672 for part in nameseq: 673 if hasattr(part, 'cssText'): 674 out.append(part.cssText) 675 elif property.name == part: 676 out.append(self._getpropertyname(property, part)) 677 else: 678 out.append(part) 679 680 if out and (not property._mediaQuery or 681 property._mediaQuery and cssvalue.cssText): 682 # MediaQuery may consist of name only 683 out.append(u':') 684 out.append(self.prefs.propertyNameSpacer) 685 686 # value 687 out.append(cssvalue.cssText) 688 689 # priority 690 if out and priorityseq: 691 out.append(u' ') 692 for part in priorityseq: 693 if hasattr(part, 'cssText'): # comments 694 out.append(part.cssText) 695 else: 696 out.append(part) 697 698 return u''.join(out)
699
700 - def do_Property_priority(self, priorityseq):
701 """ 702 a Properties priority "!" S* "important" 703 """ 704 out = [] 705 for part in priorityseq: 706 if hasattr(part, 'cssText'): # comments 707 out.append(u' ') 708 out.append(part.cssText) 709 out.append(u' ') 710 else: 711 out.append(part) 712 return u''.join(out).strip()
713
714 - def do_css_CSSValue(self, cssvalue):
715 """ 716 serializes a CSSValue 717 """ 718 if not cssvalue: 719 return u'' 720 else: 721 sep = u',%s' % self.prefs.listItemSpacer 722 out = [] 723 for part in cssvalue.seq: 724 if hasattr(part, 'cssText'): 725 # comments or CSSValue if a CSSValueList 726 out.append(part.cssText) 727 elif isinstance(part, basestring) and part == u',': 728 out.append(sep) 729 else: 730 out.append(part) 731 return (u''.join(out)).strip()
732