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  """
      6  __all__ = ['CSSSerializer']
      7  __docformat__ = 'restructuredtext'
      8  __author__ = '$LastChangedBy: cthedot $'
      9  __date__ = '$LastChangedDate: 2008-01-12 22:36:38 +0100 (Sa, 12 Jan 2008) $'
     10  __version__ = '$LastChangedRevision: 833 $'
     11
     12  import codecs
     13  import re
     14  import cssutils
     15  import util
     16
     17 -def _escapecss(e):
     18      """
     19      Escapes characters not allowed in the current encoding the CSS way
     20      with a backslash followed by a uppercase hex code point
     21
     22      E.g. the german umlaut 'ä' is escaped as \E4
     23      """
     24      s = e.args[1][e.start:e.end]
     25      return u''.join([ur'\%s ' % str(hex(ord(x)))[2:] # remove 0x from hex
     26                       .upper() for x in s]), e.end
     27
     28  codecs.register_error('escapecss', _escapecss)
     29
     30
     31 -class Preferences(object):
     32      """
     33      controls output of CSSSerializer
     34
     35      defaultAtKeyword = True
     36          Should the literal @keyword from src CSS be used or the default
     37          form, e.g. if ``True``: ``@import`` else: ``@i\mport``
     38      defaultPropertyName = True
     39          Should the normalized propertyname be used or the one given in
     40          the src file, e.g. if ``True``: ``color`` else: ``c\olor``
     41
     42          Only used if ``keepAllProperties==False``.
     43
     44      importHrefFormat = None
     45          Uses hreftype if ``None`` or explicit ``'string'`` or ``'uri'``
     46      indent = 4 * ' '
     47          Indentation of e.g Properties inside a CSSStyleDeclaration
     48      indentSpecificities = False
     49          Indent rules with subset of Selectors and higher Specitivity
     50
     51      keepAllProperties = True
     52          If ``True`` all properties set in the original CSSStylesheet
     53          are kept meaning even properties set twice with the exact same
     54          same name are kept!
     55
     56      defaultAtKeyword=%r,
     57      defaultPropertyName=%r,
     58      importHrefFormat=%r,
     59      indent=%r,
     60      keepAllProperties=%r,
     61      keepComments=%r,
     62      keepEmptyRules=%r,
     63      lineNumbers=%r,
     64      lineSeparator=%r,
     65      listItemSpacer=%r,
     66      omitLastSemicolon=%r,
     67      paranthesisSpacer=%r,
     68      propertyNameSpacer=%r,
     69      validOnly=%r,
     70      wellformedOnly=%r,
     71
     72
     73      keepComments = True
     74          If ``False`` removes all CSSComments
     75      keepEmptyRules = False
     76          defines if empty rules like e.g. ``a {}`` are kept in the resulting
     77          serialized sheet
     78
     79      lineNumbers = False
     80          Only used if a complete CSSStyleSheet is serialized.
     81      lineSeparator = u'\\n'
     82          How to end a line. This may be set to e.g. u'' for serializing of
     83          CSSStyleDeclarations usable in HTML style attribute.
     84      listItemSpacer = u' '
     85          string which is used in ``css.SelectorList``, ``css.CSSValue`` and
     86          ``stylesheets.MediaList`` after the comma
     87      omitLastSemicolon = True
     88          If ``True`` omits ; after last property of CSSStyleDeclaration
     89      paranthesisSpacer = u' '
     90          string which is used before an opening paranthesis like in a
     91          ``css.CSSMediaRule`` or ``css.CSSStyleRule``
     92      propertyNameSpacer = u' '
     93          string which is used after a Property name colon
     94
     95      validOnly = False (**not anywhere used yet**)
     96          if True only valid (Properties or Rules) are kept
     97
     98          A Property is valid if it is a known Property with a valid value.
     99          Currently CSS 2.1 values as defined in cssproperties.py would be
    100          valid.
    101
    102      wellformedOnly = True (**not anywhere used yet**)
    103          only wellformed properties and rules are kept
    104
    105      **DEPRECATED**: removeInvalid = True
    106          Omits invalid rules, replaced by ``validOnly`` which will be used
    107          more cases
    108
    109      """
    110 -    def __init__(self, **initials):
    111          """
    112          Always use named instead of positional parameters
    113          """
    114          self.useDefaults()
    115
    116          for key, value in initials.items():
    117              if value:
    118                  self.__setattr__(key, value)
    119
    120 -    def useDefaults(self):
    121          "reset all preference options to the default value"
    122          self.defaultAtKeyword = True
    123          self.defaultPropertyName = True
    124          self.importHrefFormat = None
    125          self.indent = 4 * u' '
    126          self.indentSpecificities = False
    127          self.keepAllProperties = True
    128          self.keepComments = True
    129          self.keepEmptyRules = False
    130          self.lineNumbers = False
    131          self.lineSeparator = u'\n'
    132          self.listItemSpacer = u' '
    133          self.omitLastSemicolon = True
    134          self.paranthesisSpacer = u' '
    135          self.propertyNameSpacer = u' '
    136          self.validOnly = False
    137          self.wellformedOnly = True
    138          # DEPRECATED
    139          self.removeInvalid = True
    140
    141 -    def useMinified(self):
    142          """
    143          sets options to achive a minified stylesheet
    144
    145          you may want to set preferences with this convinience method
    146          and set settings you want adjusted afterwards
    147          """
    148          self.importHrefFormat = 'string'
    149          self.indent = u''
    150          self.keepComments = False
    151          self.keepEmptyRules = False
    152          self.lineNumbers = False
    153          self.lineSeparator = u''
    154          self.listItemSpacer = u''
    155          self.omitLastSemicolon = True
    156          self.paranthesisSpacer = u''
    157          self.propertyNameSpacer = u''
    158          self.validOnly = False
    159          self.wellformedOnly = True
    160
    161 -    def __repr__(self):
    162          return u"cssutils.css.%s(%s)" % (self.__class__.__name__,
    163              u', '.join(['\n    %s=%r' % (p, self.__getattribute__(p)) for p in self.__dict__]
    164                  ))
    165
    166 -    def __str__(self):
    167          return u"<cssutils.css.%s object %s at 0x%x" % (self.__class__.__name__,
    168              u' '.join(['%s=%r' % (p, self.__getattribute__(p)) for p in self.__dict__]
    169                  ),
    170                  id(self))
    171
    172
    173 -class CSSSerializer(object):
    174      """
    175      Methods to serialize a CSSStylesheet and its parts
    176
    177      To use your own serializing method the easiest is to subclass CSS
    178      Serializer and overwrite the methods you like to customize.
    179      """
    180      __notinurimatcher = re.compile(ur'''.*?[\)\s\;]''', re.U).match
    181      # chars not in URI without quotes around
    182
    183 -    def __init__(self, prefs=None):
    184          """
    185          prefs
    186              instance of Preferences
    187          """
    188          if not prefs:
    189              prefs = Preferences()
    190          self.prefs = prefs
    191          self._level = 0 # current nesting level
    192
    193          self._selectors = [] # holds SelectorList
    194          self._selectorlevel = 0 # current specificity nesting level
    195
    196 -    def _serialize(self, text):
    197          if self.prefs.lineNumbers:
    198              pad = len(str(text.count(self.prefs.lineSeparator)+1))
    199              out = []
    200              for i, line in enumerate(text.split(self.prefs.lineSeparator)):
    201                  out.append((u'%*i: %s') % (pad, i+1, line))
    202              text = self.prefs.lineSeparator.join(out)
    203          return text
    204
    205 -    def _noinvalids(self, x):
    206          # DEPRECATED: REMOVE!
    207          if self.prefs.removeInvalid and \
    208             hasattr(x, 'valid') and not x.valid:
    209              return True
    210          else:
    211              return False
    212
    213 -    def _valid(self, x):
    214          """
    215          checks if valid items only and if yes it item is valid
    216          """
    217          return not self.prefs.validOnly or (self.prefs.validOnly and
    218                                              hasattr(x, 'valid') and
    219                                              x.valid)
    220
    221 -    def _wellformed(self, x):
    222          """
    223          checks if wellformed items only and if yes it item is wellformed
    224          """
    225          return self.prefs.wellformedOnly and hasattr(x, 'wellformed') and\
    226                 x.wellformed
    227
    228 -    def _escapestring(self, s, delim=u'"'):
    229          """
    230          escapes delim charaters in string s with \delim
    231          s might not have " or ' around it!
    232
    233          escape line breaks \n \r and \f
    234          """
    235          # \n = 0xa, \r = 0xd, \f = 0xc
    236          s = s.replace('\n', '\\a ').replace(
    237                        '\r', '\\d ').replace(
    238                        '\f', '\\c ')
    239          return s.replace(delim, u'\\%s' % delim)
    240
    241 -    def _escapeSTRINGtype(self, s):
    242          """
    243          escapes unescaped ", ' or \n in s if not escaped already
    244          s always has  "..." or '...' around
    245          """
    246          r = s[0]
    247          out = [r]
    248          for c in s[1:-1]:
    249              if c == '\n': # = 0xa
    250                  out.append(u'\\a ')
    251                  continue
    252              elif c == '\r': # = 0xd
    253                  out.append(u'\\d ')
    254                  continue
    255              elif c == '\f': # = 0xc
    256                  out.append(u'\\c ')
    257                  continue
    258              elif c == r and out[-1] != u'\\':
    259                  out.append(u'\\') # + c
    260              out.append(c)
    261          out.append(r)
    262          s = u''.join(out)
    263          return s
    264
    265 -    def _getatkeyword(self, rule, default):
    266          """
    267          used by all @rules to get the keyword used
    268          dependent on prefs setting defaultAtKeyword
    269          """
    270          if self.prefs.defaultAtKeyword:
    271              return default
    272          else:
    273              return rule.atkeyword
    274
    275 -    def _getpropertyname(self, property, actual):
    276          """
    277          used by all styledeclarations to get the propertyname used
    278          dependent on prefs setting defaultPropertyName
    279          """
    280          if self.prefs.defaultPropertyName and \
    281             not self.prefs.keepAllProperties:
    282              return property.name
    283          else:
    284              return actual
    285
    286 -    def _indentblock(self, text, level):
    287          """
    288          indent a block like a CSSStyleDeclaration to the given level
    289          which may be higher than self._level (e.g. for CSSStyleDeclaration)
    290          """
    291          if not self.prefs.lineSeparator:
    292              return text
    293          return self.prefs.lineSeparator.join(
    294              [u'%s%s' % (level * self.prefs.indent, line)
    295                  for line in text.split(self.prefs.lineSeparator)]
    296          )
    297
    298 -    def _uri(self, uri):
    299          """
    300          returns uri enclosed in " if necessary
    301          """
    302          if CSSSerializer.__notinurimatcher(uri):
    303              return '"%s"' % self._escapestring(uri, '"')
    304          else:
    305              return uri
    306
    307 -    def do_stylesheets_mediaquery(self, mediaquery):
    308          """
    309          a single media used in medialist
    310          """
    311          if len(mediaquery.seq) == 0:
    312              return u''
    313          else:
    314              out = []
    315              for part in mediaquery.seq:
    316                  if isinstance(part, cssutils.css.Property): # Property
    317                      out.append(u'(%s)' % part.cssText)
    318                  elif hasattr(part, 'cssText'): # comments
    319                      out.append(part.cssText)
    320                  else:
    321                      # TODO: media queries!
    322                      out.append(part)
    323              return u' '.join(out)
    324
    325 -    def do_stylesheets_medialist(self, medialist):
    326          """
    327          comma-separated list of media, default is 'all'
    328
    329          If "all" is in the list, every other media *except* "handheld" will
    330          be stripped. This is because how Opera handles CSS for PDAs.
    331          """
    332          if len(medialist) == 0:
    333              return u'all'
    334          else:
    335              sep = u',%s' % self.prefs.listItemSpacer
    336              return sep.join(
    337                          (mq.mediaText for mq in medialist))
    338
    339 -    def do_CSSStyleSheet(self, stylesheet):
    340          out = []
    341          for rule in stylesheet.cssRules:
    342              cssText = rule.cssText
    343              if cssText:
    344                  out.append(cssText)
    345          text = self._serialize(self.prefs.lineSeparator.join(out))
    346
    347          # get encoding of sheet, defaults to UTF-8
    348          try:
    349              encoding = stylesheet.cssRules[0].encoding
    350          except (IndexError, AttributeError):
    351              encoding = 'UTF-8'
    352
    353          return text.encode(encoding, 'escapecss')
    354
    355 -    def do_CSSComment(self, rule):
    356          """
    357          serializes CSSComment which consists only of commentText
    358          """
    359          if self.prefs.keepComments and rule._cssText:
    360              return rule._cssText
    361          else:
    362              return u''
    363
    364 -    def do_CSSCharsetRule(self, rule):
    365          """
    366          serializes CSSCharsetRule
    367          encoding: string
    368
    369          always @charset "encoding";
    370          no comments or other things allowed!
    371          """
    372          if not rule.encoding or self._noinvalids(rule):
    373              return u''
    374          return u'@charset "%s";' % self._escapestring(rule.encoding)
    375
    376 -    def do_CSSFontFaceRule(self, rule):
    377          """
    378          serializes CSSFontFaceRule
    379
    380          style
    381              CSSStyleDeclaration
    382
    383          + CSSComments
    384          """
    385          self._level += 1
    386          try:
    387              styleText = self.do_css_CSSStyleDeclaration(rule.style)
    388          finally:
    389              self._level -= 1
    390
    391          if not styleText or self._noinvalids(rule):
    392              return u''
    393
    394          before = []
    395          for x in rule.seq:
    396              if hasattr(x, 'cssText'):
    397                  before.append(x.cssText)
    398              else:
    399                  # TODO: resolve
    400                  raise SyntaxErr('serializing CSSFontFaceRule: unexpected %r' % x)
    401          if before:
    402              before = u' '.join(before).strip()
    403              if before:
    404                  before = u' %s' % before
    405          else:
    406              before = u''
    407
    408          return u'%s%s {%s%s%s%s}' % (
    409              self._getatkeyword(rule, u'@font-face'),
    410              before,
    411              self.prefs.lineSeparator,
    412              self._indentblock(styleText, self._level + 1),
    413              self.prefs.lineSeparator,
    414              (self._level + 1) * self.prefs.indent
    415              )
    416
    417 -    def do_CSSImportRule(self, rule):
    418          """
    419          serializes CSSImportRule
    420
    421          href
    422              string
    423          hreftype
    424              'uri' or 'string'
    425          media
    426              cssutils.stylesheets.medialist.MediaList
    427
    428          + CSSComments
    429          """
    430          if not rule.href or self._noinvalids(rule):
    431              return u''
    432          out = [u'%s ' % self._getatkeyword(rule, u'@import')]
    433          for part in rule.seq:
    434              if rule.href == part:
    435                  if self.prefs.importHrefFormat == 'uri':
    436                      out.append(u'url(%s)' % self._uri(part))
    437                  elif self.prefs.importHrefFormat == 'string' or \
    438                     rule.hreftype == 'string':
    439                      out.append(u'"%s"' % self._escapestring(part))
    440                  else:
    441                      out.append(u'url(%s)' % self._uri(part))
    442              elif isinstance(
    443                    part, cssutils.stylesheets.medialist.MediaList):
    444                  mediaText = self.do_stylesheets_medialist(part)#.strip()
    445                  if mediaText and not mediaText == u'all':
    446                      out.append(u' %s' % mediaText)
    447              elif hasattr(part, 'cssText'): # comments
    448                  out.append(part.cssText)
    449          return u'%s;' % u''.join(out)
    450
    451 -    def do_CSSNamespaceRule(self, rule):
    452          """
    453          serializes CSSNamespaceRule
    454
    455          uri
    456              string
    457          prefix
    458              string
    459
    460          + CSSComments
    461          """
    462          if not rule.namespaceURI or self._noinvalids(rule):
    463              return u''
    464
    465          out = [u'%s' % self._getatkeyword(rule, u'@namespace')]
    466          for part in rule.seq:
    467              if rule.prefix == part and part != u'':
    468                  out.append(u' %s' % part)
    469              elif rule.namespaceURI == part:
    470                  out.append(u' "%s"' % self._escapestring(part))
    471              elif hasattr(part, 'cssText'): # comments
    472                  out.append(part.cssText)
    473          return u'%s;' % u''.join(out)
    474
    475 -    def do_CSSMediaRule(self, rule):
    476          """
    477          serializes CSSMediaRule
    478
    479          + CSSComments
    480          """
    481          # reset selectorindent
    482          self._selectors = []
    483          self._selectorlevel = 0
    484
    485          rulesout = []
    486          for r in rule.cssRules:
    487              rtext = r.cssText
    488              if rtext:
    489                  # indent each line of cssText
    490                  rulesout.append(self._indentblock(rtext, self._level + 1))
    491                  rulesout.append(self.prefs.lineSeparator)
    492
    493          if not self.prefs.keepEmptyRules and not u''.join(rulesout).strip() or\
    494             self._noinvalids(rule.media):
    495              return u''
    496
    497  #        if len(rule.cssRules) == 0 and not self.prefs.keepEmptyRules or\
    498  #           self._noinvalids(rule.media):
    499  #            return u''
    500          mediaText = self.do_stylesheets_medialist(rule.media)#.strip()
    501          out = [u'%s %s%s{%s' % (self._getatkeyword(rule, u'@media'),
    502                                 mediaText,
    503                                 self.prefs.paranthesisSpacer,
    504                                 self.prefs.lineSeparator)]
    505          out.extend(rulesout)
    506          return u'%s%s}' % (u''.join(out),
    507                             (self._level + 1) * self.prefs.indent)
    508
    509 -    def do_CSSPageRule(self, rule):
    510          """
    511          serializes CSSPageRule
    512
    513          selectorText
    514              string
    515          style
    516              CSSStyleDeclaration
    517
    518          + CSSComments
    519          """
    520          self._level += 1
    521          try:
    522              styleText = self.do_css_CSSStyleDeclaration(rule.style)
    523          finally:
    524              self._level -= 1
    525
    526          if not styleText or self._noinvalids(rule):
    527              return u''
    528
    529          return u'%s%s {%s%s%s%s}' % (
    530              self._getatkeyword(rule, u'@page'),
    531              self.do_pageselector(rule.seq),
    532              self.prefs.lineSeparator,
    533              self._indentblock(styleText, self._level + 1),
    534              self.prefs.lineSeparator,
    535              (self._level + 1) * self.prefs.indent
    536              )
    537
    538 -    def do_pageselector(self, seq):
    539          """
    540          a selector of a CSSPageRule including comments
    541          """
    542          if len(seq) == 0 or self._noinvalids(seq):
    543              return u''
    544          else:
    545              out = []
    546              for part in seq:
    547                  if hasattr(part, 'cssText'):
    548                      out.append(part.cssText)
    549                  else:
    550                      out.append(part)
    551              return u' %s' % u''.join(out)
    552
    553 -    def do_CSSUnknownRule(self, rule):
    554          """
    555          serializes CSSUnknownRule
    556          anything until ";" or "{...}"
    557          + CSSComments
    558          """
    559          if rule.atkeyword and not self._noinvalids(rule):
    560              out = [u'%s' % rule.atkeyword]
    561              for part in rule.seq:
    562                  if isinstance(part, cssutils.css.csscomment.CSSComment):
    563                      if self.prefs.keepComments:
    564                          out.append(part.cssText)
    565                  else:
    566                      out.append(part)
    567              if not (out[-1].endswith(u';') or out[-1].endswith(u'}')):
    568                  out.append(u';')
    569              return u''.join(out)
    570          else:
    571              return u''
    572
    573 -    def do_CSSStyleRule(self, rule):
    574          """
    575          serializes CSSStyleRule
    576
    577          selectorList
    578          style
    579
    580          + CSSComments
    581          """
    582          # prepare for element nested rules
    583          # TODO: sort selectors!
    584          if self.prefs.indentSpecificities:
    585              # subselectorlist?
    586              elements = set([s.element for s in rule.selectorList])
    587              specitivities = [s.specificity for s in rule.selectorList]
    588              for selector in self._selectors:
    589                  lastelements = set([s.element for s in selector])
    590                  if elements.issubset(lastelements):
    591                      # higher specificity?
    592                      lastspecitivities = [s.specificity for s in selector]
    593                      if specitivities > lastspecitivities:
    594                          self._selectorlevel += 1
    595                          break
    596                  elif self._selectorlevel > 0:
    597                      self._selectorlevel -= 1
    598              else:
    599                  # save new reference
    600                  self._selectors.append(rule.selectorList)
    601                  self._selectorlevel = 0
    602
    603          # TODO ^ RESOLVE!!!!
    604
    605          selectorText = self.do_css_SelectorList(rule.selectorList)
    606          if not selectorText or self._noinvalids(rule):
    607              return u''
    608          self._level += 1
    609          styleText = u''
    610          try:
    611              styleText = self.do_css_CSSStyleDeclaration(rule.style)
    612          finally:
    613              self._level -= 1
    614          if not styleText:
    615                  if self.prefs.keepEmptyRules:
    616                      return u'%s%s{}' % (selectorText,
    617                                          self.prefs.paranthesisSpacer)
    618          else:
    619              return self._indentblock(
    620                  u'%s%s{%s%s%s%s}' % (
    621                      selectorText,
    622                      self.prefs.paranthesisSpacer,
    623                      self.prefs.lineSeparator,
    624                      self._indentblock(styleText, self._level + 1),
    625                      self.prefs.lineSeparator,
    626                      (self._level + 1) * self.prefs.indent),
    627                  self._selectorlevel)
    628
    629 -    def do_css_SelectorList(self, selectorlist):
    630          """
    631          comma-separated list of Selectors
    632          """
    633          if selectorlist.seq and self._wellformed(selectorlist) and\
    634                                  self._valid(selectorlist):
    635              out = []
    636              sep = u',%s' % self.prefs.listItemSpacer
    637              for part in selectorlist.seq:
    638                  if hasattr(part, 'cssText'):
    639                      out.append(part.cssText)
    640  #                elif u',' == part:
    641  #                    out.append(sep)
    642                  elif isinstance(part, cssutils.css.Selector):
    643                      out.append(self.do_css_Selector(part))
    644                  else:
    645                      out.append(part) # ?
    646              return sep.join(out)
    647          else:
    648              return u''
    649
    650 -    def _getprefix(self, namespaces, namespaceURI):
    651          """
    652          returns prefix of given nsuri as defined in given
    653          dict (prefix: uri} or TODO: from CSSNamespaceRules?
    654          """
    655          if namespaceURI is None:
    656              return u'*'
    657          for prefix, uri in namespaces.items():
    658              if uri == namespaceURI:
    659                  return prefix
    660          return u'' # should not happen!
    661
    662 -    def do_css_Selector(self, selector):
    663          """
    664          a single Selector including comments
    665          """
    666          if selector.seq and self._wellformed(selector) and\
    667                                  self._valid(selector):
    668              out = []
    669              for item in selector.seq:
    670                  typ, val = item.type, item.value
    671                  if hasattr(val, 'cssText'):
    672                      # e.g. comment
    673                      out.append(val.cssText)
    674                  elif type(val) == tuple:
    675                      # TODO: CHECK IF THIS IS REALLY OK!
    676                      namespaceURI, name = val
    677                      # namespaceURI | name (name or att)
    678                      if namespaceURI != u'':
    679                          # namespace may be None
    680                          out.append(u'%s|%s' % (self._getprefix(selector.namespaces,
    681                                                                 namespaceURI),
    682                                                 name))
    683                      else:
    684                          out.append(u'%s' % name)
    685                  elif type(val) == dict: # OLD???
    686                      out.append(val['value'])
    687                  else:
    688                      if typ == 'string':
    689                          val = self._escapeSTRINGtype(val)
    690                      out.append(val)
    691              return u''.join(out)
    692          else:
    693              return u''
    694
    695 -    def do_css_CSSStyleDeclaration(self, style, separator=None):
    696          """
    697          Style declaration of CSSStyleRule
    698          """
    699          if len(style.seq) > 0 and self._wellformed(style) and\
    700                                  self._valid(style):
    701              if separator is None:
    702                  separator = self.prefs.lineSeparator
    703
    704              if self.prefs.keepAllProperties:
    705                  parts = style.seq
    706              else:
    707                  # find distinct names
    708                  nnames = set()
    709                  for x in style.seq:
    710                      if isinstance(x, cssutils.css.Property):
    711                          nnames.add(x.name)
    712                  # filter list
    713                  parts = []
    714                  for x in reversed(style.seq):
    715                      if isinstance(x, cssutils.css.Property):
    716                          if x.name in nnames:
    717                              parts.append(x)
    718                              nnames.remove(x.name)
    719                      else:
    720                          parts.append(x)
    721                  parts.reverse()
    722
    723              out = []
    724              for (i, part) in enumerate(parts):
    725                  if isinstance(part, cssutils.css.CSSComment):
    726                      # CSSComment
    727                      if self.prefs.keepComments:
    728                          out.append(part.cssText)
    729                          out.append(separator)
    730                  elif isinstance(part, cssutils.css.Property):
    731                      # PropertySimilarNameList
    732                      out.append(self.do_Property(part))
    733                      if not (self.prefs.omitLastSemicolon and i==len(parts)-1):
    734                          out.append(u';')
    735                      out.append(separator)
    736                  else:
    737                      # other?
    738                      out.append(part)
    739
    740              if out and out[-1] == separator:
    741                  del out[-1]
    742
    743              return u''.join(out)
    744
    745          else:
    746              return u''
    747
    748 -    def do_Property(self, property):
    749          """
    750          Style declaration of CSSStyleRule
    751
    752          Property has a seqs attribute which contains seq lists for
    753          name, a CSSvalue and a seq list for priority
    754          """
    755          out = []
    756          if property.seqs[0] and self._wellformed(property) and\
    757                                  self._valid(property):
    758              nameseq, cssvalue, priorityseq = property.seqs
    759
    760              #name
    761              for part in nameseq:
    762                  if hasattr(part, 'cssText'):
    763                      out.append(part.cssText)
    764                  elif property.literalname == part:
    765                      out.append(self._getpropertyname(property, part))
    766                  else:
    767                      out.append(part)
    768
    769              if out and (not property._mediaQuery or
    770                          property._mediaQuery and cssvalue.cssText):
    771                  # MediaQuery may consist of name only
    772                  out.append(u':')
    773                  out.append(self.prefs.propertyNameSpacer)
    774
    775              # value
    776              out.append(cssvalue.cssText)
    777
    778              # priority
    779              if out and priorityseq:
    780                  out.append(u' ')
    781                  for part in priorityseq:
    782                      if hasattr(part, 'cssText'): # comments
    783                          out.append(part.cssText)
    784                      else:
    785                          out.append(part)
    786
    787          return u''.join(out)
    788
    789 -    def do_Property_priority(self, priorityseq):
    790          """
    791          a Properties priority "!" S* "important"
    792          """
    793          out = []
    794          for part in priorityseq:
    795              if hasattr(part, 'cssText'): # comments
    796                  out.append(u' ')
    797                  out.append(part.cssText)
    798                  out.append(u' ')
    799              else:
    800                  out.append(part)
    801          return u''.join(out).strip()
    802
    803 -    def do_css_CSSValue(self, cssvalue):
    804          """
    805          serializes a CSSValue
    806          """
    807          if not cssvalue:
    808              return u''
    809          else:
    810              sep = u',%s' % self.prefs.listItemSpacer
    811              out = []
    812              for part in cssvalue.seq:
    813                  if hasattr(part, 'cssText'):
    814                      # comments or CSSValue if a CSSValueList
    815                      out.append(part.cssText)
    816                  elif isinstance(part, basestring) and part == u',':
    817                      out.append(sep)
    818                  else:
    819                      # TODO: escape func parameter if STRING!
    820                      if part and part[0] == part[-1] and part[0] in '\'"':
    821                          part = self._escapeSTRINGtype(part)
    822                      out.append(part)
    823              return (u''.join(out)).strip()
    824