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

Source Code for Module cssutils.serialize

  1  #!/usr/bin/env python 
  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   
18 -class Preferences(object):
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
85 - def useDefaults(self):
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 # DEPRECATED 103 self.removeInvalid = True
104
105 - def useMinified(self):
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
126 -class CSSSerializer(object):
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 # chars not in URI without quotes around 135
136 - def __init__(self, prefs=None):
137 """ 138 prefs 139 instance of Preferences 140 """ 141 if not prefs: 142 prefs = Preferences() 143 self.prefs = prefs 144 self._level = 0 # current nesting level
145
146 - def _serialize(self, text):
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
155 - def _noinvalids(self, x):
156 # DEPRECATED: REMOVE! 157 if self.prefs.removeInvalid and \ 158 hasattr(x, 'valid') and not x.valid: 159 return True 160 else: 161 return False
162
163 - def _valid(self, x):
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
171 - def _wellformed(self, x):
172 """ 173 checks if wellformed items only and if yes it item is wellformed 174 """ 175 return self.prefs.wellformedOnly and hasattr(x, 'wellformed') and\ 176 x.wellformed
177
178 - def _escapestring(self, s, delim=u'"'):
179 """ 180 escapes delim charaters in string s with \delim 181 """ 182 return s.replace(delim, u'\\%s' % delim)
183
184 - def _getatkeyword(self, rule, default):
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
194 - def _getpropertyname(self, property, actual):
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
205 - def _indentblock(self, text, level):
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):
218 """ 219 returns uri enclosed in " if necessary 220 """ 221 if CSSSerializer.__notinurimatcher(uri): 222 return '"%s"' % uri 223 else: 224 return uri
225
226 - def do_stylesheets_mediaquery(self, mediaquery):
227 """ 228 a single media used in medialist 229 """ 230 if len(mediaquery.seq) == 0: 231 return u'' 232 else: 233 out = [] 234 for part in mediaquery.seq: 235 if isinstance(part, cssutils.css.Property): # Property 236 out.append(u'(%s)' % part.cssText) 237 elif hasattr(part, 'cssText'): # comments 238 out.append(part.cssText) 239 else: 240 # TODO: media queries! 241 out.append(part) 242 return u' '.join(out)
243
244 - def do_stylesheets_medialist(self, medialist):
245 """ 246 comma-separated list of media, default is 'all' 247 248 If "all" is in the list, every other media *except* "handheld" will 249 be stripped. This is because how Opera handles CSS for PDAs. 250 """ 251 if len(medialist) == 0: 252 return u'all' 253 else: 254 sep = u',%s' % self.prefs.listItemSpacer 255 return sep.join( 256 (mq.mediaText for mq in medialist))
257
258 - def do_CSSStyleSheet(self, stylesheet):
259 out = [] 260 for rule in stylesheet.cssRules: 261 cssText = rule.cssText 262 if cssText: 263 out.append(cssText) 264 return self._serialize(self.prefs.lineSeparator.join(out))
265
266 - def do_CSSComment(self, rule):
267 """ 268 serializes CSSComment which consists only of commentText 269 """ 270 if self.prefs.keepComments and rule._cssText: 271 return rule._cssText 272 else: 273 return u''
274
275 - def do_CSSCharsetRule(self, rule):
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
287 - def do_CSSImportRule(self, rule):
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'): # comments 318 out.append(part.cssText) 319 return u'%s;' % u''.join(out)
320
321 - def do_CSSNamespaceRule(self, rule):
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'): # comments 342 out.append(part.cssText) 343 return u'%s;' % u''.join(out)
344
345 - def do_CSSMediaRule(self, rule):
346 """ 347 serializes CSSMediaRule 348 349 + CSSComments 350 """ 351 rulesout = [] 352 for r in rule.cssRules: 353 rtext = r.cssText 354 if rtext: 355 # indent each line of cssText 356 rulesout.append(self._indentblock(rtext, self._level + 1)) 357 rulesout.append(self.prefs.lineSeparator) 358 359 if not self.prefs.keepEmptyRules and not u''.join(rulesout).strip() or\ 360 self._noinvalids(rule.media): 361 return u'' 362 363 # if len(rule.cssRules) == 0 and not self.prefs.keepEmptyRules or\ 364 # self._noinvalids(rule.media): 365 # return u'' 366 mediaText = self.do_stylesheets_medialist(rule.media).strip() 367 out = [u'%s %s%s{%s' % (self._getatkeyword(rule, u'@media'), 368 mediaText, 369 self.prefs.paranthesisSpacer, 370 self.prefs.lineSeparator)] 371 out.extend(rulesout) 372 return u'%s%s}' % (u''.join(out), 373 (self._level + 1) * self.prefs.indent)
374
375 - def do_CSSPageRule(self, rule):
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
419 - def do_CSSUnknownRule(self, rule):
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
439 - def do_CSSStyleRule(self, rule):
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
470 - def do_css_SelectorList(self, selectorlist):
471 """ 472 comma-separated list of Selectors 473 """ 474 if len(selectorlist.seq) == 0 or self._noinvalids(selectorlist): 475 return u'' 476 else: 477 out = [] 478 sep = u',%s' % self.prefs.listItemSpacer 479 for part in selectorlist.seq: 480 if hasattr(part, 'cssText'): 481 out.append(part.cssText) 482 # elif u',' == part: 483 # out.append(sep) 484 elif isinstance(part, cssutils.css.Selector): 485 out.append(self.do_css_Selector(part).strip()) 486 else: 487 out.append(part) # ? 488 return sep.join(out)
489
490 - def do_css_Selector(self, selector):
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
510 - def do_css_CSSStyleDeclaration(self, style, separator=None):
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 # find distinct names 524 nnames = set() 525 for x in style.seq: 526 if isinstance(x, cssutils.css.Property): 527 nnames.add(x.normalname) 528 # filter list 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 # CSSComment 543 if self.prefs.keepComments: 544 out.append(part.cssText) 545 out.append(separator) 546 elif isinstance(part, cssutils.css.Property): 547 # PropertySimilarNameList 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 # other? 554 out.append(part) 555 556 if out and out[-1] == separator: 557 del out[-1] 558 559 return u''.join(out)
560
561 - def do_Property(self, property):
562 """ 563 Style declaration of CSSStyleRule 564 565 Property has a seqs attribute which contains seq lists for 566 name, a CSSvalue and a seq list for priority 567 """ 568 out = [] 569 if property.seqs[0] and self._wellformed(property) and\ 570 self._valid(property): 571 nameseq, cssvalue, priorityseq = property.seqs 572 573 #name 574 for part in nameseq: 575 if hasattr(part, 'cssText'): 576 out.append(part.cssText) 577 elif property.name == part: 578 out.append(self._getpropertyname(property, part)) 579 else: 580 out.append(part) 581 582 if out and (not property._mediaQuery or 583 property._mediaQuery and cssvalue.cssText): 584 # MediaQuery may consist of name only 585 out.append(u':') 586 out.append(self.prefs.propertyNameSpacer) 587 588 # value 589 out.append(cssvalue.cssText) 590 591 # priority 592 if out and priorityseq: 593 out.append(u' ') 594 for part in priorityseq: 595 if hasattr(part, 'cssText'): # comments 596 out.append(part.cssText) 597 else: 598 out.append(part) 599 600 return u''.join(out)
601
602 - def do_Property_priority(self, priorityseq):
603 """ 604 a Properties priority "!" S* "important" 605 """ 606 out = [] 607 for part in priorityseq: 608 if hasattr(part, 'cssText'): # comments 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
616 - def do_css_CSSValue(self, cssvalue):
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 # comments or CSSValue if a CSSValueList 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