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