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