Package cssutils :: Package css :: Module cssstylesheet |
[hide private]
[frames] | no frames]

# Source Code for Module cssutils.css.cssstylesheet

      1  """
      2  CSSStyleSheet implements DOM Level 2 CSS CSSStyleSheet.
      3
      4  Partly also:
      5      - http://dev.w3.org/csswg/cssom/#the-cssstylesheet
      6      - http://www.w3.org/TR/2006/WD-css3-namespace-20060828/
      7
      8  TODO:
      9      - ownerRule and ownerNode
     10  """
     11  __all__ = ['CSSStyleSheet']
     12  __docformat__ = 'restructuredtext'
     13  __author__ = '$LastChangedBy: cthedot $'
     14  __date__ = '$LastChangedDate: 2008-02-12 23:43:05 +0100 (Di, 12 Feb 2008) $'
     15  __version__ = '$LastChangedRevision: 1049 $'
     16
     17  import xml.dom
     18  import cssutils.stylesheets
     19  from cssutils.util import _Namespaces, _SimpleNamespaces
     20
     21 -class CSSStyleSheet(cssutils.stylesheets.StyleSheet):
     22      """
     23      The CSSStyleSheet interface represents a CSS style sheet.
     24
     25      Properties
     26      ==========
     27      CSSOM
     28      -----
     29      cssRules
     30          of type CSSRuleList, (DOM readonly)
     31      encoding
     32          reflects the encoding of an @charset rule or 'utf-8' (default)
     33          if set to ``None``
     34      ownerRule
     35          of type CSSRule, readonly (NOT IMPLEMENTED YET)
     36
     37      Inherits properties from stylesheet.StyleSheet
     38
     39      cssutils
     40      --------
     41      cssText: string
     42          a textual representation of the stylesheet
     43      namespaces
     44          reflects set @namespace rules of this rule.
     45          A dict of {prefix: namespaceURI} mapping.
     46
     47      Format
     48      ======
     49      stylesheet
     50        : [ CHARSET_SYM S* STRING S* ';' ]?
     51          [S|CDO|CDC]* [ import [S|CDO|CDC]* ]*
     52          [ namespace [S|CDO|CDC]* ]* # according to @namespace WD
     53          [ [ ruleset | media | page ] [S|CDO|CDC]* ]*
     54      """
     55
     56      type = 'text/css'
     57
     58 -    def __init__(self, href=None, media=None, title=u'', disabled=None,
     59                   ownerNode=None, parentStyleSheet=None, readonly=False):
     60          """
     61          init parameters are the same as for stylesheets.StyleSheet
     62          """
     63          super(CSSStyleSheet, self).__init__(
     64                  self.type, href, media, title, disabled,
     65                  ownerNode, parentStyleSheet)
     66
     67          self.cssRules = cssutils.css.CSSRuleList()
     68          self.cssRules.append = self.insertRule
     69          self.cssRules.extend = self.insertRule
     70          self._namespaces = _Namespaces(parentStyleSheet=self)
     71          self._readonly = readonly
     72
     73 -    def __iter__(self):
     74          "generator which iterates over cssRules."
     75          for rule in self.cssRules:
     76              yield rule
     77
     78 -    def _cleanNamespaces(self):
     79          "removes all namespace rules with same namespaceURI but last one set"
     80          rules = self.cssRules
     81          namespaceitems = self.namespaces.items()
     82          i = 0
     83          while i < len(rules):
     84              rule = rules[i]
     85              if rule.type == rule.NAMESPACE_RULE and \
     86                 (rule.prefix, rule.namespaceURI) not in namespaceitems:
     87                  self.deleteRule(i)
     88              else:
     89                  i += 1
     90
     91 -    def _getUsedURIs(self):
     92          "returns set of URIs used in the sheet"
     93          useduris = set()
     94          for r1 in self:
     95              if r1.STYLE_RULE == r1.type:
     96                  useduris.update(r1.selectorList._getUsedUris())
     97              elif r1.MEDIA_RULE == r1.type:
     98                  for r2 in r1:
     99                      if r2.type == r2.STYLE_RULE:
    100                          useduris.update(r2.selectorList._getUsedUris())
    101          return useduris
    102
    103 -    def _getCssText(self):
    104          return cssutils.ser.do_CSSStyleSheet(self)
    105
    106 -    def _setCssText(self, cssText):
    107          """
    108          (cssutils)
    109          Parses ``cssText`` and overwrites the whole stylesheet.
    110
    111          :param cssText:
    112              a parseable string or a tuple of (cssText, dict-of-namespaces)
    113          :Exceptions:
    114              - `NAMESPACE_ERR`:
    115                If a namespace prefix is found which is not declared.
    116              - `NO_MODIFICATION_ALLOWED_ERR`: (self)
    117                Raised if the rule is readonly.
    118              - `SYNTAX_ERR`:
    119                Raised if the specified CSS string value has a syntax error and
    120                is unparsable.
    121          """
    122          self._checkReadonly()
    123
    124          cssText, namespaces = self._splitNamespacesOff(cssText)
    125          if not namespaces:
    126              namespaces = _SimpleNamespaces()
    127
    128          tokenizer = self._tokenize2(cssText)
    129          newseq = [] #cssutils.css.CSSRuleList()
    130
    131          # for closures: must be a mutable
    132          new = { 'namespaces': namespaces}
    133
    134          def S(expected, seq, token, tokenizer=None):
    135              # @charset must be at absolute beginning of style sheet
    136              if expected == 0:
    137                  return 1
    138              else:
    139                  return expected
    140
    141          def COMMENT(expected, seq, token, tokenizer=None):
    142              "special: sets parent*"
    143              comment = cssutils.css.CSSComment([token],
    144                                  parentStyleSheet=self.parentStyleSheet)
    145              seq.append(comment)
    146              return expected
    147
    148          def charsetrule(expected, seq, token, tokenizer):
    149              rule = cssutils.css.CSSCharsetRule(parentStyleSheet=self)
    150              rule.cssText = self._tokensupto2(tokenizer, token)
    151              if expected > 0 or len(seq) > 0:
    152                  self._log.error(
    153                      u'CSSStylesheet: CSSCharsetRule only allowed at beginning of stylesheet.',
    154                      token, xml.dom.HierarchyRequestErr)
    155              else:
    156                  if rule.wellformed:
    157                      seq.append(rule)
    158              return 1
    159
    160          def importrule(expected, seq, token, tokenizer):
    161              rule = cssutils.css.CSSImportRule(parentStyleSheet=self)
    162              rule.cssText = self._tokensupto2(tokenizer, token)
    163              if expected > 1:
    164                  self._log.error(
    165                      u'CSSStylesheet: CSSImportRule not allowed here.',
    166                      token, xml.dom.HierarchyRequestErr)
    167              else:
    168                  if rule.wellformed:
    169                      seq.append(rule)
    170              return 1
    171
    172          def namespacerule(expected, seq, token, tokenizer):
    173              rule = cssutils.css.CSSNamespaceRule(
    174                                  cssText=self._tokensupto2(tokenizer, token),
    175                                  parentStyleSheet=self)
    176              if expected > 2:
    177                  self._log.error(
    178                      u'CSSStylesheet: CSSNamespaceRule not allowed here.',
    179                      token, xml.dom.HierarchyRequestErr)
    180              else:
    181                  if rule.wellformed:
    182                      seq.append(rule)
    183                      # temporary namespaces given to CSSStyleRule and @media
    184                      new['namespaces'][rule.prefix] = rule.namespaceURI
    185              return 2
    186
    187          def fontfacerule(expected, seq, token, tokenizer):
    188              rule = cssutils.css.CSSFontFaceRule(parentStyleSheet=self)
    189              rule.cssText = self._tokensupto2(tokenizer, token)
    190              if rule.wellformed:
    191                  seq.append(rule)
    192              return 3
    193
    194          def mediarule(expected, seq, token, tokenizer):
    195              rule = cssutils.css.CSSMediaRule()
    196              rule.cssText = (self._tokensupto2(tokenizer, token),
    197                              new['namespaces'])
    198              if rule.wellformed:
    199                  rule._parentStyleSheet=self
    200                  for r in rule:
    201                      r._parentStyleSheet=self
    202                  seq.append(rule)
    203              return 3
    204
    205          def pagerule(expected, seq, token, tokenizer):
    206              rule = cssutils.css.CSSPageRule(parentStyleSheet=self)
    207              rule.cssText = self._tokensupto2(tokenizer, token)
    208              if rule.wellformed:
    209                  seq.append(rule)
    210              return 3
    211
    212          def unknownrule(expected, seq, token, tokenizer):
    213              rule = cssutils.css.CSSUnknownRule(parentStyleSheet=self)
    214              rule.cssText = self._tokensupto2(tokenizer, token)
    215              if rule.wellformed:
    216                  seq.append(rule)
    217              return expected
    218
    219          def ruleset(expected, seq, token, tokenizer):
    220              rule = cssutils.css.CSSStyleRule()
    221              rule.cssText = (self._tokensupto2(tokenizer, token),
    222                              new['namespaces'])
    223              if rule.wellformed:
    224                  rule._parentStyleSheet=self
    225                  seq.append(rule)
    226              return 3
    227
    228          # expected:
    229          # ['CHARSET', 'IMPORT', 'NAMESPACE', ('PAGE', 'MEDIA', ruleset)]
    230          wellformed, expected = self._parse(0, newseq, tokenizer,
    231              {'S': S,
    232               'COMMENT': COMMENT,
    233               'CDO': lambda *ignored: None,
    234               'CDC': lambda *ignored: None,
    235               'CHARSET_SYM': charsetrule,
    236               'FONT_FACE_SYM': fontfacerule,
    237               'IMPORT_SYM': importrule,
    238               'NAMESPACE_SYM': namespacerule,
    239               'PAGE_SYM': pagerule,
    240               'MEDIA_SYM': mediarule,
    241               'ATKEYWORD': unknownrule
    242               },
    243               default=ruleset)
    244
    245          if wellformed:
    246              del self.cssRules[:]
    247              for rule in newseq:
    248                  self.insertRule(rule, _clean=False)
    249              self._cleanNamespaces()
    250
    251      cssText = property(_getCssText, _setCssText,
    252              "(cssutils) a textual representation of the stylesheet")
    253
    254 -    def _setEncoding(self, encoding):
    255          """
    256          sets encoding of charset rule if present or inserts new charsetrule
    257          with given encoding. If encoding if None removes charsetrule if
    258          present.
    259          """
    260          try:
    261              rule = self.cssRules[0]
    262          except IndexError:
    263              rule = None
    264          if rule and rule.CHARSET_RULE == rule.type:
    265              if encoding:
    266                  rule.encoding = encoding
    267              else:
    268                  self.deleteRule(0)
    269          elif encoding:
    270              self.insertRule(cssutils.css.CSSCharsetRule(encoding=encoding), 0)
    271
    272 -    def _getEncoding(self):
    273          "return encoding if @charset rule if given or default of 'utf-8'"
    274          try:
    275              return self.cssRules[0].encoding
    276          except (IndexError, AttributeError):
    277              return 'utf-8'
    278
    279      encoding = property(_getEncoding, _setEncoding,
    280              "(cssutils) reflects the encoding of an @charset rule or 'UTF-8' (default) if set to ``None``")
    281
    282      namespaces = property(lambda self: self._namespaces,
    283                            doc="Namespaces used in this CSSStyleSheet.")
    284
    285 -    def add(self, rule):
    286          """
    287          Adds rule to stylesheet at appropriate position.
    288          Same as ``sheet.insertRule(rule, inOrder=True)``.
    289          """
    290          return self.insertRule(rule, index=None, inOrder=True)
    291
    292 -    def deleteRule(self, index):
    293          """
    294          Used to delete a rule from the style sheet.
    295
    296          :param index:
    297              of the rule to remove in the StyleSheet's rule list. For an
    298              index < 0 **no** INDEX_SIZE_ERR is raised but rules for
    299              normal Python lists are used. E.g. ``deleteRule(-1)`` removes
    300              the last rule in cssRules.
    301          :Exceptions:
    302              - `INDEX_SIZE_ERR`: (self)
    303                Raised if the specified index does not correspond to a rule in
    304                the style sheet's rule list.
    305              - `NAMESPACE_ERR`: (self)
    306                Raised if removing this rule would result in an invalid StyleSheet
    307              - `NO_MODIFICATION_ALLOWED_ERR`: (self)
    308                Raised if this style sheet is readonly.
    309          """
    310          self._checkReadonly()
    311
    312          try:
    313              rule = self.cssRules[index]
    314          except IndexError:
    315              raise xml.dom.IndexSizeErr(
    316                  u'CSSStyleSheet: %s is not a valid index in the rulelist of length %i' % (
    317                  index, self.cssRules.length))
    318          else:
    319              if rule.type == rule.NAMESPACE_RULE:
    320                  # check all namespacerules if used
    321                  uris = [r.namespaceURI for r in self if r.type == r.NAMESPACE_RULE]
    322                  useduris = self._getUsedURIs()
    323                  if rule.namespaceURI in useduris and\
    324                     uris.count(rule.namespaceURI) == 1:
    325                      raise xml.dom.NoModificationAllowedErr(
    326                          u'CSSStyleSheet: NamespaceURI defined in this rule is used, cannot remove.')
    327                      return
    328
    329              rule._parentStyleSheet = None # detach
    330              del self.cssRules[index] # delete from StyleSheet
    331
    332 -    def insertRule(self, rule, index=None, inOrder=False, _clean=True):
    333          """
    334          Used to insert a new rule into the style sheet. The new rule now
    335          becomes part of the cascade.
    336
    337          :Parameters:
    338              rule
    339                  a parsable DOMString, in cssutils also a CSSRule or a
    340                  CSSRuleList
    341              index
    342                  of the rule before the new rule will be inserted.
    343                  If the specified index is equal to the length of the
    344                  StyleSheet's rule collection, the rule will be added to the end
    345                  of the style sheet.
    346                  If index is not given or None rule will be appended to rule
    347                  list.
    348              inOrder
    349                  if True the rule will be put to a proper location while
    350                  ignoring index but without raising HIERARCHY_REQUEST_ERR.
    351                  The resulting index is returned nevertheless
    352          :returns: the index within the stylesheet's rule collection
    353          :Exceptions:
    354              - `HIERARCHY_REQUEST_ERR`: (self)
    355                Raised if the rule cannot be inserted at the specified index
    356                e.g. if an @import rule is inserted after a standard rule set
    357                or other at-rule.
    358              - `INDEX_SIZE_ERR`: (self)
    359                Raised if the specified index is not a valid insertion point.
    360              - `NO_MODIFICATION_ALLOWED_ERR`: (self)
    361                Raised if this style sheet is readonly.
    362              - `SYNTAX_ERR`: (rule)
    363                Raised if the specified rule has a syntax error and is
    364                unparsable.
    365          """
    366          self._checkReadonly()
    367
    368          # check position
    369          if index is None:
    370              index = len(self.cssRules)
    371          elif index < 0 or index > self.cssRules.length:
    372              raise xml.dom.IndexSizeErr(
    373                  u'CSSStyleSheet: Invalid index %s for CSSRuleList with a length of %s.' % (
    374                      index, self.cssRules.length))
    375              return
    376
    377          if isinstance(rule, basestring):
    378              # parse a new rule
    379              tempsheet = CSSStyleSheet()
    380              tempsheet.cssText = (rule, self._namespaces)
    381              if len(tempsheet.cssRules) != 1 or (tempsheet.cssRules and
    382               not isinstance(tempsheet.cssRules[0], cssutils.css.CSSRule)):
    383                  self._log.error(u'CSSStyleSheet: Invalid Rule: %s' % rule)
    384                  return
    385              rule = tempsheet.cssRules[0]
    386              rule._parentStyleSheet = None
    387
    388          elif isinstance(rule, cssutils.css.CSSRuleList):
    389              # insert all rules
    390              for i, r in enumerate(rule):
    391                  self.insertRule(r, index + i)
    392              return index
    393
    394          if not rule.wellformed:
    395              self._log.error(u'CSSStyleSheet: Invalid rules cannot be added.')
    396              return
    397
    398          # CHECK HIERARCHY
    399          # @charset
    400          if rule.type == rule.CHARSET_RULE:
    401              if inOrder:
    402                  index = 0
    403                  # always first and only
    404                  if (self.cssRules and self.cssRules[0].type == rule.CHARSET_RULE):
    405                      self.cssRules[0].encoding = rule.encoding
    406                  else:
    407                      self.cssRules.insert(0, rule)
    408              elif index != 0 or (self.cssRules and
    409                                self.cssRules[0].type == rule.CHARSET_RULE):
    410                  self._log.error(
    411                      u'CSSStylesheet: @charset only allowed once at the beginning of a stylesheet.',
    412                      error=xml.dom.HierarchyRequestErr)
    413                  return
    414              else:
    415                  self.cssRules.insert(index, rule)
    416
    417          # @unknown or comment
    418          elif rule.type in (rule.UNKNOWN_RULE, rule.COMMENT) and not inOrder:
    419              if index == 0 and self.cssRules and\
    420                 self.cssRules[0].type == rule.CHARSET_RULE:
    421                  self._log.error(
    422                      u'CSSStylesheet: @charset must be the first rule.',
    423                      error=xml.dom.HierarchyRequestErr)
    424                  return
    425              else:
    426                  self.cssRules.insert(index, rule)
    427
    428          # @import
    429          elif rule.type == rule.IMPORT_RULE:
    430              if inOrder:
    431                  # automatic order
    432                  if rule.type in (r.type for r in self):
    433                      # find last of this type
    434                      for i, r in enumerate(reversed(self.cssRules)):
    435                          if r.type == rule.type:
    436                              index = len(self.cssRules) - i
    437                              break
    438                  else:
    439                      # find first point to insert
    440                      if self.cssRules and self.cssRules[0].type in (rule.CHARSET_RULE,
    441                                                                     rule.COMMENT):
    442                          index = 1
    443                      else:
    444                          index = 0
    445              else:
    446                  # after @charset
    447                  if index == 0 and self.cssRules and\
    448                     self.cssRules[0].type == rule.CHARSET_RULE:
    449                      self._log.error(
    450                          u'CSSStylesheet: Found @charset at index 0.',
    451                          error=xml.dom.HierarchyRequestErr)
    452                      return
    453                  # before @namespace, @page, @font-face, @media and stylerule
    454                  for r in self.cssRules[:index]:
    455                      if r.type in (r.NAMESPACE_RULE, r.MEDIA_RULE, r.PAGE_RULE,
    456                                    r.STYLE_RULE, r.FONT_FACE_RULE):
    457                          self._log.error(
    458                              u'CSSStylesheet: Cannot insert @import here, found @namespace, @media, @page or CSSStyleRule before index %s.' %
    459                              index,
    460                              error=xml.dom.HierarchyRequestErr)
    461                          return
    462              self.cssRules.insert(index, rule)
    463
    464          # @namespace
    465          elif rule.type == rule.NAMESPACE_RULE:
    466              if inOrder:
    467                  if rule.type in (r.type for r in self):
    468                      # find last of this type
    469                      for i, r in enumerate(reversed(self.cssRules)):
    470                          if r.type == rule.type:
    471                              index = len(self.cssRules) - i
    472                              break
    473                  else:
    474                      # find first point to insert
    475                      for i, r in enumerate(self.cssRules):
    476                          if r.type in (r.MEDIA_RULE, r.PAGE_RULE, r.STYLE_RULE,
    477                                        r.FONT_FACE_RULE):
    478                              index = i # before these
    479                              break
    480              else:
    481                  # after @charset and @import
    482                  for r in self.cssRules[index:]:
    483                      if r.type in (r.CHARSET_RULE, r.IMPORT_RULE):
    484                          self._log.error(
    485                              u'CSSStylesheet: Cannot insert @namespace here, found @charset or @import after index %s.' %
    486                              index,
    487                              error=xml.dom.HierarchyRequestErr)
    488                          return
    489                  # before @media and stylerule
    490                  for r in self.cssRules[:index]:
    491                      if r.type in (r.MEDIA_RULE, r.PAGE_RULE, r.STYLE_RULE,
    492                                    r.FONT_FACE_RULE):
    493                          self._log.error(
    494                              u'CSSStylesheet: Cannot insert @namespace here, found @media, @page or CSSStyleRule before index %s.' %
    495                              index,
    496                              error=xml.dom.HierarchyRequestErr)
    497                          return
    498
    499              if not (rule.prefix in self.namespaces and
    500                 self.namespaces[rule.prefix] == rule.namespaceURI):
    501                  # no doublettes
    502                  self.cssRules.insert(index, rule)
    503                  if _clean:
    504                      self._cleanNamespaces()
    505
    506          # all other
    507          else:
    508              if inOrder:
    509                  # after last of this kind or at end of sheet
    510                  if rule.type in (r.type for r in self):
    511                      # find last of this type
    512                      for i, r in enumerate(reversed(self.cssRules)):
    513                          if r.type == rule.type:
    514                              index = len(self.cssRules) - i
    515                              break
    516                      self.cssRules.insert(index, rule)
    517                  else:
    518                      self.cssRules.append(rule) # to end as no same present
    519              else:
    520                  for r in self.cssRules[index:]:
    521                      if r.type in (r.CHARSET_RULE, r.IMPORT_RULE, r.NAMESPACE_RULE):
    522                          self._log.error(
    523                              u'CSSStylesheet: Cannot insert rule here, found @charset, @import or @namespace before index %s.' %
    524                              index,
    525                              error=xml.dom.HierarchyRequestErr)
    526                          return
    527                  self.cssRules.insert(index, rule)
    528
    529          rule._parentStyleSheet = self
    530          if rule.MEDIA_RULE == rule.type:
    531              for r in rule:
    532                  r._parentStyleSheet = self
    533          return index
    534
    535 -    def _getsetOwnerRuleDummy(self):
    536          """
    537          NOT IMPLEMENTED YET
    538
    539          CSSOM
    540          -----
    541          The ownerRule  attribute, on getting, must return the CSSImportRule
    542          that caused this style sheet to be imported (if any). Otherwise, if
    543          no CSSImportRule caused it to be imported the attribute must return
    544          null.
    545          """
    546          raise NotImplementedError()
    547
    548      ownerRule = property(_getsetOwnerRuleDummy, _getsetOwnerRuleDummy,
    549                           doc="(DOM attribute) NOT IMPLEMENTED YET")
    550
    551 -    def replaceUrls(self, replacer):
    552          """
    553          **EXPERIMENTAL**
    554
    555          Utility method to replace all ``url(urlstring)`` values in
    556          ``CSSImportRules`` and ``CSSStyleDeclaration`` objects (properties).
    557
    558          ``replacer`` must be a function which is called with a single
    559          argument ``urlstring`` which is the current value of url()
    560          excluding ``url(`` and ``)``. It still may have surrounding
    561          single or double quotes though.
    562          """
    563          for importrule in (r for r in self if r.type == r.IMPORT_RULE):
    564              importrule.href = replacer(importrule.href)
    565
    566          def setProperty(v):
    567              if v.CSS_PRIMITIVE_VALUE == v.cssValueType and\
    568                 v.CSS_URI == v.primitiveType:
    569                      v.setStringValue(v.CSS_URI,
    570                                       replacer(v.getStringValue()))
    571
    572          def styleDeclarations(base):
    573              "recurive function to find all CSSStyleDeclarations"
    574              styles = []
    575              if hasattr(base, 'cssRules'):
    576                  for rule in base.cssRules:
    577                      styles.extend(styleDeclarations(rule))
    578              elif hasattr(base, 'style'):
    579                  styles.append(base.style)
    580              return styles
    581
    582          for style in styleDeclarations(self):
    583              for p in style.getProperties(all=True):
    584                  v = p.cssValue
    585                  if v.CSS_VALUE_LIST == v.cssValueType:
    586                      for item in v:
    587                          setProperty(item)
    588                  elif v.CSS_PRIMITIVE_VALUE == v.cssValueType:
    589                      setProperty(v)
    590
    591 -    def setSerializer(self, cssserializer):
    592          """
    593          Sets the global Serializer used for output of all stylesheet
    594          output.
    595          """
    596          if isinstance(cssserializer, cssutils.CSSSerializer):
    597              cssutils.ser = cssserializer
    598          else:
    599              raise ValueError(u'Serializer must be an instance of cssutils.CSSSerializer.')
    600
    601 -    def setSerializerPref(self, pref, value):
    602          """
    603          Sets Preference of CSSSerializer used for output of this
    604          stylesheet. See cssutils.serialize.Preferences for possible
    605          preferences to be set.
    606          """
    607          cssutils.ser.prefs.__setattr__(pref, value)
    608
    609 -    def __repr__(self):
    610          return "cssutils.css.%s(href=%r, title=%r)" % (
    611                  self.__class__.__name__, self.href, self.title)
    612
    613 -    def __str__(self):
    614          return "<cssutils.css.%s object title=%r href=%r encoding=%r "\
    615                 "namespaces=%r at 0x%x>" % (
    616                  self.__class__.__name__, self.title, self.href, self.encoding,
    617                  self.namespaces.namespaces, id(self))
    618