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  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   
20 -def _escapecss(e):
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:] # remove 0x from hex 30 .zfill(6).upper() for x in s]), e.end
31 32 codecs.register_error('escapecss', _escapecss) 33 34
35 -class Preferences(object):
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
102 - def useDefaults(self):
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 # DEPRECATED 120 self.removeInvalid = True
121
122 - def useMinified(self):
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
143 -class CSSSerializer(object):
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 # chars not in URI without quotes around 152
153 - def __init__(self, prefs=None):
154 """ 155 prefs 156 instance of Preferences 157 """ 158 if not prefs: 159 prefs = Preferences() 160 self.prefs = prefs 161 self._level = 0 # current nesting level
162
163 - def _serialize(self, text):
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
172 - def _noinvalids(self, x):
173 # DEPRECATED: REMOVE! 174 if self.prefs.removeInvalid and \ 175 hasattr(x, 'valid') and not x.valid: 176 return True 177 else: 178 return False
179
180 - def _valid(self, x):
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
188 - def _wellformed(self, x):
189 """ 190 checks if wellformed items only and if yes it item is wellformed 191 """ 192 return self.prefs.wellformedOnly and hasattr(x, 'wellformed') and\ 193 x.wellformed
194
195 - def _escapestring(self, s, delim=u'"'):
196 """ 197 escapes delim charaters in string s with \delim 198 """ 199 return s.replace(delim, u'\\%s' % delim)
200
201 - def _getatkeyword(self, rule, default):
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
211 - def _getpropertyname(self, property, actual):
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
222 - def _indentblock(self, text, level):
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):
235 """ 236 returns uri enclosed in " if necessary 237 """ 238 if CSSSerializer.__notinurimatcher(uri): 239 return '"%s"' % uri 240 else: 241 return uri
242
243 - def do_stylesheets_mediaquery(self, mediaquery):
244 """ 245 a single media used in medialist 246 """ 247 if len(mediaquery.seq) == 0: 248 return u'' 249 else: 250 out = [] 251 for part in mediaquery.seq: 252 if isinstance(part, cssutils.css.Property): # Property 253 out.append(u'(%s)' % part.cssText) 254 elif hasattr(part, 'cssText'): # comments 255 out.append(part.cssText) 256 else: 257 # TODO: media queries! 258 out.append(part) 259 return u' '.join(out)
260
261 - def do_stylesheets_medialist(self, medialist):
262 """ 263 comma-separated list of media, default is 'all' 264 265 If "all" is in the list, every other media *except* "handheld" will 266 be stripped. This is because how Opera handles CSS for PDAs. 267 """ 268 if len(medialist) == 0: 269 return u'all' 270 else: 271 sep = u',%s' % self.prefs.listItemSpacer 272 return sep.join( 273 (mq.mediaText for mq in medialist))
274
275 - def do_CSSStyleSheet(self, stylesheet):
276 out = [] 277 for rule in stylesheet.cssRules: 278 cssText = rule.cssText 279 if cssText: 280 out.append(cssText) 281 text = self._serialize(self.prefs.lineSeparator.join(out)) 282 283 # get encoding of sheet, defaults to UTF-8 284 try: 285 encoding = stylesheet.cssRules[0].encoding 286 except (IndexError, AttributeError): 287 encoding = 'UTF-8' 288 289 return text.encode(encoding, 'escapecss')
290
291 - def do_CSSComment(self, rule):
292 """ 293 serializes CSSComment which consists only of commentText 294 """ 295 if self.prefs.keepComments and rule._cssText: 296 return rule._cssText 297 else: 298 return u''
299
300 - def do_CSSCharsetRule(self, rule):
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
312 - def do_CSSImportRule(self, rule):
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'): # comments 343 out.append(part.cssText) 344 return u'%s;' % u''.join(out)
345
346 - def do_CSSNamespaceRule(self, rule):
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'): # comments 367 out.append(part.cssText) 368 return u'%s;' % u''.join(out)
369
370 - def do_CSSMediaRule(self, rule):
371 """ 372 serializes CSSMediaRule 373 374 + CSSComments 375 """ 376 rulesout = [] 377 for r in rule.cssRules: 378 rtext = r.cssText 379 if rtext: 380 # indent each line of cssText 381 rulesout.append(self._indentblock(rtext, self._level + 1)) 382 rulesout.append(self.prefs.lineSeparator) 383 384 if not self.prefs.keepEmptyRules and not u''.join(rulesout).strip() or\ 385 self._noinvalids(rule.media): 386 return u'' 387 388 # if len(rule.cssRules) == 0 and not self.prefs.keepEmptyRules or\ 389 # self._noinvalids(rule.media): 390 # return u'' 391 mediaText = self.do_stylesheets_medialist(rule.media).strip() 392 out = [u'%s %s%s{%s' % (self._getatkeyword(rule, u'@media'), 393 mediaText, 394 self.prefs.paranthesisSpacer, 395 self.prefs.lineSeparator)] 396 out.extend(rulesout) 397 return u'%s%s}' % (u''.join(out), 398 (self._level + 1) * self.prefs.indent)
399
400 - def do_CSSPageRule(self, rule):
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
444 - def do_CSSUnknownRule(self, rule):
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
464 - def do_CSSStyleRule(self, rule):
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
495 - def do_css_SelectorList(self, selectorlist):
496 """ 497 comma-separated list of Selectors 498 """ 499 if len(selectorlist.seq) == 0 or self._noinvalids(selectorlist): 500 return u'' 501 else: 502 out = [] 503 sep = u',%s' % self.prefs.listItemSpacer 504 for part in selectorlist.seq: 505 if hasattr(part, 'cssText'): 506 out.append(part.cssText) 507 # elif u',' == part: 508 # out.append(sep) 509 elif isinstance(part, cssutils.css.Selector): 510 out.append(self.do_css_Selector(part).strip()) 511 else: 512 out.append(part) # ? 513 return sep.join(out)
514
515 - def do_css_Selector(self, selector):
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
535 - def do_css_CSSStyleDeclaration(self, style, separator=None):
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 # find distinct names 548 nnames = set() 549 for x in style.seq: 550 if isinstance(x, cssutils.css.Property): 551 nnames.add(x.normalname) 552 # filter list 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 # CSSComment 567 if self.prefs.keepComments: 568 out.append(part.cssText) 569 out.append(separator) 570 elif isinstance(part, cssutils.css.Property): 571 # PropertySimilarNameList 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 # other? 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
588 - def do_Property(self, property):
589 """ 590 Style declaration of CSSStyleRule 591 592 Property has a seqs attribute which contains seq lists for 593 name, a CSSvalue and a seq list for priority 594 """ 595 out = [] 596 if property.seqs[0] and self._wellformed(property) and\ 597 self._valid(property): 598 nameseq, cssvalue, priorityseq = property.seqs 599 600 #name 601 for part in nameseq: 602 if hasattr(part, 'cssText'): 603 out.append(part.cssText) 604 elif property.name == part: 605 out.append(self._getpropertyname(property, part)) 606 else: 607 out.append(part) 608 609 if out and (not property._mediaQuery or 610 property._mediaQuery and cssvalue.cssText): 611 # MediaQuery may consist of name only 612 out.append(u':') 613 out.append(self.prefs.propertyNameSpacer) 614 615 # value 616 out.append(cssvalue.cssText) 617 618 # priority 619 if out and priorityseq: 620 out.append(u' ') 621 for part in priorityseq: 622 if hasattr(part, 'cssText'): # comments 623 out.append(part.cssText) 624 else: 625 out.append(part) 626 627 return u''.join(out)
628
629 - def do_Property_priority(self, priorityseq):
630 """ 631 a Properties priority "!" S* "important" 632 """ 633 out = [] 634 for part in priorityseq: 635 if hasattr(part, 'cssText'): # comments 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
643 - def do_css_CSSValue(self, cssvalue):
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 # comments or CSSValue if a CSSValueList 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