"""CSSMediaRule implements DOM Level 2 CSS CSSMediaRule.""" __all__ = ['CSSMediaRule'] __docformat__ = 'restructuredtext' __version__ = '$Id$' import cssrule import cssutils import xml.dom class CSSMediaRule(cssrule.CSSRuleRules): """ Objects implementing the CSSMediaRule interface can be identified by the MEDIA_RULE constant. On these objects the type attribute must return the value of that constant. Format:: : MEDIA_SYM S* medium [ COMMA S* medium ]* STRING? # the name LBRACE S* ruleset* '}' S*; ``cssRules`` All Rules in this media rule, a :class:`~cssutils.css.CSSRuleList`. """ def __init__(self, mediaText='all', name=None, parentRule=None, parentStyleSheet=None, readonly=False): """constructor""" super(CSSMediaRule, self).__init__(parentRule=parentRule, parentStyleSheet=parentStyleSheet) self._atkeyword = u'@media' # 1. media if mediaText: self.media = mediaText else: self.media = cssutils.stylesheets.MediaList() self.name = name self._readonly = readonly def __repr__(self): return u"cssutils.css.%s(mediaText=%r)" % ( self.__class__.__name__, self.media.mediaText) def __str__(self): return u"" % ( self.__class__.__name__, self.media.mediaText, id(self)) def _getCssText(self): """Return serialized property cssText.""" return cssutils.ser.do_CSSMediaRule(self) def _setCssText(self, cssText): """ :param cssText: a parseable string or a tuple of (cssText, dict-of-namespaces) :Exceptions: - :exc:`~xml.dom.NamespaceErr`: Raised if a specified selector uses an unknown namespace prefix. - :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. """ # media "name"? { cssRules } super(CSSMediaRule, self)._setCssText(cssText) # might be (cssText, namespaces) cssText, namespaces = self._splitNamespacesOff(cssText) try: # use parent style sheet ones if available namespaces = self.parentStyleSheet.namespaces except AttributeError: pass tokenizer = self._tokenize2(cssText) attoken = self._nexttoken(tokenizer, None) if self._type(attoken) != self._prods.MEDIA_SYM: self._log.error(u'CSSMediaRule: No CSSMediaRule found: %s' % self._valuestr(cssText), error=xml.dom.InvalidModificationErr) else: # save if parse goes wrong oldMedia = self._media oldName = self._name oldCssRules = self._cssRules ok = True # media mediatokens, end = self._tokensupto2(tokenizer, mediaqueryendonly=True, separateEnd=True) if u'{' == self._tokenvalue(end)\ or self._prods.STRING == self._type(end): self.media = cssutils.stylesheets.MediaList(parentRule=self) # TODO: remove special case self.media.mediaText = mediatokens ok = ok and self.media.wellformed else: ok = False # name (optional) name = None nameseq = self._tempSeq() if self._prods.STRING == self._type(end): name = self._stringtokenvalue(end) # TODO: for now comments are lost after name nametokens, end = self._tokensupto2(tokenizer, blockstartonly=True, separateEnd=True) wellformed, expected = self._parse(None, nameseq, nametokens, {}) if not wellformed: ok = False self._log.error(u'CSSMediaRule: Syntax Error: %s' % self._valuestr(cssText)) # check for { if u'{' != self._tokenvalue(end): self._log.error(u'CSSMediaRule: No "{" found: %s' % self._valuestr(cssText)) return # cssRules cssrulestokens, braceOrEOF = self._tokensupto2(tokenizer, mediaendonly=True, separateEnd=True) nonetoken = self._nexttoken(tokenizer, None) if 'EOF' == self._type(braceOrEOF): # HACK!!! # TODO: Not complete, add EOF to rule and } to @media cssrulestokens.append(braceOrEOF) braceOrEOF = ('CHAR', '}', 0, 0) self._log.debug(u'CSSMediaRule: Incomplete, adding "}".', token=braceOrEOF, neverraise=True) if u'}' != self._tokenvalue(braceOrEOF): self._log.error(u'CSSMediaRule: No "}" found.', token=braceOrEOF) elif nonetoken: self._log.error(u'CSSMediaRule: Trailing content found.', token=nonetoken) else: # for closures: must be a mutable new = {'wellformed': True } def COMMENT(expected, seq, token, tokenizer=None): self.insertRule(cssutils.css.CSSComment([token], parentRule=self, parentStyleSheet=self.parentStyleSheet)) return expected def ruleset(expected, seq, token, tokenizer): rule = cssutils.css.CSSStyleRule(parentRule=self, parentStyleSheet=self.parentStyleSheet) rule.cssText = self._tokensupto2(tokenizer, token) if rule.wellformed: self.insertRule(rule) return expected def atrule(expected, seq, token, tokenizer): # TODO: get complete rule! tokens = self._tokensupto2(tokenizer, token) atval = self._tokenvalue(token) if atval in ('@charset ', '@font-face', '@import', '@namespace', '@page', '@media', '@variables'): self._log.error(u'CSSMediaRule: This rule is not ' u'allowed in CSSMediaRule - ignored: ' u'%s.' % self._valuestr(tokens), token = token, error=xml.dom.HierarchyRequestErr) else: rule = cssutils.css.CSSUnknownRule(tokens, parentRule=self, parentStyleSheet=self.parentStyleSheet) if rule.wellformed: self.insertRule(rule) return expected # save for possible reset oldCssRules = self.cssRules self.cssRules = cssutils.css.CSSRuleList() seq = [] # not used really tokenizer = iter(cssrulestokens) wellformed, expected = self._parse(braceOrEOF, seq, tokenizer, { 'COMMENT': COMMENT, 'CHARSET_SYM': atrule, 'FONT_FACE_SYM': atrule, 'IMPORT_SYM': atrule, 'NAMESPACE_SYM': atrule, 'PAGE_SYM': atrule, 'MEDIA_SYM': atrule, 'ATKEYWORD': atrule }, default=ruleset, new=new) ok = ok and wellformed if ok: self.name = name self._setSeq(nameseq) else: self._media = oldMedia self._cssRules = oldCssRules cssText = property(_getCssText, _setCssText, doc=u"(DOM) The parsable textual representation of this " u"rule.") def _setName(self, name): if isinstance(name, basestring) or name is None: # "" or '' if not name: name = None self._name = name else: self._log.error(u'CSSImportRule: Not a valid name: %s' % name) name = property(lambda self: self._name, _setName, doc=u"An optional name for this media rule.") def _setMedia(self, media): """ :param media: a :class:`~cssutils.stylesheets.MediaList` or string """ self._checkReadonly() if isinstance(media, basestring): self._media = cssutils.stylesheets.MediaList(mediaText=media, parentRule=self) else: media._parentRule = self self._media = media # NOT IN @media seq at all?! # # update seq # for i, item in enumerate(self.seq): # if item.type == 'media': # self._seq[i] = (self._media, 'media', None, None) # break # else: # # insert after @media if not in seq at all # self.seq.insert(0, # self._media, 'media', None, None) media = property(lambda self: self._media, _setMedia, doc=u"(DOM) A list of media types for this rule " u"of type :class:`~cssutils.stylesheets.MediaList`.") 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, cssutils.css.CSSPageRule) or \ isinstance(rule, cssutils.css.MarginRule) or \ isinstance(rule, 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) type = property(lambda self: self.MEDIA_RULE, doc=u"The type of this rule, as defined by a CSSRule " u"type constant.") wellformed = property(lambda self: self.media.wellformed)