Package cssutils :: Module util |
[hide private]
[frames] | no frames]

# Source Code for Module cssutils.util

      1  """base classes for css and stylesheets packages
      2  """
      3  __all__ = []
      4  __docformat__ = 'restructuredtext'
      5  __author__ = '$LastChangedBy: cthedot $'
      6  __date__ = '$LastChangedDate: 2008-01-13 00:06:08 +0100 (So, 13 Jan 2008) $'
      7  __version__ = '$LastChangedRevision: 838 $'
      8
      9  import re
     10  import types
     11  import xml.dom
     12  import cssutils
     13  from tokenize2 import Tokenizer
     14
     15 -class ListSeq(object):
     16      """
     17      (EXPERIMENTAL)
     18      A base class used for list classes like css.SelectorList or
     19      stylesheets.MediaList
     20
     21      adds list like behaviour running on inhering class' property ``seq``
     22
     23      - item in x => bool
     24      - len(x) => integer
     25      - get, set and del x[i]
     26      - for item in x
     27      - append(item)
     28
     29      some methods must be overwritten in inheriting class
     30      """
     31 -    def __init__(self):
     32          self.seq = [] # does not need to use ``Seq`` as simple list only
     33
     34 -    def __contains__(self, item):
     35          return item in self.seq
     36
     37 -    def __delitem__(self, index):
     38          del self.seq[index]
     39
     40 -    def __getitem__(self, index):
     41          return self.seq[index]
     42
     43 -    def __iter__(self):
     44          return iter(self.seq)
     45
     46 -    def __len__(self):
     47          return len(self.seq)
     48
     49 -    def __setitem__(self, index, item):
     50          "must be overwritten"
     51          raise NotImplementedError
     52
     53 -    def append(self, item):
     54          "must be overwritten"
     55          raise NotImplementedError
     56
     57
     58 -class Base(object):
     59      """
     60      Base class for most CSS and StyleSheets classes
     61
     62      **Superceded by Base2 which is used for new seq handling class.**
     63      See cssutils.util.Base2
     64
     65      Contains helper methods for inheriting classes helping parsing
     66
     67      ``_normalize`` is static as used by Preferences.
     68      """
     69      __tokenizer2 = Tokenizer()
     70      _log = cssutils.log
     71      _prods = cssutils.tokenize2.CSSProductions
     72
     73      # for more on shorthand properties see
     74      # http://www.dustindiaz.com/css-shorthand/
     75      # format: shorthand: [(propname, mandatorycheck?)*]
     76      _SHORTHANDPROPERTIES = {
     77              u'background': [],
     78              u'border': [],
     79              u'border-left': [],
     80              u'border-right': [],
     81              u'border-top': [],
     82              u'border-bottom': [],
     83              u'border-color': [],
     84              u'border-style': [],
     85              u'border-width': [],
     86              u'cue': [],
     87              u'font': [],
     88  #                      [('font-weight', True),
     89  #                      ('font-size', True),
     90  #                      ('line-height', False),
     91  #                      ('font-family', True)],
     92              u'list-style': [],
     93              u'margin': [],
     94              u'outline': [],
     95              u'padding': [],
     96              u'pause': []
     97              }
     98
     99      # simple escapes, all non unicodes
    100      __escapes = re.compile(ur'(\\[^0-9a-fA-F])').sub
    101      # all unicode (see cssproductions "unicode")
    102      __unicodes = re.compile(ur'\\[0-9a-fA-F]{1,6}[\t|\r|\n|\f|\x20]?').sub
    103
    104      @staticmethod
    105 -    def _normalize(x):
    106          """
    107          normalizes x, namely:
    108
    109          - remove any \ before non unicode sequences (0-9a-zA-Z) so for
    110            x=="c\olor\" return "color" (unicode escape sequences should have
    111            been resolved by the tokenizer already)
    112          - lowercase
    113          """
    114          if x:
    115              def removeescape(matchobj):
    116                  return matchobj.group(0)[1:]
    117              x = Base.__escapes(removeescape, x)
    118              return x.lower()
    119          else:
    120              return x
    121
    122 -    def _checkReadonly(self):
    123          "raises xml.dom.NoModificationAllowedErr if rule/... is readonly"
    124          if hasattr(self, '_readonly') and self._readonly:
    125              raise xml.dom.NoModificationAllowedErr(
    126                  u'%s is readonly.' % self.__class__)
    127              return True
    128          return False
    129
    130 -    def _tokenize2(self, textortokens):
    131          """
    132          returns tokens of textortokens which may already be tokens in which
    133          case simply returns input
    134          """
    135          if not textortokens:
    136              return None
    137          elif isinstance(textortokens, basestring):
    138              # needs to be tokenized
    139              return self.__tokenizer2.tokenize(
    140                   textortokens)
    141          elif types.GeneratorType == type(textortokens):
    142              # already tokenized
    143              return textortokens
    144          elif isinstance(textortokens, tuple):
    145              # a single token (like a comment)
    146              return [textortokens]
    147          else:
    148              # already tokenized but return generator
    149              return (x for x in textortokens)
    150
    151 -    def _nexttoken(self, tokenizer, default=None):
    152          "returns next token in generator tokenizer or the default value"
    153          try:
    154              return tokenizer.next()
    155          except (StopIteration, AttributeError):
    156              return default
    157
    158 -    def _type(self, token):
    159          "returns type of Tokenizer token"
    160          if token:
    161              return token[0]
    162          else:
    163              return None
    164
    165 -    def _tokenvalue(self, token, normalize=False):
    166          "returns value of Tokenizer token"
    167          if token and normalize:
    168              return Base._normalize(token[1])
    169          elif token:
    170              return token[1]
    171          else:
    172              return None
    173
    174 -    def _tokensupto2(self,
    175                       tokenizer,
    176                       starttoken=None,
    177                       blockstartonly=False,
    178                       blockendonly=False,
    179                       mediaendonly=False,
    180                       semicolon=False,
    181                       propertynameendonly=False,
    182                       propertyvalueendonly=False,
    183                       propertypriorityendonly=False,
    184                       selectorattendonly=False,
    185                       funcendonly=False,
    186                       listseponly=False, # ,
    187                       keepEnd=True,
    188                       keepEOF=True):
    189          """
    190          returns tokens upto end of atrule and end index
    191          end is defined by parameters, might be ; } ) or other
    192
    193          default looks for ending "}" and ";"
    194          """
    195          ends = u';}'
    196          brace = bracket = parant = 0 # {}, [], ()
    197
    198          if blockstartonly: # {
    199              ends = u'{'
    200              brace = -1 # set to 0 with first {
    201          elif blockendonly: # }
    202              ends = u'}'
    203          elif mediaendonly: # }
    204              ends = u'}'
    205              brace = 1 # rules } and mediarules }
    206          elif semicolon:
    207              ends = u';'
    208          elif propertynameendonly: # : and ; in case of an error
    209              ends = u':;'
    210          elif propertyvalueendonly: # ; or !important
    211              ends = (u';', u'!')
    212          elif propertypriorityendonly: # ;
    213              ends = u';'
    214          elif selectorattendonly: # ]
    215              ends = u']'
    216              if starttoken and self._tokenvalue(starttoken) == u'[':
    217                  bracket = 1
    218          elif funcendonly: # )
    219              ends = u')'
    220              parant = 1
    221          elif listseponly: # ,
    222              ends = u','
    223
    224          resulttokens = []
    225          if starttoken:
    226              resulttokens.append(starttoken)
    227          if tokenizer:
    228              for token in tokenizer:
    229                  typ, val, line, col = token
    230                  if 'EOF' == typ:
    231                      if keepEOF and keepEnd:
    232                          resulttokens.append(token)
    233                      break
    234                  if u'{' == val:
    235                      brace += 1
    236                  elif u'}' == val:
    237                      brace -= 1
    238                  elif u'[' == val:
    239                      bracket += 1
    240                  elif u']' == val:
    241                      bracket -= 1
    242                  # function( or single (
    243                  elif u'(' == val or \
    244                       Base._prods.FUNCTION == typ:
    245                      parant += 1
    246                  elif u')' == val:
    247                      parant -= 1
    248
    249                  if val in ends and (brace == bracket == parant == 0):
    250                      if keepEnd:
    251                          resulttokens.append(token)
    252                      break
    253                  else:
    254                      resulttokens.append(token)
    255
    256          return resulttokens
    257
    258 -    def _valuestr(self, t):
    259          """
    260          returns string value of t (t may be a string, a list of token tuples
    261          or a single tuple in format (type, value, line, col).
    262          Mainly used to get a string value of t for error messages.
    263          """
    264          if not t:
    265              return u''
    266          elif isinstance(t, basestring):
    267              return t
    268          else:
    269              return u''.join([x[1] for x in t])
    270
    271 -    def __adddefaultproductions(self, productions):
    272          """
    273          adds default productions if not already present, used by
    274          _parse only
    275
    276          each production should return the next expected token
    277          normaly a name like "uri" or "EOF"
    278          some have no expectation like S or COMMENT, so simply return
    279          the current value of self.__expected
    280          """
    281          def ATKEYWORD(expected, seq, token, tokenizer=None):
    282              "TODO: add default impl for unexpected @rule?"
    283              return expected
    284
    285          def COMMENT(expected, seq, token, tokenizer=None):
    286              "default implementation for COMMENT token adds CSSCommentRule"
    287              seq.append(cssutils.css.CSSComment([token]))
    288              return expected
    289
    290          def S(expected, seq, token, tokenizer=None):
    291              "default implementation for S token, does nothing"
    292              return expected
    293
    294          def EOF(expected=None, seq=None, token=None, tokenizer=None):
    295              "default implementation for EOF token"
    296              return 'EOF'
    297
    298          p = {'ATKEYWORD': ATKEYWORD,
    299               'COMMENT': COMMENT,
    300               'S': S,
    301               'EOF': EOF # only available if fullsheet
    302               }
    303          p.update(productions)
    304          return p
    305
    306 -    def _parse(self, expected, seq, tokenizer, productions, default=None):
    307          """
    308          puts parsed tokens in seq by calling a production with
    309              (seq, tokenizer, token)
    310
    311          expected
    312              a name what token or value is expected next, e.g. 'uri'
    313          seq
    314              to add rules etc to
    315          tokenizer
    316              call tokenizer.next() to get next token
    317          productions
    318              callbacks {tokentype: callback}
    319          default
    320              default callback if tokentype not in productions
    321
    322          returns (wellformed, expected) which the last prod might have set
    323          """
    324          wellformed = True
    325          if tokenizer:
    326              prods = self.__adddefaultproductions(productions)
    327              for token in tokenizer:
    328                  p = prods.get(token[0], default)
    329                  if p:
    330                      expected = p(expected, seq, token, tokenizer)
    331                  else:
    332                      wellformed = False
    333                      self._log.error(u'Unexpected token (%s, %s, %s, %s)' % token)
    334
    335          return wellformed, expected
    336
    337
    338 -class Seq(object):
    339      """
    340      property seq of Base2 inheriting classes, holds a list of Item objects.
    341
    342      used only by Selector for now
    343
    344      is normally readonly, only writable during parsing
    345      """
    346 -    def __init__(self, readonly=True):
    347          """
    348          only way to write to a Seq is to initialize it with new items
    349          each itemtuple has (value, type, line) where line is optional
    350          """
    351          self.__seq = []
    352          self._readonly = readonly
    353
    354 -    def __delitem__(self, i):
    355          del self.__seq[i]
    356
    357 -    def __getitem__(self, i):
    358          return self.__seq[i]
    359
    360 -    def __iter__(self):
    361          return iter(self.__seq)
    362
    363 -    def __len__(self):
    364          return len(self.__seq)
    365
    366 -    def append(self, val, typ, line=None):
    367          "if not readonly add new Item()"
    368          if self._readonly:
    369              raise AttributeError('Seq is readonly.')
    370          else:
    371              self.__seq.append(Item(val, typ, line))
    372
    373 -    def replace(self, index=-1, val=None, typ=None, line=None):
    374          """
    375          if not readonly replace Item at index with new Item or
    376          simply replace value or type
    377          """
    378          if self._readonly:
    379              raise AttributeError('Seq is readonly.')
    380          else:
    381              self.__seq[index] = Item(val, typ, line)
    382
    383 -    def __repr__(self):
    384          "returns a repr same as a list of tuples of (value, type)"
    385          return u'cssutils.%s.%s([\n    %s])' % (self.__module__,
    386                                            self.__class__.__name__,
    387              u',\n    '.join([u'(%r, %r)' % (item.type, item.value)
    388                            for item in self.__seq]
    389              ))
    390 -    def __str__(self):
    391          return "<cssutils.%s.%s object length=%r at 0x%x>" % (
    392                  self.__module__, self.__class__.__name__, len(self), id(self))
    393
    394 -class Item(object):
    395      """
    396      an item in the seq list of classes (successor to tuple items in old seq)
    397
    398      each item has attributes:
    399
    400      type
    401          a sematic type like "element", "attribute"
    402      value
    403          the actual value which may be a string, number etc or an instance
    404          of e.g. a CSSComment
    405      *line*
    406          **NOT IMPLEMENTED YET, may contain the line in the source later**
    407      """
    408 -    def __init__(self, val, typ, line=None):
    409          self.__value = val
    410          self.__type = typ
    411          self.__line = line
    412
    413      type = property(lambda self: self.__type)
    414      value = property(lambda self: self.__value)
    415      line = property(lambda self: self.__line)
    416
    417
    418 -class Base2(Base):
    419      """
    420      Base class for new seq handling, used by Selector for now only
    421      """
    422 -    def __init__(self):
    423          self._seq = Seq()
    424
    425 -    def _setSeq(self, newseq):
    426          """
    427          sets newseq and makes it readonly
    428          """
    429          newseq._readonly = True
    430          self._seq = newseq
    431
    432      seq = property(fget=lambda self: self._seq,
    433                     fset=_setSeq,
    434                     doc="seq for most classes")
    435
    436 -    def _tempSeq(self, readonly=False):
    437          "get a writeable Seq() which is added later"
    438          return Seq(readonly=readonly)
    439
    440
    441 -class Deprecated(object):
    442      """This is a decorator which can be used to mark functions
    443      as deprecated. It will result in a warning being emitted
    444      when the function is used.
    445
    446      It accepts a single paramter ``msg`` which is shown with the warning.
    447      It should contain information which function or method to use instead.
    448      """
    449 -    def __init__(self, msg):
    450          self.msg = msg
    451
    452 -    def __call__(self, func):
    453          def newFunc(*args, **kwargs):
    454              import warnings
    455              warnings.warn("Call to deprecated method %r. %s" %
    456                              (func.__name__, self.msg),
    457                              category=DeprecationWarning,
    458                              stacklevel=2)
    459              return func(*args, **kwargs)
    460          newFunc.__name__ = func.__name__
    461          newFunc.__doc__ = func.__doc__
    462          newFunc.__dict__.update(func.__dict__)
    463          return newFunc
    464