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