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-06-17 19:49:45 +0200 (So, 17 Jun 2007) $' 
 12  __version__ = '0.9.2a2 $LastChangedRevision: 89 $' 
 13   
 14  import cssutils 
 15   
16 -class Preferences(object):
17 """ 18 controls output of CSSSerializer 19 20 defaultAtKeyword = True 21 Should the literal @keyword from src CSS be used or the default 22 form, e.g. if ``True``: ``@import`` else: ``@i\mport`` 23 defaultPropertyName = True 24 Should the normalized propertyname be used or the one given in 25 the src file, e.g. if ``True``: ``color`` else: ``c\olor`` 26 27 Only used if ``keepAllProperties==False``. 28 29 importHrefFormat = None 30 Uses hreftype if ``None`` or explicit 'string' or 'uri' 31 indent = 4 * ' ' 32 Indentation of e.g Properties inside a CSSStyleDeclaration 33 keepAllProperties = False 34 If ``True`` all properties set in the original CSSStylesheet 35 are kept meaning even properties set twice with the exact same 36 same name are kept! 37 keepComments = True 38 If ``False`` removes all CSSComments 39 omitLastSemicolon = True 40 If ``True`` omits ; after last property of CSSStyleDeclaration 41 lineNumbers = False 42 Only used if a complete CSSStyleSheet is serialized. 43 removeInvalid = True 44 Omits invalid rules, MAY CHANGE! 45 setBOM = False 46 NOT USED YET 47 """
48 - def __init__(self, indent=u' '):
49 """ 50 Always use named instead of positional parameters! 51 """ 52 self.defaultAtKeyword = True 53 self.defaultPropertyName = True 54 self.indent = indent 55 self.importHrefFormat = None 56 self.keepAllProperties = False 57 self.keepComments = True 58 self.omitLastSemicolon = True 59 self.lineNumbers = False 60 self.removeInvalid = True 61 self.setBOM = False # TODO
62 63
64 -class CSSSerializer(object):
65 """ 66 Methods to serialize a CSSStylesheet and its parts 67 68 To use your own serializing method the easiest is to subclass CSS 69 Serializer and overwrite the methods you like to customize. 70 """
71 - def __init__(self, prefs=None):
72 """ 73 prefs 74 instance of Preferences 75 """ 76 if not prefs: 77 prefs = Preferences() 78 self.prefs = prefs 79 self.ttypes = cssutils.token.Token 80 81 self._map = { 82 cssutils.css.cssrule.CSSRule.COMMENT: 83 self.do_CSSComment, 84 cssutils.css.cssrule.CSSRule.CHARSET_RULE: 85 self.do_CSSCharsetRule, 86 cssutils.css.cssrule.CSSRule.IMPORT_RULE: 87 self.do_CSSImportRule, 88 cssutils.css.cssrule.CSSRule.MEDIA_RULE: 89 self.do_CSSMediaRule, 90 cssutils.css.cssrule.CSSRule.NAMESPACE_RULE: 91 self.do_CSSNamespaceRule, 92 cssutils.css.cssrule.CSSRule.PAGE_RULE: 93 self.do_CSSPageRule, 94 cssutils.css.cssrule.CSSRule.STYLE_RULE: 95 self.do_CSSStyleRule, 96 cssutils.css.cssrule.CSSRule.UNKNOWN_RULE: 97 self.do_CSSUnknownRule 98 }
99 100
101 - def _serialize(self, text):
102 if self.prefs.lineNumbers: 103 pad = text.count(u'\n') / 10 + 1 104 out = [] 105 for i, line in enumerate(text.split(u'\n')): 106 out.append((u'%'+str(pad)+'i: %s') % (i+1, line)) 107 text = u'\n'.join(out) 108 return text
109 110
111 - def _noinvalids(self, x):
112 """ 113 returns 114 True if x.valid or prefs.removeInvalid == False 115 else False 116 """ 117 if self.prefs.removeInvalid and \ 118 hasattr(x, 'valid') and not x.valid: 119 return True 120 else: 121 return False
122 123
124 - def _escapestring(self, s, delim=u'"'):
125 """ 126 escapes delim charaters in string s with \delim 127 """ 128 return s.replace(delim, u'\%s' % delim)
129
130 - def _getatkeyword(self, rule, default):
131 """ 132 used by all @rules to get the keyword used 133 dependent on prefs setting defaultAtKeyword 134 """ 135 if self.prefs.defaultAtKeyword: 136 return default 137 else: 138 return rule.atkeyword
139
140 - def _getpropertyname(self, property, actual):
141 """ 142 used by all styledeclarations to get the propertyname used 143 dependent on prefs setting defaultPropertyName 144 """ 145 if self.prefs.defaultPropertyName and \ 146 not self.prefs.keepAllProperties: 147 return property.normalname 148 else: 149 return actual
150 151
152 - def _do_unknown(self, rule):
153 raise NotImplementedError( 154 "Serializer not implemented for %s" % rule)
155 156
157 - def do_stylesheets_medialist(self, medialist):
158 """ 159 comma-separated list of media, default is 'all' 160 161 If "all" is in the list, every other media *except* "handheld" will 162 be stripped. This is because how Opera handles CSS for PDAs. 163 """ 164 if len(medialist.seq) == 0: 165 return u'all' 166 else: 167 hasall = bool( 168 [1 for part in medialist.seq if part == u'all']) 169 hashandheld = bool( 170 [1 for part in medialist.seq if part == u'handheld']) 171 doneall = donehandheld = False 172 out = [] 173 for part in medialist.seq: 174 if hasattr(part, 'cssText'): # comments 175 out.append(part.cssText) 176 elif u',' == part and not hasall: 177 out.append(u', ') 178 elif not hasall or ( 179 hasall and hashandheld and \ 180 part in (u'handheld', u'all')): 181 if hasall and hashandheld and ( 182 (donehandheld and part == u'all') or\ 183 (doneall and part == u'handheld') 184 ): 185 out.append(u', ') 186 if part == u'all': 187 doneall = True 188 else: 189 donehandheld = True 190 out.append(part) 191 if out == []: 192 out = [u'all'] 193 return u''.join(out)
194 195
196 - def do_stylesheet(self, stylesheet):
197 out = [] 198 for rule in stylesheet.cssRules: 199 cssText = self._map.get(rule.type, self._do_unknown)(rule) 200 if cssText: 201 out.append(cssText) 202 return self._serialize(u'\n'.join(out))
203 204
205 - def do_CSSComment(self, rule):
206 """ 207 serializes CSSComment which consists only of commentText 208 """ 209 if self.prefs.keepComments and rule._cssText: 210 return rule._cssText 211 else: 212 return u''
213 214
215 - def do_CSSCharsetRule(self, rule):
216 """ 217 serializes CSSCharsetRule 218 encoding: string 219 220 always @charset "encoding"; 221 no comments or other things allowed! 222 """ 223 if not rule.encoding or self._noinvalids(rule): 224 return u'' 225 return u'@charset "%s";' % self._escapestring(rule.encoding)
226 227
228 - def do_CSSImportRule(self, rule):
229 """ 230 serializes CSSImportRule 231 232 href 233 string 234 hreftype 235 'uri' or 'string' 236 media 237 cssutils.stylesheets.medialist.MediaList 238 239 + CSSComments 240 """ 241 if not rule.href or self._noinvalids(rule): 242 return u'' 243 out = [u'%s ' % self._getatkeyword(rule, u'@import')] 244 for part in rule.seq: 245 if rule.href == part: 246 if self.prefs.importHrefFormat == 'uri': 247 out.append('url(%s)' % part) 248 elif self.prefs.importHrefFormat == 'string' or \ 249 rule.hreftype == 'string': 250 out.append('"%s"' % self._escapestring(part)) 251 else: 252 out.append('url(%s)' % part) 253 elif isinstance( 254 part, cssutils.stylesheets.medialist.MediaList): 255 mediaText = self.do_stylesheets_medialist(part).strip() 256 if mediaText and not mediaText == u'all': 257 out.append(u' %s' % mediaText) 258 elif hasattr(part, 'cssText'): # comments 259 out.append(part.cssText) 260 out.append(';') 261 return u''.join(out)
262 263
264 - def do_CSSNamespaceRule(self, rule):
265 """ 266 serializes CSSNamespaceRule 267 268 uri 269 string 270 prefix 271 string 272 273 + CSSComments 274 """ 275 if not rule.uri or self._noinvalids(rule): 276 return u'' 277 278 out = [u'%s' % self._getatkeyword(rule, u'@namespace')] 279 for part in rule.seq: 280 if rule.prefix == part and part != u'': 281 out.append(' %s' % part) 282 elif rule.uri == part: 283 out.append(' "%s"' % self._escapestring(part)) 284 elif hasattr(part, 'cssText'): # comments 285 out.append(part.cssText) 286 out.append(';') 287 return u''.join(out)
288 289
290 - def do_CSSMediaRule(self, rule):
291 """ 292 serializes CSSMediaRule 293 294 + CSSComments 295 """ 296 if not rule.cssRules or self._noinvalids(rule.media): 297 return u'' 298 mediaText = self.do_stylesheets_medialist(rule.media).strip() 299 out = [u'%s %s {\n' % ( 300 self._getatkeyword(rule, u'@media'), mediaText)] 301 for r in rule.cssRules: 302 rtext = r.cssText 303 if rtext: 304 rtext = '\n'.join( 305 [u'%s%s' % (self.prefs.indent, line) 306 for line in rtext.split('\n')]) 307 out.append(rtext + u'\n') 308 out.append('%s}' % self.prefs.indent) 309 return u''.join(out)
310 311
312 - def do_CSSPageRule(self, rule):
313 """ 314 serializes CSSPageRule 315 316 selectorText 317 string 318 style 319 CSSStyleDeclaration 320 321 + CSSComments 322 """ 323 styleText = self.do_css_CSSStyleDeclaration(rule.style) 324 if not styleText or self._noinvalids(rule): 325 return u'' 326 327 sel = self.do_pageselector(rule.seq) 328 return u'%s%s {%s}' % ( 329 self._getatkeyword(rule, u'@page'), 330 sel, 331 self.do_css_CSSStyleDeclaration(rule.style))
332 333
334 - def do_pageselector(self, seq):
335 """ 336 a selector of a CSSPageRule including comments 337 """ 338 if len(seq) == 0 or self._noinvalids(seq): 339 return u'' 340 else: 341 out = [] 342 for part in seq: 343 if isinstance(part, cssutils.css.csscomment.CSSComment): 344 out.append(part.cssText) 345 else: 346 out.append(part) 347 return u' %s' % u''.join(out)
348 349
350 - def do_CSSUnknownRule(self, rule):
351 """ 352 serializes CSSUnknownRule 353 anything until ";" or "{...}" 354 + CSSComments 355 """ 356 if rule.atkeyword and not self._noinvalids(rule): 357 out = [u'@%s' % rule.atkeyword] 358 for part in rule.seq: 359 if isinstance(part, cssutils.css.csscomment.CSSComment): 360 if self.prefs.keepComments: 361 out.append(part.cssText) 362 else: 363 out.append(part) 364 if not (out[-1].endswith(';') or out[-1].endswith('}')): 365 out.append(u';') 366 return u''.join(out) 367 else: 368 return u''
369 370
371 - def do_CSSStyleRule(self, rule):
372 """ 373 serializes CSSStyleRule 374 375 selectorList 376 style 377 378 + CSSComments 379 """ 380 selectorText = self.do_css_SelectorList(rule.selectorList) 381 if not selectorText or self._noinvalids(rule): 382 return u'' 383 styleText = self.do_css_CSSStyleDeclaration(rule.style) 384 if not styleText: 385 styleText = u'' 386 387 return u'%s {%s}' % (selectorText, styleText)
388 389
390 - def do_css_SelectorList(self, selectorlist):
391 """ 392 comma-separated list of Selectors 393 """ 394 if len(selectorlist.seq) == 0 or self._noinvalids(selectorlist): 395 return u'' 396 else: 397 out = [] 398 for part in selectorlist.seq: 399 if isinstance(part, cssutils.css.csscomment.CSSComment): 400 out.append(part.cssText) 401 elif u',' == part: 402 out.append(u', ') 403 elif isinstance(part, cssutils.css.cssstylerule.Selector): 404 out.append(self.do_css_Selector(part).strip()) 405 else: 406 out.append(part) # ? 407 return u''.join(out)
408 409
410 - def do_css_Selector(self, selector):
411 """ 412 a single selector including comments 413 414 TODO: style combinators like + > 415 """ 416 if len(selector.seq) == 0 or self._noinvalids(selector): 417 return u'' 418 else: 419 out = [] 420 for part in selector.seq: 421 if isinstance(part, cssutils.css.csscomment.CSSComment): 422 out.append(part.cssText) 423 else: 424 if type(part) == dict: 425 out.append(part['value']) 426 else: 427 out.append(part) 428 return u''.join(out)
429 430
431 - def do_css_CSSStyleDeclaration(self, style):
432 """ 433 Style declaration of CSSStyleRule 434 """ 435 if len(style.seq) == 0 or self._noinvalids(style): 436 return u'' 437 else: 438 out = ['\n'] 439 done1 = False # if no content empty done1 440 lastwasprop = False 441 for part in style.seq: 442 # CSSComment 443 if isinstance(part, 444 cssutils.css.csscomment.CSSComment): 445 if self.prefs.keepComments: 446 done1 = True 447 if lastwasprop: 448 out.append(u';\n') 449 out.append(self.prefs.indent) 450 out.append(part.cssText) 451 out.append(u'\n') 452 lastwasprop = False 453 454 # PropertySimilarNameList 455 elif isinstance(part, 456 cssutils.css.cssstyledeclaration.SameNamePropertyList): 457 # only last valid Property 458 if not self.prefs.keepAllProperties: 459 _index = part._currentIndex() 460 propertytext = self.do_css_Property(part[_index]) 461 else: 462 # or all Properties 463 o = [] 464 for p in part: 465 pt = self.do_css_Property(p) 466 if pt: 467 o.append(pt) 468 o.append(u';\n') 469 o.append(self.prefs.indent) 470 propertytext = u''.join(o[:-2]) # omit last \n and indent 471 472 if propertytext: 473 done1 = True 474 if lastwasprop: 475 out.append(u';\n') 476 out.append(self.prefs.indent) 477 out.append(propertytext) 478 lastwasprop = True 479 480 # other? 481 else: 482 done1 = True 483 if lastwasprop: 484 out.append(u';\n') 485 out.append(part) 486 lastwasprop = False 487 if not done1: 488 return u'' 489 if lastwasprop: 490 if self.prefs.omitLastSemicolon: 491 out.append(u'\n') 492 else: 493 out.append(u';\n') 494 out.append(self.prefs.indent) 495 return u''.join(out)
496 497
498 - def do_css_Property(self, property):
499 """ 500 Style declaration of CSSStyleRule 501 502 Property has a seqs attribute which contains seq lists for 503 name, a CSSvalue and a seq list for priority 504 """ 505 if not property.seqs[0] or self._noinvalids(property): 506 return u'' 507 else: 508 out = [] 509 nameseq, cssvalue, priorityseq = property.seqs 510 511 for part in nameseq: 512 if isinstance(part, cssutils.css.csscomment.CSSComment): 513 out.append(part.cssText) 514 elif property.name == part: 515 out.append(self._getpropertyname(property, part)) 516 else: 517 out.append(part) 518 if out: 519 out.append(u': ') 520 v = self.do_css_CSSvalue(cssvalue) 521 if v: 522 out.append(v) 523 524 if out and priorityseq: 525 out.append(u' ') 526 for part in priorityseq: 527 if hasattr(part, 'cssText'): # comments 528 out.append(part.cssText) 529 else: 530 out.append(part) 531 532 return u''.join(out)
533 534
535 - def do_css_CSSvalue(self, cssvalue):
536 """ 537 serializes a CSSValue 538 """ 539 if not cssvalue: 540 return u'' 541 else: 542 out = [] 543 for part in cssvalue.seq: 544 if hasattr(part, 'cssText'): # comments 545 out.append(part.cssText) 546 else: 547 out.append(part) 548 return (u''.join(out)).strip()
549