"""CSSPageRule implements DOM Level 2 CSS CSSPageRule.""" __all__ = ['CSSPageRule'] __docformat__ = 'restructuredtext' __version__ = '$Id$' from itertools import chain from cssstyledeclaration import CSSStyleDeclaration from marginrule import MarginRule import cssrule import cssutils import xml.dom class CSSPageRule(cssrule.CSSRuleRules): """ The CSSPageRule interface represents a @page rule within a CSS style sheet. The @page rule is used to specify the dimensions, orientation, margins, etc. of a page box for paged media. Format:: page : PAGE_SYM S* IDENT? pseudo_page? S* '{' S* [ declaration | margin ]? [ ';' S* [ declaration | margin ]? ]* '}' S* ; pseudo_page : ':' [ "left" | "right" | "first" ] ; margin : margin_sym S* '{' declaration [ ';' S* declaration? ]* '}' S* ; margin_sym : TOPLEFTCORNER_SYM | TOPLEFT_SYM | TOPCENTER_SYM | TOPRIGHT_SYM | TOPRIGHTCORNER_SYM | BOTTOMLEFTCORNER_SYM | BOTTOMLEFT_SYM | BOTTOMCENTER_SYM | BOTTOMRIGHT_SYM | BOTTOMRIGHTCORNER_SYM | LEFTTOP_SYM | LEFTMIDDLE_SYM | LEFTBOTTOM_SYM | RIGHTTOP_SYM | RIGHTMIDDLE_SYM | RIGHTBOTTOM_SYM ; `cssRules` contains a list of `MarginRule` objects. """ def __init__(self, selectorText=None, style=None, parentRule=None, parentStyleSheet=None, readonly=False): """ If readonly allows setting of properties in constructor only. :param selectorText: type string :param style: CSSStyleDeclaration for this CSSStyleRule """ super(CSSPageRule, self).__init__(parentRule=parentRule, parentStyleSheet=parentStyleSheet) self._atkeyword = u'@page' self._specificity = (0, 0, 0) tempseq = self._tempSeq() if selectorText: self.selectorText = selectorText tempseq.append(self.selectorText, 'selectorText') else: self._selectorText = self._tempSeq() if style: self.style = style else: self.style = CSSStyleDeclaration() tempseq.append(self.style, 'style') self._setSeq(tempseq) self._readonly = readonly def __repr__(self): return u"cssutils.css.%s(selectorText=%r, style=%r)" % ( self.__class__.__name__, self.selectorText, self.style.cssText) def __str__(self): return (u"") % ( self.__class__.__name__, self.selectorText, self.specificity, self.style.cssText, len(self.cssRules), id(self)) def __contains__(self, margin): """Check if margin is set in the rule.""" return margin in self.keys() def keys(self): "Return list of all set margins (MarginRule)." return list(r.margin for r in self.cssRules) def __getitem__(self, margin): """Retrieve the style (of MarginRule) for `margin` (which must be normalized). """ for r in self.cssRules: if r.margin == margin: return r.style def __setitem__(self, margin, style): """Set the style (of MarginRule) for `margin` (which must be normalized). """ for i, r in enumerate(self.cssRules): if r.margin == margin: r.style = style return i else: return self.add(MarginRule(margin, style)) def __delitem__(self, margin): """Delete the style (the MarginRule) for `margin` (which must be normalized). """ for r in self.cssRules: if r.margin == margin: self.deleteRule(r) def __parseSelectorText(self, selectorText): """ Parse `selectorText` which may also be a list of tokens and returns (selectorText, seq). see _setSelectorText for details """ # for closures: must be a mutable new = {'wellformed': True, 'last-S': False, 'name': 0, 'first': 0, 'lr': 0} specificity = (0, 0, 0) def _char(expected, seq, token, tokenizer=None): # pseudo_page, :left, :right or :first val = self._tokenvalue(token) if not new['last-S'] and expected in ['page', ': or EOF']\ and u':' == val: try: identtoken = tokenizer.next() except StopIteration: self._log.error( u'CSSPageRule selectorText: No IDENT found.', token) else: ival, ityp = self._tokenvalue(identtoken),\ self._type(identtoken) if self._prods.IDENT != ityp: self._log.error(u'CSSPageRule selectorText: Expected ' u'IDENT but found: %r' % ival, token) else: if not ival in (u'first', u'left', u'right'): self._log.warn(u'CSSPageRule: Unknown @page ' u'selector: %r' % (u':'+ival,), neverraise=True) if ival == u'first': new['first'] = 1 else: new['lr'] = 1 seq.append(val + ival, 'pseudo') return 'EOF' return expected else: new['wellformed'] = False self._log.error(u'CSSPageRule selectorText: Unexpected CHAR: %r' % val, token) return expected def S(expected, seq, token, tokenizer=None): "Does not raise if EOF is found." if expected == ': or EOF': # pseudo must directly follow IDENT if given new['last-S'] = True return expected def IDENT(expected, seq, token, tokenizer=None): "" val = self._tokenvalue(token) if 'page' == expected: if self._normalize(val) == u'auto': self._log.error(u'CSSPageRule selectorText: Invalid pagename.', token) else: new['name'] = 1 seq.append(val, 'IDENT') return ': or EOF' else: new['wellformed'] = False self._log.error(u'CSSPageRule selectorText: Unexpected IDENT: ' u'%r' % val, token) return expected def COMMENT(expected, seq, token, tokenizer=None): "Does not raise if EOF is found." seq.append(cssutils.css.CSSComment([token]), 'COMMENT') return expected newseq = self._tempSeq() wellformed, expected = self._parse(expected='page', seq=newseq, tokenizer=self._tokenize2(selectorText), productions={'CHAR': _char, 'IDENT': IDENT, 'COMMENT': COMMENT, 'S': S}, new=new) wellformed = wellformed and new['wellformed'] # post conditions if expected == 'ident': self._log.error( u'CSSPageRule selectorText: No valid selector: %r' % self._valuestr(selectorText)) return wellformed, newseq, (new['name'], new['first'], new['lr']) def __parseMarginAndStyle(self, tokens): "tokens is a list, no generator (yet)" g = iter(tokens) styletokens = [] # new rules until parse done cssRules = [] for token in g: if token[0] == 'ATKEYWORD' and \ self._normalize(token[1]) in MarginRule.margins: # MarginRule m = MarginRule(parentRule=self, parentStyleSheet=self.parentStyleSheet) m.cssText = chain([token], g) # merge if margin set more than once for r in cssRules: if r.margin == m.margin: for p in m.style: r.style.setProperty(p, replace=False) break else: cssRules.append(m) continue # TODO: Properties? styletokens.append(token) return cssRules, styletokens def _getCssText(self): """Return serialized property cssText.""" return cssutils.ser.do_CSSPageRule(self) def _setCssText(self, cssText): """ :exceptions: - :exc:`~xml.dom.SyntaxErr`: Raised if the specified CSS string value has a syntax error and is unparsable. - :exc:`~xml.dom.InvalidModificationErr`: Raised if the specified CSS string value represents a different type of rule than the current one. - :exc:`~xml.dom.HierarchyRequestErr`: Raised if the rule cannot be inserted at this point in the style sheet. - :exc:`~xml.dom.NoModificationAllowedErr`: Raised if the rule is readonly. """ super(CSSPageRule, self)._setCssText(cssText) tokenizer = self._tokenize2(cssText) if self._type(self._nexttoken(tokenizer)) != self._prods.PAGE_SYM: self._log.error(u'CSSPageRule: No CSSPageRule found: %s' % self._valuestr(cssText), error=xml.dom.InvalidModificationErr) else: newStyle = CSSStyleDeclaration(parentRule=self) ok = True selectortokens, startbrace = self._tokensupto2(tokenizer, blockstartonly=True, separateEnd=True) styletokens, braceorEOFtoken = self._tokensupto2(tokenizer, blockendonly=True, separateEnd=True) nonetoken = self._nexttoken(tokenizer) if self._tokenvalue(startbrace) != u'{': ok = False self._log.error(u'CSSPageRule: No start { of style declaration ' u'found: %r' % self._valuestr(cssText), startbrace) elif nonetoken: ok = False self._log.error(u'CSSPageRule: Trailing content found.', token=nonetoken) selok, newselseq, specificity = self.__parseSelectorText(selectortokens) ok = ok and selok val, type_ = self._tokenvalue(braceorEOFtoken),\ self._type(braceorEOFtoken) if val != u'}' and type_ != 'EOF': ok = False self._log.error( u'CSSPageRule: No "}" after style declaration found: %r' % self._valuestr(cssText)) else: if 'EOF' == type_: # add again as style needs it styletokens.append(braceorEOFtoken) # filter pagemargin rules out first cssRules, styletokens = self.__parseMarginAndStyle(styletokens) # SET, may raise: newStyle.cssText = styletokens if ok: self._selectorText = newselseq self._specificity = specificity self.style = newStyle self.cssRules = cssutils.css.CSSRuleList() for r in cssRules: self.cssRules.append(r) cssText = property(_getCssText, _setCssText, doc=u"(DOM) The parsable textual representation of this rule.") def _getSelectorText(self): """Wrapper for cssutils Selector object.""" return cssutils.ser.do_CSSPageRuleSelector(self._selectorText) def _setSelectorText(self, selectorText): """Wrapper for cssutils Selector object. :param selectorText: DOM String, in CSS 2.1 one of - :first - :left - :right - empty :exceptions: - :exc:`~xml.dom.SyntaxErr`: Raised if the specified CSS string value has a syntax error and is unparsable. - :exc:`~xml.dom.NoModificationAllowedErr`: Raised if this rule is readonly. """ self._checkReadonly() # may raise SYNTAX_ERR wellformed, newseq, specificity = self.__parseSelectorText(selectorText) if wellformed: self._selectorText = newseq self._specificity = specificity selectorText = property(_getSelectorText, _setSelectorText, doc=u"(DOM) The parsable textual representation of " u"the page selector for the rule.") def _setStyle(self, style): """ :param style: a CSSStyleDeclaration or string """ self._checkReadonly() if isinstance(style, basestring): self._style = CSSStyleDeclaration(cssText=style, parentRule=self) else: style._parentRule = self self._style = style style = property(lambda self: self._style, _setStyle, doc=u"(DOM) The declaration-block of this rule set, " u"a :class:`~cssutils.css.CSSStyleDeclaration`.") def insertRule(self, rule, index=None): """Implements base ``insertRule``.""" rule, index = self._prepareInsertRule(rule, index) if rule is False or rule is True: # done or error return # check hierarchy if isinstance(rule, cssutils.css.CSSCharsetRule) or \ isinstance(rule, cssutils.css.CSSFontFaceRule) or \ isinstance(rule, cssutils.css.CSSImportRule) or \ isinstance(rule, cssutils.css.CSSNamespaceRule) or \ isinstance(rule, CSSPageRule) or \ isinstance(rule, cssutils.css.CSSMediaRule): self._log.error(u'%s: This type of rule is not allowed here: %s' % (self.__class__.__name__, rule.cssText), error=xml.dom.HierarchyRequestErr) return return self._finishInsertRule(rule, index) specificity = property(lambda self: self._specificity, doc=u"""Specificity of this page rule (READONLY). Tuple of (f, g, h) where: - if the page selector has a named page, f=1; else f=0 - if the page selector has a ':first' pseudo-class, g=1; else g=0 - if the page selector has a ':left' or ':right' pseudo-class, h=1; else h=0 """) type = property(lambda self: self.PAGE_RULE, doc=u"The type of this rule, as defined by a CSSRule " u"type constant.") # constant but needed: wellformed = property(lambda self: True)