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-09-01 18:43:49 +0200 (Sa, 01 Sep 2007) $' 
 12  __version__ = '$LastChangedRevision: 305 $' 
 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 lineNumbers = False 40 Only used if a complete CSSStyleSheet is serialized. 41 lineSeparator = u'\n' 42 How to end a line. This may be set to e.g. u'' for serializing 43 of CSSStyleDeclarations usable in HTML style attribute. 44 omitLastSemicolon = True 45 If ``True`` omits ; after last property of CSSStyleDeclaration 46 removeInvalid = True 47 Omits invalid rules, MAY CHANGE! 48 """
49 - def __init__(self, indent=u' ', lineSeparator=u'\n'):
50 """ 51 Always use named instead of positional parameters 52 """ 53 self.defaultAtKeyword = True 54 self.defaultPropertyName = True 55 self.indent = indent 56 self.lineSeparator = lineSeparator 57 self.importHrefFormat = None 58 self.keepAllProperties = False 59 self.keepComments = True 60 self.omitLastSemicolon = True 61 self.lineNumbers = False 62 self.removeInvalid = True
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 self._level = 0 # current nesting level
81
82 - def _serialize(self, text):
83 if self.prefs.lineNumbers: 84 pad = len(str(text.count(self.prefs.lineSeparator)+1)) 85 out = [] 86 for i, line in enumerate(text.split(self.prefs.lineSeparator)): 87 out.append((u'%*i: %s') % (pad, i+1, line)) 88 text = self.prefs.lineSeparator.join(out) 89 return text
90
91 - def _noinvalids(self, x):
92 """ 93 returns 94 True if x.valid or prefs.removeInvalid == False 95 else False 96 """ 97 if self.prefs.removeInvalid and \ 98 hasattr(x, 'valid') and not x.valid: 99 return True 100 else: 101 return False
102
103 - def _escapestring(self, s, delim=u'"'):
104 """ 105 escapes delim charaters in string s with \delim 106 """ 107 return s.replace(delim, u'\%s' % delim)
108
109 - def _getatkeyword(self, rule, default):
110 """ 111 used by all @rules to get the keyword used 112 dependent on prefs setting defaultAtKeyword 113 """ 114 if self.prefs.defaultAtKeyword: 115 return default 116 else: 117 return rule.atkeyword
118
119 - def _getpropertyname(self, property, actual):
120 """ 121 used by all styledeclarations to get the propertyname used 122 dependent on prefs setting defaultPropertyName 123 """ 124 if self.prefs.defaultPropertyName and \ 125 not self.prefs.keepAllProperties: 126 return property.normalname 127 else: 128 return actual
129
130 - def _indentblock(self, text, level):
131 """ 132 indent a block like a CSSStyleDeclaration to the given level 133 which may be higher than self._level (e.g. for CSSStyleDeclaration) 134 """ 135 if not self.prefs.lineSeparator: 136 return text 137 return self.prefs.lineSeparator.join( 138 [u'%s%s' % (level * self.prefs.indent, line) 139 for line in text.split(self.prefs.lineSeparator)] 140 )
141
142 - def _do_unknown(self, rule):
143 # TODO: REMOVE??? 144 raise NotImplementedError( 145 "Serializer not implemented for %s" % rule)
146
147 - def do_stylesheets_medialist(self, medialist):
148 """ 149 comma-separated list of media, default is 'all' 150 151 If "all" is in the list, every other media *except* "handheld" will 152 be stripped. This is because how Opera handles CSS for PDAs. 153 """ 154 if len(medialist.seq) == 0: 155 return u'all' 156 else: 157 hasall = bool( 158 [1 for part in medialist.seq if part == u'all']) 159 hashandheld = bool( 160 [1 for part in medialist.seq if part == u'handheld']) 161 doneall = donehandheld = False 162 out = [] 163 for part in medialist.seq: 164 if hasattr(part, 'cssText'): # comments 165 out.append(part.cssText) 166 elif u',' == part and not hasall: 167 out.append(u', ') 168 elif not hasall or ( 169 hasall and hashandheld and \ 170 part in (u'handheld', u'all')): 171 if hasall and hashandheld and ( 172 (donehandheld and part == u'all') or\ 173 (doneall and part == u'handheld') 174 ): 175 out.append(u', ') 176 if part == u'all': 177 doneall = True 178 else: 179 donehandheld = True 180 out.append(part) 181 if out == []: 182 out = [u'all'] 183 return u''.join(out)
184
185 - def do_CSSStyleSheet(self, stylesheet):
186 out = [] 187 for rule in stylesheet.cssRules: 188 cssText = rule.cssText 189 if cssText: 190 out.append(cssText) 191 return self._serialize(self.prefs.lineSeparator.join(out))
192
193 - def do_CSSComment(self, rule):
194 """ 195 serializes CSSComment which consists only of commentText 196 """ 197 if self.prefs.keepComments and rule._cssText: 198 return rule._cssText 199 else: 200 return u''
201
202 - def do_CSSCharsetRule(self, rule):
203 """ 204 serializes CSSCharsetRule 205 encoding: string 206 207 always @charset "encoding"; 208 no comments or other things allowed! 209 """ 210 if not rule.encoding or self._noinvalids(rule): 211 return u'' 212 return u'@charset "%s";' % self._escapestring(rule.encoding)
213
214 - def do_CSSImportRule(self, rule):
215 """ 216 serializes CSSImportRule 217 218 href 219 string 220 hreftype 221 'uri' or 'string' 222 media 223 cssutils.stylesheets.medialist.MediaList 224 225 + CSSComments 226 """ 227 if not rule.href or self._noinvalids(rule): 228 return u'' 229 out = [u'%s ' % self._getatkeyword(rule, u'@import')] 230 for part in rule.seq: 231 if rule.href == part: 232 if self.prefs.importHrefFormat == 'uri': 233 out.append(u'url(%s)' % part) 234 elif self.prefs.importHrefFormat == 'string' or \ 235 rule.hreftype == 'string': 236 out.append(u'"%s"' % self._escapestring(part)) 237 else: 238 out.append(u'url(%s)' % part) 239 elif isinstance( 240 part, cssutils.stylesheets.medialist.MediaList): 241 mediaText = self.do_stylesheets_medialist(part).strip() 242 if mediaText and not mediaText == u'all': 243 out.append(u' %s' % mediaText) 244 elif hasattr(part, 'cssText'): # comments 245 out.append(part.cssText) 246 return u'%s;' % u''.join(out)
247
248 - def do_CSSNamespaceRule(self, rule):
249 """ 250 serializes CSSNamespaceRule 251 252 uri 253 string 254 prefix 255 string 256 257 + CSSComments 258 """ 259 if not rule.uri or self._noinvalids(rule): 260 return u'' 261 262 out = [u'%s' % self._getatkeyword(rule, u'@namespace')] 263 for part in rule.seq: 264 if rule.prefix == part and part != u'': 265 out.append(u' %s' % part) 266 elif rule.uri == part: 267 out.append(u' "%s"' % self._escapestring(part)) 268 elif hasattr(part, 'cssText'): # comments 269 out.append(part.cssText) 270 return u'%s;' % u''.join(out)
271
272 - def do_CSSMediaRule(self, rule):
273 """ 274 serializes CSSMediaRule 275 276 + CSSComments 277 """ 278 if not rule.cssRules or self._noinvalids(rule.media): 279 return u'' 280 mediaText = self.do_stylesheets_medialist(rule.media).strip() 281 out = [u'%s %s {%s' % (self._getatkeyword(rule, u'@media'), 282 mediaText, self.prefs.lineSeparator)] 283 for r in rule.cssRules: 284 rtext = r.cssText 285 if rtext: 286 # indent each line of cssText 287 out.append(self._indentblock(rtext, self._level + 1)) 288 out.append(self.prefs.lineSeparator) 289 return u'%s%s}' % (u''.join(out), (self._level + 1) * self.prefs.indent)
290
291 - def do_CSSPageRule(self, rule):
292 """ 293 serializes CSSPageRule 294 295 selectorText 296 string 297 style 298 CSSStyleDeclaration 299 300 + CSSComments 301 """ 302 self._level += 1 303 try: 304 styleText = self.do_css_CSSStyleDeclaration(rule.style) 305 finally: 306 self._level -= 1 307 308 if not styleText or self._noinvalids(rule): 309 return u'' 310 311 return u'%s%s {%s%s%s%s}' % ( 312 self._getatkeyword(rule, u'@page'), 313 self.do_pageselector(rule.seq), 314 self.prefs.lineSeparator, 315 self._indentblock(styleText, self._level + 1), 316 self.prefs.lineSeparator, 317 (self._level + 1) * self.prefs.indent 318 )
319
320 - def do_pageselector(self, seq):
321 """ 322 a selector of a CSSPageRule including comments 323 """ 324 if len(seq) == 0 or self._noinvalids(seq): 325 return u'' 326 else: 327 out = [] 328 for part in seq: 329 if hasattr(part, 'cssText'): 330 out.append(part.cssText) 331 else: 332 out.append(part) 333 return u' %s' % u''.join(out)
334
335 - def do_CSSUnknownRule(self, rule):
336 """ 337 serializes CSSUnknownRule 338 anything until ";" or "{...}" 339 + CSSComments 340 """ 341 if rule.atkeyword and not self._noinvalids(rule): 342 out = [u'@%s' % rule.atkeyword] 343 for part in rule.seq: 344 if isinstance(part, cssutils.css.csscomment.CSSComment): 345 if self.prefs.keepComments: 346 out.append(part.cssText) 347 else: 348 out.append(part) 349 if not (out[-1].endswith(u';') or out[-1].endswith(u'}')): 350 out.append(u';') 351 return u''.join(out) 352 else: 353 return u''
354
355 - def do_CSSStyleRule(self, rule):
356 """ 357 serializes CSSStyleRule 358 359 selectorList 360 style 361 362 + CSSComments 363 """ 364 selectorText = self.do_css_SelectorList(rule.selectorList) 365 if not selectorText or self._noinvalids(rule): 366 return u'' 367 self._level += 1 368 styleText = u'' 369 try: 370 styleText = self.do_css_CSSStyleDeclaration(rule.style) 371 finally: 372 self._level -= 1 373 374 if not styleText: 375 return u'%s {}' % ( 376 selectorText, 377 ) 378 else: 379 return u'%s {%s%s%s%s}' % ( 380 selectorText, 381 self.prefs.lineSeparator, 382 self._indentblock(styleText, self._level + 1), 383 self.prefs.lineSeparator, 384 (self._level + 1) * self.prefs.indent, 385 )
386
387 - def do_css_SelectorList(self, selectorlist):
388 """ 389 comma-separated list of Selectors 390 """ 391 if len(selectorlist.seq) == 0 or self._noinvalids(selectorlist): 392 return u'' 393 else: 394 out = [] 395 for part in selectorlist.seq: 396 if hasattr(part, 'cssText'): 397 out.append(part.cssText) 398 elif u',' == part: 399 out.append(u', ') 400 elif isinstance(part, cssutils.css.cssstylerule.Selector): 401 out.append(self.do_css_Selector(part).strip()) 402 else: 403 out.append(part) # ? 404 return u''.join(out)
405
406 - def do_css_Selector(self, selector):
407 """ 408 a single selector including comments 409 410 TODO: style combinators like + > 411 """ 412 if len(selector.seq) == 0 or self._noinvalids(selector): 413 return u'' 414 else: 415 out = [] 416 for part in selector.seq: 417 if hasattr(part, 'cssText'): 418 out.append(part.cssText) 419 else: 420 if type(part) == dict: 421 out.append(part['value']) 422 else: 423 out.append(part) 424 return u''.join(out)
425
426 - def do_css_CSSStyleDeclaration(self, style, separator=None):
427 """ 428 Style declaration of CSSStyleRule 429 """ 430 if len(style.seq) == 0 or self._noinvalids(style): 431 return u'' 432 else: 433 if separator is None: 434 separator = self.prefs.lineSeparator 435 436 out = [] 437 for (i, part) in enumerate(style.seq): 438 # CSSComment 439 if isinstance(part, 440 cssutils.css.csscomment.CSSComment): 441 if self.prefs.keepComments: 442 out.append(self.do_CSSComment(part)) 443 out.append(separator) 444 445 # PropertySimilarNameList 446 elif isinstance(part, 447 cssutils.css.cssstyledeclaration.SameNamePropertyList): 448 # only last valid Property 449 if not self.prefs.keepAllProperties: 450 _index = part._currentIndex() 451 out.append( 452 self.do_css_Property(part[_index], 453 self.prefs.omitLastSemicolon and 454 i==len(style.seq)-1)) 455 out.append(separator) 456 else: 457 # or all Properties 458 for (j, p) in enumerate(part): 459 out.append( 460 self.do_css_Property(p, 461 self.prefs.omitLastSemicolon and 462 i==len(style.seq)-1 and 463 j==len(part)-1)) 464 out.append(separator) 465 # other? 466 else: 467 out.append(part) 468 469 if out and out[-1] == separator: 470 del out[-1] 471 472 return u''.join(out)
473
474 - def do_css_Property(self, property, omitSemicolon=False):
475 """ 476 Style declaration of CSSStyleRule 477 478 Property has a seqs attribute which contains seq lists for 479 name, a CSSvalue and a seq list for priority 480 """ 481 if not property.seqs[0] or self._noinvalids(property): 482 return u'' 483 else: 484 out = [] 485 nameseq, cssvalue, priorityseq = property.seqs 486 #name 487 for part in nameseq: 488 if hasattr(part, 'cssText'): 489 out.append(part.cssText) 490 elif property.name == part: 491 out.append(self._getpropertyname(property, part)) 492 else: 493 out.append(part) 494 if out: 495 out.append(u': ') 496 # value 497 out.append(self.do_css_CSSvalue(cssvalue)) 498 # priority 499 if out and priorityseq: 500 out.append(u' ') 501 for part in priorityseq: 502 if hasattr(part, 'cssText'): # comments 503 out.append(part.cssText) 504 else: 505 out.append(part) 506 return u'%s%s' % (u''.join(out), (";", "")[bool(omitSemicolon)])
507
508 - def do_css_CSSvalue(self, cssvalue):
509 """ 510 serializes a CSSValue 511 """ 512 if not cssvalue: 513 return u'' 514 else: 515 out = [] 516 for part in cssvalue.seq: 517 if hasattr(part, 'cssText'): 518 # comments or CSSValue if a CSSValueList 519 out.append(part.cssText) 520 else: 521 out.append(part) 522 return (u''.join(out)).strip()
523