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