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 1284 2008-06-05 16:29:17Z 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                 lineSeparator=False):
    174          """
    175          Appends val. Adds a single S after each token except as follows:
    176
    177          - typ COMMENT
    178              uses cssText depending on self.ser.prefs.keepComments
    179          - typ "Property", cssutils.css.CSSRule.UNKNOWN_RULE
    180              uses cssText
    181          - typ STRING
    182              escapes ser._string
    183          - typ S
    184              ignored except ``keepS=True``
    185          - typ URI
    186              calls ser_uri
    187          - val {
    188              adds \n after
    189          - val ;
    190              removes S before and adds \n after
    191          - val , :
    192              removes S before
    193          - val + > ~
    194              encloses in prefs.selectorCombinatorSpacer
    195          - some other vals
    196              add *spacer except ``space=False``
    197          """
    198          if val or 'STRING' == typ:
    199              # PRE
    200              if 'COMMENT' == typ:
    201                  if self.ser.prefs.keepComments:
    202                      val = val.cssText
    203                  else:
    204                      return
    205              elif typ in ('Property', cssutils.css.CSSRule.UNKNOWN_RULE):
    206                  val = val.cssText
    207              elif 'S' == typ and not keepS:
    208                  return
    209              elif 'STRING' == typ:
    210                  # may be empty but MUST not be None
    211                  if val is None:
    212                      return
    213                  val = self.ser._string(val)
    214              elif 'URI' == typ:
    215                  val = self.ser._uri(val)
    216              elif val in u'+>~,:{;)]':
    217                  self._remove_last_if_S()
    218
    219              # APPEND
    220              if indent:
    221                  self.out.append(self.ser._indentblock(val, self.ser._level+1))
    222              else:
    223                  self.out.append(val)
    224              # POST
    225              if lineSeparator:
    226                  # Property , ...
    227                  pass
    228              elif 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          if rule._cssText and self.prefs.keepComments:
    366              return rule._cssText
    367          else:
    368              return u''
    369
    370 -    def do_CSSCharsetRule(self, rule):
    371          """
    372          serializes CSSCharsetRule
    373          encoding: string
    374
    375          always @charset "encoding";
    376          no comments or other things allowed!
    377          """
    378          if rule.wellformed:
    379              return u'@charset %s;' % self._string(rule.encoding)
    380          else:
    381              return u''
    382
    383 -    def do_CSSFontFaceRule(self, rule):
    384          """
    385          serializes CSSFontFaceRule
    386
    387          style
    388              CSSStyleDeclaration
    389
    390          + CSSComments
    391          """
    392          styleText = self.do_css_CSSStyleDeclaration(rule.style)
    393
    394          if styleText and rule.wellformed:
    395              out = Out(self)
    396              out.append(self._atkeyword(rule, u'@font-face'))
    397              for item in rule.seq:
    398                  # assume comments {
    399                  out.append(item.value, item.type)
    400              out.append(u'{')
    401              out.append(u'%s%s}' % (styleText, self.prefs.lineSeparator),
    402                         indent=1)
    403              return out.value()
    404          else:
    405              return u''
    406
    407 -    def do_CSSImportRule(self, rule):
    408          """
    409          serializes CSSImportRule
    410
    411          href
    412              string
    413          media
    414              optional cssutils.stylesheets.medialist.MediaList
    415          name
    416              optional string
    417
    418          + CSSComments
    419          """
    420          if rule.wellformed:
    421              out = Out(self)
    422              out.append(self._atkeyword(rule, u'@import'))
    423
    424              for item in rule.seq:
    425                  typ, val = item.type, item.value
    426                  if 'href' == typ:
    427                      # "href" or url(href)
    428                      if self.prefs.importHrefFormat == 'string' or (
    429                               self.prefs.importHrefFormat != 'uri' and
    430                               rule.hreftype == 'string'):
    431                          out.append(val, 'STRING')
    432                      else:
    433                          out.append(val, 'URI')
    434                  elif 'media' == typ:
    435                      # media
    436                      mediaText = self.do_stylesheets_medialist(val)
    437                      if mediaText and mediaText != u'all':
    438                          out.append(mediaText)
    439                  elif 'name' == typ:
    440                      out.append(val, 'STRING')
    441                  else:
    442                      out.append(val, typ)
    443
    444              return out.value(end=u';')
    445          else:
    446              return u''
    447
    448 -    def do_CSSNamespaceRule(self, rule):
    449          """
    450          serializes CSSNamespaceRule
    451
    452          uri
    453              string
    454          prefix
    455              string
    456
    457          + CSSComments
    458          """
    459          if rule.wellformed:
    460              out = Out(self)
    461              out.append(self._atkeyword(rule, u'@namespace'))
    462
    463              for item in rule.seq:
    464                  typ, val = item.type, item.value
    465                  if 'namespaceURI' == typ:
    466                      out.append(val, 'STRING')
    467                  else:
    468                      out.append(val, typ)
    469
    470              return out.value(end=u';')
    471          else:
    472              return u''
    473
    474 -    def do_CSSMediaRule(self, rule):
    475          """
    476          serializes CSSMediaRule
    477
    478          + CSSComments
    479          """
    480          # TODO: use Out()?
    481
    482          # @media
    483          out = [self._atkeyword(rule, u'@media')]
    484          out.append(self.prefs.spacer) # might be empty
    485
    486          # mediaquery
    487          if not rule.media.wellformed:
    488              return u''
    489          out.append(self.do_stylesheets_medialist(rule.media))
    490
    491          # name, seq contains content after name only (Comments)
    492          if rule.name:
    493              out.append(self.prefs.spacer)
    494              nameout = Out(self)
    495              nameout.append(self._string(rule.name))
    496              for item in rule.seq:
    497                  nameout.append(item.value, item.type)
    498              out.append(nameout.value())
    499
    500          #  {
    501          out.append(self.prefs.paranthesisSpacer)
    502          out.append(u'{')
    503          out.append(self.prefs.lineSeparator)
    504
    505          # rules
    506          rulesout = []
    507          for r in rule.cssRules:
    508              rtext = r.cssText
    509              if rtext:
    510                  # indent each line of cssText
    511                  rulesout.append(self._indentblock(rtext, self._level + 1))
    512                  rulesout.append(self.prefs.lineSeparator)
    513          if not self.prefs.keepEmptyRules and not u''.join(rulesout).strip():
    514              return u''
    515          out.extend(rulesout)
    516
    517          #     }
    518          out.append(u'%s}' % ((self._level + 1) * self.prefs.indent))
    519
    520          return u''.join(out)
    521
    522 -    def do_CSSPageRule(self, rule):
    523          """
    524          serializes CSSPageRule
    525
    526          selectorText
    527              string
    528          style
    529              CSSStyleDeclaration
    530
    531          + CSSComments
    532          """
    533          styleText = self.do_css_CSSStyleDeclaration(rule.style)
    534
    535          if styleText and rule.wellformed:
    536              out = Out(self)
    537              out.append(self._atkeyword(rule, u'@page'))
    538
    539              for item in rule.seq:
    540                  out.append(item.value, item.type)
    541
    542              out.append(u'{')
    543              out.append(u'%s%s}' % (styleText, self.prefs.lineSeparator),
    544                         indent=1)
    545              return out.value()
    546          else:
    547              return u''
    548
    549 -    def do_CSSUnknownRule(self, rule):
    550          """
    551          serializes CSSUnknownRule
    552          anything until ";" or "{...}"
    553          + CSSComments
    554          """
    555          if rule.wellformed:
    556              out = Out(self)
    557              out.append(rule.atkeyword)
    558              stacks = []
    559              for item in rule.seq:
    560                  typ, val = item.type, item.value
    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                      else:
    570                          val = self._indentblock(val, min(1, len(stacks)+1))
    571                  # APPEND
    572                  if stacks:
    573                      stacks[-1].append(val, typ)
    574                  else:
    575                      out.append(val, typ)
    576
    577                  # POST
    578                  if u'{' == val:
    579                      # new stack level
    580                      stacks.append(Out(self))
    581
    582              return out.value()
    583          else:
    584              return u''
    585
    586 -    def do_CSSStyleRule(self, rule):
    587          """
    588          serializes CSSStyleRule
    589
    590          selectorList
    591          style
    592
    593          + CSSComments
    594          """
    595          # TODO: use Out()
    596
    597          # prepare for element nested rules
    598          # TODO: sort selectors!
    599          if self.prefs.indentSpecificities:
    600              # subselectorlist?
    601              elements = set([s.element for s in rule.selectorList])
    602              specitivities = [s.specificity for s in rule.selectorList]
    603              for selector in self._selectors:
    604                  lastelements = set([s.element for s in selector])
    605                  if elements.issubset(lastelements):
    606                      # higher specificity?
    607                      lastspecitivities = [s.specificity for s in selector]
    608                      if specitivities > lastspecitivities:
    609                          self._selectorlevel += 1
    610                          break
    611                  elif self._selectorlevel > 0:
    612                      self._selectorlevel -= 1
    613              else:
    614                  # save new reference
    615                  self._selectors.append(rule.selectorList)
    616                  self._selectorlevel = 0
    617
    618          # TODO ^ RESOLVE!!!!
    619
    620          selectorText = self.do_css_SelectorList(rule.selectorList)
    621          if not selectorText or not rule.wellformed:
    622              return u''
    623          self._level += 1
    624          styleText = u''
    625          try:
    626              styleText = self.do_css_CSSStyleDeclaration(rule.style)
    627          finally:
    628              self._level -= 1
    629          if not styleText:
    630                  if self.prefs.keepEmptyRules:
    631                      return u'%s%s{}' % (selectorText,
    632                                          self.prefs.paranthesisSpacer)
    633          else:
    634              return self._indentblock(
    635                  u'%s%s{%s%s%s%s}' % (
    636                      selectorText,
    637                      self.prefs.paranthesisSpacer,
    638                      self.prefs.lineSeparator,
    639                      self._indentblock(styleText, self._level + 1),
    640                      self.prefs.lineSeparator,
    641                      (self._level + 1) * self.prefs.indent),
    642                  self._selectorlevel)
    643
    644 -    def do_css_SelectorList(self, selectorlist):
    645          "comma-separated list of Selectors"
    646          # does not need Out() as it is too simple
    647          if selectorlist.wellformed:
    648              out = []
    649              for part in selectorlist.seq:
    650                  if isinstance(part, cssutils.css.Selector):
    651                      out.append(part.selectorText)
    652                  else:
    653                      out.append(part) # should not happen
    654              sep = u',%s' % self.prefs.listItemSpacer
    655              return sep.join(out)
    656          else:
    657              return u''
    658
    659 -    def do_css_Selector(self, selector):
    660          """
    661          a single Selector including comments
    662
    663          an element has syntax (namespaceURI, name) where namespaceURI may be:
    664
    665          - cssutils._ANYNS => ``*|name``
    666          - None => ``name``
    667          - u'' => ``|name``
    668          - any other value: => ``prefix|name``
    669          """
    670          if selector.wellformed:
    671              out = Out(self)
    672
    673              DEFAULTURI = selector._namespaces.get('', None)
    674              for item in selector.seq:
    675                  typ, val = item.type, item.value
    676                  if type(val) == tuple:
    677                      # namespaceURI|name (element or attribute)
    678                      namespaceURI, name = val
    679                      if DEFAULTURI == namespaceURI or (not DEFAULTURI and
    680                                                        namespaceURI is None):
    681                          out.append(name, typ, space=False)
    682                      else:
    683                          if namespaceURI == cssutils._ANYNS:
    684                              prefix = u'*'
    685                          else:
    686                              try:
    687                                  prefix = selector._namespaces.prefixForNamespaceURI(
    688                                                      namespaceURI)
    689                              except IndexError:
    690                                  prefix = u''
    691
    692                          out.append(u'%s|%s' % (prefix, name), typ, space=False)
    693                  else:
    694                      out.append(val, typ, space=False, keepS=True)
    695
    696              return out.value()
    697          else:
    698              return u''
    699
    700 -    def do_css_CSSStyleDeclaration(self, style, separator=None):
    701          """
    702          Style declaration of CSSStyleRule
    703          """
    704  #        # TODO: use Out()
    705
    706          # may be comments only
    707          if len(style.seq) > 0:
    708              if separator is None:
    709                  separator = self.prefs.lineSeparator
    710
    711              if self.prefs.keepAllProperties:
    712                  # all
    713                  seq = style.seq
    714              else:
    715                  # only effective ones
    716                  _effective = style.getProperties()
    717                  seq = [item for item in style.seq
    718                           if (isinstance(item.value, cssutils.css.Property)
    719                               and item.value in _effective)
    720                           or not isinstance(item.value, cssutils.css.Property)]
    721
    722              out = []
    723              for i, item in enumerate(seq):
    724                  typ, val = item.type, item.value
    725                  if isinstance(val, cssutils.css.CSSComment):
    726                      # CSSComment
    727                      if self.prefs.keepComments:
    728                          out.append(val.cssText)
    729                          out.append(separator)
    730                  elif isinstance(val, cssutils.css.Property):
    731                      # PropertySimilarNameList
    732                      out.append(self.do_Property(val))
    733                      if not (self.prefs.omitLastSemicolon and i==len(seq)-1):
    734                          out.append(u';')
    735                      out.append(separator)
    736                  elif isinstance(val, cssutils.css.CSSUnknownRule):
    737                      # @rule
    738                      out.append(val.cssText)
    739                      out.append(separator)
    740                  else:
    741                      # ?
    742                      out.append(val)
    743                      out.append(separator)
    744
    745              if out and out[-1] == separator:
    746                  del out[-1]
    747
    748              return u''.join(out)
    749
    750          else:
    751              return u''
    752
    753 -    def do_Property(self, property):
    754          """
    755          Style declaration of CSSStyleRule
    756
    757          Property has a seqs attribute which contains seq lists for
    758          name, a CSSvalue and a seq list for priority
    759          """
    760          # TODO: use Out()
    761
    762          out = []
    763          if property.seqs[0] and property.wellformed and self._valid(property):
    764              nameseq, cssvalue, priorityseq = property.seqs
    765
    766              #name
    767              for part in nameseq:
    768                  if hasattr(part, 'cssText'):
    769                      out.append(part.cssText)
    770                  elif property.literalname == part:
    771                      out.append(self._propertyname(property, part))
    772                  else:
    773                      out.append(part)
    774
    775              if out and (not property._mediaQuery or
    776                          property._mediaQuery and cssvalue.cssText):
    777                  # MediaQuery may consist of name only
    778                  out.append(u':')
    779                  out.append(self.prefs.propertyNameSpacer)
    780
    781              # value
    782              out.append(cssvalue.cssText)
    783
    784              # priority
    785              if out and priorityseq:
    786                  out.append(u' ')
    787                  for part in priorityseq:
    788                      if hasattr(part, 'cssText'): # comments
    789                          out.append(part.cssText)
    790                      else:
    791                          if part == property.literalpriority and\
    792                             self.prefs.defaultPropertyPriority:
    793                              out.append(property.priority)
    794                          else:
    795                              out.append(part)
    796
    797          return u''.join(out)
    798
    799 -    def do_Property_priority(self, priorityseq):
    800          """
    801          a Properties priority "!" S* "important"
    802          """
    803          # TODO: use Out()
    804
    805          out = []
    806          for part in priorityseq:
    807              if hasattr(part, 'cssText'): # comments
    808                  out.append(u' ')
    809                  out.append(part.cssText)
    810                  out.append(u' ')
    811              else:
    812                  out.append(part)
    813          return u''.join(out).strip()
    814
    815 -    def do_css_CSSValue(self, cssvalue):
    816          """
    817          serializes a CSSValue
    818          """
    819          # TODO: use Out()
    820          # TODO: use self._valid(cssvalue)?
    821
    822          if not cssvalue:
    823              return u''
    824          else:
    825              sep = u',%s' % self.prefs.listItemSpacer
    826              out = []
    827              for part in cssvalue.seq:
    828                  if hasattr(part, 'cssText'):
    829                      # comments or CSSValue if a CSSValueList
    830                      out.append(part.cssText)
    831                  elif isinstance(part, basestring) and part == u',':
    832                      out.append(sep)
    833                  else:
    834                      # TODO: escape func parameter if STRING!
    835                      if part and part[0] == part[-1] and part[0] in '\'"':
    836                          # string has " " around it in CSSValue!
    837                          part = self._string(part[1:-1])
    838                      out.append(part)
    839              return (u''.join(out)).strip()
    840
    841 -    def do_stylesheets_medialist(self, medialist):
    842          """
    843          comma-separated list of media, default is 'all'
    844
    845          If "all" is in the list, every other media *except* "handheld" will
    846          be stripped. This is because how Opera handles CSS for PDAs.
    847          """
    848          if len(medialist) == 0:
    849              return u'all'
    850          else:
    851              sep = u',%s' % self.prefs.listItemSpacer
    852              return sep.join((mq.mediaText for mq in medialist))
    853
    854 -    def do_stylesheets_mediaquery(self, mediaquery):
    855          """
    856          a single media used in medialist
    857          """
    858          if mediaquery.wellformed:
    859              out = []
    860              for part in mediaquery.seq:
    861                  if isinstance(part, cssutils.css.Property): # Property
    862                      out.append(u'(%s)' % part.cssText)
    863                  elif hasattr(part, 'cssText'): # comments
    864                      out.append(part.cssText)
    865                  else:
    866                      # TODO: media queries!
    867                      out.append(part)
    868              return u' '.join(out)
    869          else:
    870              return u''
    871