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', 'Preferences']
      7  __docformat__ = 'restructuredtext'
      8  __author__ = '$LastChangedBy: cthedot $'
      9  __date__ = '$LastChangedDate: 2008-02-22 19:03:20 +0100 (Fr, 22 Feb 2008) $'
     10  __version__ = '$LastChangedRevision: 1070 $'
     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.object[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      defaultPropertyPriority = True
     45          Should the normalized or literal priority be used, e.g. '!important'
     46          or u'!Im\portant'
     47
     48      importHrefFormat = None
     49          Uses hreftype if ``None`` or explicit ``'string'`` or ``'uri'``
     50      indent = 4 * ' '
     51          Indentation of e.g Properties inside a CSSStyleDeclaration
     52      indentSpecificities = False
     53          Indent rules with subset of Selectors and higher Specitivity
     54
     55      keepAllProperties = True
     56          If ``True`` all properties set in the original CSSStylesheet
     57          are kept meaning even properties set twice with the exact same
     58          same name are kept!
     59      keepComments = True
     60          If ``False`` removes all CSSComments
     61      keepEmptyRules = False
     62          defines if empty rules like e.g. ``a {}`` are kept in the resulting
     63          serialized sheet
     64      keepUsedNamespaceRulesOnly = False
     65          if True only namespace rules which are actually used are kept
     66
     67      lineNumbers = False
     68          Only used if a complete CSSStyleSheet is serialized.
     69      lineSeparator = u'\\n'
     70          How to end a line. This may be set to e.g. u'' for serializing of
     71          CSSStyleDeclarations usable in HTML style attribute.
     72      listItemSpacer = u' '
     73          string which is used in ``css.SelectorList``, ``css.CSSValue`` and
     74          ``stylesheets.MediaList`` after the comma
     75      omitLastSemicolon = True
     76          If ``True`` omits ; after last property of CSSStyleDeclaration
     77      paranthesisSpacer = u' '
     78          string which is used before an opening paranthesis like in a
     79          ``css.CSSMediaRule`` or ``css.CSSStyleRule``
     80      propertyNameSpacer = u' '
     81          string which is used after a Property name colon
     82      selectorCombinatorSpacer = u' '
     83          string which is used before and after a Selector combinator like +, > or ~.
     84          CSSOM defines a single space for this which is also the default in cssutils.
     85      spacer = u' '
     86          general spacer, used e.g. by CSSUnknownRule
     87
     88      validOnly = False **DO NOT CHANGE YET**
     89          if True only valid (currently Properties) are kept
     90
     91          A Property is valid if it is a known Property with a valid value.
     92          Currently CSS 2.1 values as defined in cssproperties.py would be
     93          valid.
     94
     95      """
     96 -    def __init__(self, **initials):
     97          """
     98          Always use named instead of positional parameters
     99          """
    100          self.useDefaults()
    101
    102          for key, value in initials.items():
    103              if value:
    104                  self.__setattr__(key, value)
    105
    106 -    def useDefaults(self):
    107          "reset all preference options to the default value"
    108          self.defaultAtKeyword = True
    109          self.defaultPropertyName = True
    110          self.defaultPropertyPriority = True
    111          self.importHrefFormat = None
    112          self.indent = 4 * u' '
    113          self.indentSpecificities = False
    114          self.keepAllProperties = True
    115          self.keepComments = True
    116          self.keepEmptyRules = False
    117          self.keepUsedNamespaceRulesOnly = False
    118          self.lineNumbers = False
    119          self.lineSeparator = u'\n'
    120          self.listItemSpacer = u' '
    121          self.omitLastSemicolon = True
    122          self.paranthesisSpacer = u' '
    123          self.propertyNameSpacer = u' '
    124          self.selectorCombinatorSpacer = u' '
    125          self.spacer = u' '
    126          self.validOnly = False # should not be changed currently!!!
    127
    128 -    def useMinified(self):
    129          """
    130          sets options to achive a minified stylesheet
    131
    132          you may want to set preferences with this convinience method
    133          and set settings you want adjusted afterwards
    134          """
    135          self.importHrefFormat = 'string'
    136          self.indent = u''
    137          self.keepComments = False
    138          self.keepEmptyRules = False
    139          self.keepUsedNamespaceRulesOnly = True
    140          self.lineNumbers = False
    141          self.lineSeparator = u''
    142          self.listItemSpacer = u''
    143          self.omitLastSemicolon = True
    144          self.paranthesisSpacer = u''
    145          self.propertyNameSpacer = u''
    146          self.selectorCombinatorSpacer = u''
    147          self.spacer = u''
    148          self.validOnly = False
    149
    150 -    def __repr__(self):
    151          return u"cssutils.css.%s(%s)" % (self.__class__.__name__,
    152              u', '.join(['\n    %s=%r' % (p, self.__getattribute__(p)) for p in self.__dict__]
    153                  ))
    154
    155 -    def __str__(self):
    156          return u"<cssutils.css.%s object %s at 0x%x" % (self.__class__.__name__,
    157              u' '.join(['%s=%r' % (p, self.__getattribute__(p)) for p in self.__dict__]
    158                  ),
    159                  id(self))
    160
    161
    162 -class Out(object):
    163      """
    164      a simple class which makes appended items available as a combined string
    165      """
    166 -    def __init__(self, ser):
    167          self.ser = ser
    168          self.out = []
    169
    170 -    def _remove_last_if_S(self):
    171          if self.out and not self.out[-1].strip():
    172              # remove trailing S
    173              del self.out[-1]
    174
    175 -    def append(self, val, typ=None, space=True, keepS=False, indent=False):
    176          """
    177          Appends val. Adds a single S after each token except as follows:
    178
    179          - typ COMMENT
    180              uses cssText depending on self.ser.prefs.keepComments
    181          - typ cssutils.css.CSSRule.UNKNOWN_RULE
    182              uses cssText
    183          - typ STRING
    184              escapes ser._string
    185          - typ S
    186              ignored except ``keepS=True``
    187          - typ URI
    188              calls ser_uri
    189          - val {
    190              adds \n after
    191          - val ;
    192              removes S before and adds \n after
    193          - val , :
    194              removes S before
    195          - val + > ~
    196              encloses in prefs.selectorCombinatorSpacer
    197          - some other vals
    198              add *spacer except ``space=False``
    199          """
    200          if val or 'STRING' == typ:
    201              # PRE
    202              if 'COMMENT' == typ:
    203                  if self.ser.prefs.keepComments:
    204                      val = val.cssText
    205                  else:
    206                      return
    207              elif cssutils.css.CSSRule.UNKNOWN_RULE == typ:
    208                  val = val.cssText
    209              elif 'S' == typ and not keepS:
    210                  return
    211              elif 'STRING' == typ:
    212                  # may be empty but MUST not be None
    213                  if val is None:
    214                      return
    215                  val = self.ser._string(val)
    216              elif 'URI' == typ:
    217                  val = self.ser._uri(val)
    218              elif val in u'+>~,:{;)]':
    219                  self._remove_last_if_S()
    220
    221              # APPEND
    222              if indent:
    223                  self.out.append(self.ser._indentblock(val, self.ser._level+1))
    224              else:
    225                  self.out.append(val)
    226
    227              # POST
    228              if val in u'+>~': # enclose selector combinator
    229                  self.out.insert(-1, self.ser.prefs.selectorCombinatorSpacer)
    230                  self.out.append(self.ser.prefs.selectorCombinatorSpacer)
    231              elif u',' == val: # list
    232                  self.out.append(self.ser.prefs.listItemSpacer)
    233              elif u':' == val: # prop
    234                  self.out.append(self.ser.prefs.propertyNameSpacer)
    235              elif u'{' == val: # block start
    236                  self.out.insert(-1, self.ser.prefs.paranthesisSpacer)
    237                  self.out.append(self.ser.prefs.lineSeparator)
    238              elif u';' == val: # end or prop or block
    239                  self.out.append(self.ser.prefs.lineSeparator)
    240              elif val not in u'}[]()' and space:
    241                  self.out.append(self.ser.prefs.spacer)
    242
    243 -    def value(self, delim=u'', end=None):
    244          "returns all items joined by delim"
    245          self._remove_last_if_S()
    246          if end:
    247              self.out.append(end)
    248          return delim.join(self.out)
    249
    250
    251 -class CSSSerializer(object):
    252      """
    253      Methods to serialize a CSSStylesheet and its parts
    254
    255      To use your own serializing method the easiest is to subclass CSS
    256      Serializer and overwrite the methods you like to customize.
    257      """
    258      # chars not in URI without quotes around
    259      __forbidden_in_uri_matcher = re.compile(ur'''.*?[\)\s\;]''', re.U).match
    260
    261 -    def __init__(self, prefs=None):
    262          """
    263          prefs
    264              instance of Preferences
    265          """
    266          if not prefs:
    267              prefs = Preferences()
    268          self.prefs = prefs
    269          self._level = 0 # current nesting level
    270
    271          # TODO:
    272          self._selectors = [] # holds SelectorList
    273          self._selectorlevel = 0 # current specificity nesting level
    274
    275 -    def _atkeyword(self, rule, default):
    276          "returns default or source atkeyword depending on prefs"
    277          if self.prefs.defaultAtKeyword:
    278              return default
    279          else:
    280              return rule.atkeyword
    281
    282 -    def _indentblock(self, text, level):
    283          """
    284          indent a block like a CSSStyleDeclaration to the given level
    285          which may be higher than self._level (e.g. for CSSStyleDeclaration)
    286          """
    287          if not self.prefs.lineSeparator:
    288              return text
    289          return self.prefs.lineSeparator.join(
    290              [u'%s%s' % (level * self.prefs.indent, line)
    291                  for line in text.split(self.prefs.lineSeparator)]
    292          )
    293
    294 -    def _propertyname(self, property, actual):
    295          """
    296          used by all styledeclarations to get the propertyname used
    297          dependent on prefs setting defaultPropertyName and
    298          keepAllProperties
    299          """
    300          if self.prefs.defaultPropertyName and not self.prefs.keepAllProperties:
    301              return property.name
    302          else:
    303              return actual
    304
    305 -    def _linenumnbers(self, text):
    306          if self.prefs.lineNumbers:
    307              pad = len(str(text.count(self.prefs.lineSeparator)+1))
    308              out = []
    309              for i, line in enumerate(text.split(self.prefs.lineSeparator)):
    310                  out.append((u'%*i: %s') % (pad, i+1, line))
    311              text = self.prefs.lineSeparator.join(out)
    312          return text
    313
    314 -    def _string(self, s):
    315          """
    316          returns s encloded between "..." and escaped delim charater ",
    317          escape line breaks \\n \\r and \\f
    318          """
    319          # \n = 0xa, \r = 0xd, \f = 0xc
    320          s = s.replace('\n', '\\a ').replace(
    321                        '\r', '\\d ').replace(
    322                        '\f', '\\c ')
    323          return u'"%s"' % s.replace('"', u'\\"')
    324
    325 -    def _uri(self, uri):
    326          """returns uri enclosed in url() and "..." if necessary"""
    327          if CSSSerializer.__forbidden_in_uri_matcher(uri):
    328              return 'url(%s)' % self._string(uri)
    329          else:
    330              return 'url(%s)' % uri
    331
    332 -    def _valid(self, x):
    333          "checks items valid property and prefs.validOnly"
    334          return not self.prefs.validOnly or (self.prefs.validOnly and
    335                                              x.valid)
    336
    337 -    def do_CSSStyleSheet(self, stylesheet):
    338          """serializes a complete CSSStyleSheet"""
    339          useduris = stylesheet._getUsedURIs()
    340          out = []
    341          for rule in stylesheet.cssRules:
    342              if self.prefs.keepUsedNamespaceRulesOnly and\
    343                 rule.NAMESPACE_RULE == rule.type and\
    344                 rule.namespaceURI not in useduris and (
    345                      rule.prefix or None not in useduris):
    346                  continue
    347
    348              cssText = rule.cssText
    349              if cssText:
    350                  out.append(cssText)
    351          text = self._linenumnbers(self.prefs.lineSeparator.join(out))
    352
    353          # get encoding of sheet, defaults to UTF-8
    354          try:
    355              encoding = stylesheet.cssRules[0].encoding
    356          except (IndexError, AttributeError):
    357              encoding = 'UTF-8'
    358
    359          return text.encode(encoding, 'escapecss')
    360
    361 -    def do_CSSComment(self, rule):
    362          """
    363          serializes CSSComment which consists only of commentText
    364          """
    365          # no need to use Out() as too simple
    366          if rule._cssText and self.prefs.keepComments:
    367              return rule._cssText
    368          else:
    369              return u''
    370
    371 -    def do_CSSCharsetRule(self, rule):
    372          """
    373          serializes CSSCharsetRule
    374          encoding: string
    375
    376          always @charset "encoding";
    377          no comments or other things allowed!
    378          """
    379          # no need to use Out() as too simple
    380          if rule.wellformed:
    381              return u'@charset %s;' % self._string(rule.encoding)
    382          else:
    383              return u''
    384
    385 -    def do_CSSFontFaceRule(self, rule):
    386          """
    387          serializes CSSFontFaceRule
    388
    389          style
    390              CSSStyleDeclaration
    391
    392          + CSSComments
    393          """
    394          styleText = self.do_css_CSSStyleDeclaration(rule.style)
    395
    396          if styleText and rule.wellformed:
    397              out = Out(self)
    398              out.append(self._atkeyword(rule, u'@font-face'))
    399              for item in rule.seq:
    400                  # assume comments {
    401                  out.append(item.value, item.type)
    402              out.append(u'{')
    403              out.append(u'%s%s}' % (styleText, self.prefs.lineSeparator),
    404                         indent=1)
    405              return out.value()
    406          else:
    407              return u''
    408
    409 -    def do_CSSImportRule(self, rule):
    410          """
    411          serializes CSSImportRule
    412
    413          href
    414              string
    415          media
    416              optional cssutils.stylesheets.medialist.MediaList
    417          name
    418              optional string
    419
    420          + CSSComments
    421          """
    422          if rule.wellformed:
    423              out = Out(self)
    424              out.append(self._atkeyword(rule, u'@import'))
    425
    426              for item in rule.seq:
    427                  typ, val = item.type, item.value
    428                  if 'href' == typ:
    429                      # "href" or url(href)
    430                      if self.prefs.importHrefFormat == 'string' or (
    431                               self.prefs.importHrefFormat != 'uri' and
    432                               rule.hreftype == 'string'):
    433                          out.append(val, 'STRING')
    434                      else:
    435                          out.append(val, 'URI')
    436                  elif 'media' == typ:
    437                      # media
    438                      mediaText = self.do_stylesheets_medialist(val)
    439                      if mediaText and mediaText != u'all':
    440                          out.append(mediaText)
    441                  elif 'name' == typ:
    442                      out.append(val, 'STRING')
    443                  else:
    444                      out.append(val, typ)
    445
    446              return out.value(end=u';')
    447          else:
    448              return u''
    449
    450 -    def do_CSSNamespaceRule(self, rule):
    451          """
    452          serializes CSSNamespaceRule
    453
    454          uri
    455              string
    456          prefix
    457              string
    458
    459          + CSSComments
    460          """
    461          if rule.wellformed:
    462              out = Out(self)
    463              out.append(self._atkeyword(rule, u'@namespace'))
    464
    465              for item in rule.seq:
    466                  typ, val = item.type, item.value
    467                  if 'namespaceURI' == typ:
    468                      out.append(val, 'STRING')
    469                  else:
    470                      out.append(val, typ)
    471
    472              return out.value(end=u';')
    473          else:
    474              return u''
    475
    476 -    def do_CSSMediaRule(self, rule):
    477          """
    478          serializes CSSMediaRule
    479
    480          + CSSComments
    481          """
    482          # TODO: use Out()?
    483
    484          # @media
    485          out = [self._atkeyword(rule, u'@media')]
    486          out.append(self.prefs.spacer) # might be empty
    487
    488          # mediaquery
    489          if not rule.media.wellformed:
    490              return u''
    491          out.append(self.do_stylesheets_medialist(rule.media))
    492
    493          # name, seq contains content after name only (Comments)
    494          if rule.name:
    495              out.append(self.prefs.spacer)
    496              nameout = Out(self)
    497              nameout.append(self._string(rule.name))
    498              for item in rule.seq:
    499                  nameout.append(item.value, item.type)
    500              out.append(nameout.value())
    501
    502          #  {
    503          out.append(self.prefs.paranthesisSpacer)
    504          out.append(u'{')
    505          out.append(self.prefs.lineSeparator)
    506
    507          # rules
    508          rulesout = []
    509          for r in rule.cssRules:
    510              rtext = r.cssText
    511              if rtext:
    512                  # indent each line of cssText
    513                  rulesout.append(self._indentblock(rtext, self._level + 1))
    514                  rulesout.append(self.prefs.lineSeparator)
    515          if not self.prefs.keepEmptyRules and not u''.join(rulesout).strip():
    516              return u''
    517          out.extend(rulesout)
    518
    519          #     }
    520          out.append(u'%s}' % ((self._level + 1) * self.prefs.indent))
    521
    522          return u''.join(out)
    523
    524 -    def do_CSSPageRule(self, rule):
    525          """
    526          serializes CSSPageRule
    527
    528          selectorText
    529              string
    530          style
    531              CSSStyleDeclaration
    532
    533          + CSSComments
    534          """
    535          styleText = self.do_css_CSSStyleDeclaration(rule.style)
    536
    537          if styleText and rule.wellformed:
    538              out = Out(self)
    539              out.append(self._atkeyword(rule, u'@page'))
    540
    541              for item in rule.seq:
    542                  out.append(item.value, item.type)
    543
    544              out.append(u'{')
    545              out.append(u'%s%s}' % (styleText, self.prefs.lineSeparator),
    546                         indent=1)
    547              return out.value()
    548          else:
    549              return u''
    550
    551 -    def do_CSSUnknownRule(self, rule):
    552          """
    553          serializes CSSUnknownRule
    554          anything until ";" or "{...}"
    555          + CSSComments
    556          """
    557          if rule.wellformed:
    558              out = Out(self)
    559              out.append(rule.atkeyword)
    560              stacks = []
    561              for item in rule.seq:
    562                  typ, val = item.type, item.value
    563
    564                  # PRE
    565                  if u'}' == val:
    566                      # close last open item on stack
    567                      stackblock = stacks.pop().value()
    568                      if stackblock:
    569                          val = self._indentblock(
    570                                 stackblock + self.prefs.lineSeparator + val,
    571                                 min(1, len(stacks)+1))
    572                  # APPEND
    573                  if stacks:
    574                      stacks[-1].append(val, typ)
    575                  else:
    576                      out.append(val, typ)
    577
    578                  # POST
    579                  if u'{' == val:
    580                      # new stack level
    581                      stacks.append(Out(self))
    582
    583              return out.value()
    584          else:
    585              return u''
    586
    587 -    def do_CSSStyleRule(self, rule):
    588          """
    589          serializes CSSStyleRule
    590
    591          selectorList
    592          style
    593
    594          + CSSComments
    595          """
    596          # TODO: use Out()
    597
    598          # prepare for element nested rules
    599          # TODO: sort selectors!
    600          if self.prefs.indentSpecificities:
    601              # subselectorlist?
    602              elements = set([s.element for s in rule.selectorList])
    603              specitivities = [s.specificity for s in rule.selectorList]
    604              for selector in self._selectors:
    605                  lastelements = set([s.element for s in selector])
    606                  if elements.issubset(lastelements):
    607                      # higher specificity?
    608                      lastspecitivities = [s.specificity for s in selector]
    609                      if specitivities > lastspecitivities:
    610                          self._selectorlevel += 1
    611                          break
    612                  elif self._selectorlevel > 0:
    613                      self._selectorlevel -= 1
    614              else:
    615                  # save new reference
    616                  self._selectors.append(rule.selectorList)
    617                  self._selectorlevel = 0
    618
    619          # TODO ^ RESOLVE!!!!
    620
    621          selectorText = self.do_css_SelectorList(rule.selectorList)
    622          if not selectorText or not rule.wellformed:
    623              return u''
    624          self._level += 1
    625          styleText = u''
    626          try:
    627              styleText = self.do_css_CSSStyleDeclaration(rule.style)
    628          finally:
    629              self._level -= 1
    630          if not styleText:
    631                  if self.prefs.keepEmptyRules:
    632                      return u'%s%s{}' % (selectorText,
    633                                          self.prefs.paranthesisSpacer)
    634          else:
    635              return self._indentblock(
    636                  u'%s%s{%s%s%s%s}' % (
    637                      selectorText,
    638                      self.prefs.paranthesisSpacer,
    639                      self.prefs.lineSeparator,
    640                      self._indentblock(styleText, self._level + 1),
    641                      self.prefs.lineSeparator,
    642                      (self._level + 1) * self.prefs.indent),
    643                  self._selectorlevel)
    644
    645 -    def do_css_SelectorList(self, selectorlist):
    646          "comma-separated list of Selectors"
    647          # does not need Out() as it is too simple
    648          if selectorlist.wellformed:
    649              out = []
    650              for part in selectorlist.seq:
    651                  if isinstance(part, cssutils.css.Selector):
    652                      out.append(part.selectorText)
    653                  else:
    654                      out.append(part) # should not happen
    655              sep = u',%s' % self.prefs.listItemSpacer
    656              return sep.join(out)
    657          else:
    658              return u''
    659
    660 -    def do_css_Selector(self, selector):
    661          """
    662          a single Selector including comments
    663
    664          an element has syntax (namespaceURI, name) where namespaceURI may be:
    665
    666          - cssutils._ANYNS => ``*|name``
    667          - None => ``name``
    668          - u'' => ``|name``
    669          - any other value: => ``prefix|name``
    670          """
    671          if selector.wellformed:
    672              out = Out(self)
    673
    674              DEFAULTURI = selector._namespaces.get('', None)
    675              for item in selector.seq:
    676                  typ, val = item.type, item.value
    677                  if type(val) == tuple:
    678                      # namespaceURI|name (element or attribute)
    679                      namespaceURI, name = val
    680                      if DEFAULTURI == namespaceURI or (not DEFAULTURI and
    681                                                        namespaceURI is None):
    682                          out.append(name, typ, space=False)
    683                      else:
    684                          if namespaceURI == cssutils._ANYNS:
    685                              prefix = u'*'
    686                          else:
    687                              try:
    688                                  prefix = selector._namespaces.prefixForNamespaceURI(
    689                                                      namespaceURI)
    690                              except IndexError:
    691                                  prefix = u''
    692
    693                          out.append(u'%s|%s' % (prefix, name), typ, space=False)
    694                  else:
    695                      out.append(val, typ, space=False, keepS=True)
    696
    697              return out.value()
    698          else:
    699              return u''
    700
    701 -    def do_css_CSSStyleDeclaration(self, style, separator=None):
    702          """
    703          Style declaration of CSSStyleRule
    704          """
    705          # TODO: use Out()
    706
    707          # may be comments only
    708          if len(style.seq) > 0:
    709              if separator is None:
    710                  separator = self.prefs.lineSeparator
    711
    712              if self.prefs.keepAllProperties:
    713                  # all
    714                  parts = style.seq
    715              else:
    716                  # only effective ones
    717                  _effective = style.getProperties()
    718                  parts = [x for x in style.seq
    719                           if (isinstance(x, cssutils.css.Property)
    720                               and x in _effective)
    721                           or not isinstance(x, cssutils.css.Property)]
    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          # TODO: use Out()
    756
    757          out = []
    758          if property.seqs[0] and property.wellformed and self._valid(property):
    759              nameseq, cssvalue, priorityseq = property.seqs
    760
    761              #name
    762              for part in nameseq:
    763                  if hasattr(part, 'cssText'):
    764                      out.append(part.cssText)
    765                  elif property.literalname == part:
    766                      out.append(self._propertyname(property, part))
    767                  else:
    768                      out.append(part)
    769
    770              if out and (not property._mediaQuery or
    771                          property._mediaQuery and cssvalue.cssText):
    772                  # MediaQuery may consist of name only
    773                  out.append(u':')
    774                  out.append(self.prefs.propertyNameSpacer)
    775
    776              # value
    777              out.append(cssvalue.cssText)
    778
    779              # priority
    780              if out and priorityseq:
    781                  out.append(u' ')
    782                  for part in priorityseq:
    783                      if hasattr(part, 'cssText'): # comments
    784                          out.append(part.cssText)
    785                      else:
    786                          if part == property.literalpriority and\
    787                             self.prefs.defaultPropertyPriority:
    788                              out.append(property.priority)
    789                          else:
    790                              out.append(part)
    791
    792          return u''.join(out)
    793
    794 -    def do_Property_priority(self, priorityseq):
    795          """
    796          a Properties priority "!" S* "important"
    797          """
    798          # TODO: use Out()
    799
    800          out = []
    801          for part in priorityseq:
    802              if hasattr(part, 'cssText'): # comments
    803                  out.append(u' ')
    804                  out.append(part.cssText)
    805                  out.append(u' ')
    806              else:
    807                  out.append(part)
    808          return u''.join(out).strip()
    809
    810 -    def do_css_CSSValue(self, cssvalue):
    811          """
    812          serializes a CSSValue
    813          """
    814          # TODO: use Out()
    815          # TODO: use self._valid(cssvalue)?
    816
    817          if not cssvalue:
    818              return u''
    819          else:
    820              sep = u',%s' % self.prefs.listItemSpacer
    821              out = []
    822              for part in cssvalue.seq:
    823                  if hasattr(part, 'cssText'):
    824                      # comments or CSSValue if a CSSValueList
    825                      out.append(part.cssText)
    826                  elif isinstance(part, basestring) and part == u',':
    827                      out.append(sep)
    828                  else:
    829                      # TODO: escape func parameter if STRING!
    830                      if part and part[0] == part[-1] and part[0] in '\'"':
    831                          # string has " " around it in CSSValue!
    832                          part = self._string(part[1:-1])
    833                      out.append(part)
    834              return (u''.join(out)).strip()
    835
    836 -    def do_stylesheets_medialist(self, medialist):
    837          """
    838          comma-separated list of media, default is 'all'
    839
    840          If "all" is in the list, every other media *except* "handheld" will
    841          be stripped. This is because how Opera handles CSS for PDAs.
    842          """
    843          if len(medialist) == 0:
    844              return u'all'
    845          else:
    846              sep = u',%s' % self.prefs.listItemSpacer
    847              return sep.join((mq.mediaText for mq in medialist))
    848
    849 -    def do_stylesheets_mediaquery(self, mediaquery):
    850          """
    851          a single media used in medialist
    852          """
    853          if mediaquery.wellformed:
    854              out = []
    855              for part in mediaquery.seq:
    856                  if isinstance(part, cssutils.css.Property): # Property
    857                      out.append(u'(%s)' % part.cssText)
    858                  elif hasattr(part, 'cssText'): # comments
    859                      out.append(part.cssText)
    860                  else:
    861                      # TODO: media queries!
    862                      out.append(part)
    863              return u' '.join(out)
    864          else:
    865              return u''
    866