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