1"""CSSMediaRule implements DOM Level 2 CSS CSSMediaRule.""" 2__all__ = ['CSSMediaRule'] 3 4from . import cssrule 5import cssutils 6import xml.dom 7 8 9class CSSMediaRule(cssrule.CSSRuleRules): 10 """ 11 Objects implementing the CSSMediaRule interface can be identified by the 12 MEDIA_RULE constant. On these objects the type attribute must return the 13 value of that constant. 14 15 Format:: 16 17 : MEDIA_SYM S* medium [ COMMA S* medium ]* 18 19 STRING? # the name 20 21 LBRACE S* ruleset* '}' S*; 22 23 ``cssRules`` 24 All Rules in this media rule, a :class:`~cssutils.css.CSSRuleList`. 25 """ 26 27 def __init__( 28 self, 29 mediaText='all', 30 name=None, 31 parentRule=None, 32 parentStyleSheet=None, 33 readonly=False, 34 ): 35 """constructor""" 36 super(CSSMediaRule, self).__init__( 37 parentRule=parentRule, parentStyleSheet=parentStyleSheet 38 ) 39 self._atkeyword = '@media' 40 41 # 1. media 42 if mediaText: 43 self.media = mediaText 44 else: 45 self.media = cssutils.stylesheets.MediaList() 46 47 self.name = name 48 self._readonly = readonly 49 50 def __repr__(self): 51 return "cssutils.css.%s(mediaText=%r)" % ( 52 self.__class__.__name__, 53 self.media.mediaText, 54 ) 55 56 def __str__(self): 57 return "<cssutils.css.%s object mediaText=%r at 0x%x>" % ( 58 self.__class__.__name__, 59 self.media.mediaText, 60 id(self), 61 ) 62 63 def _getCssText(self): 64 """Return serialized property cssText.""" 65 return cssutils.ser.do_CSSMediaRule(self) 66 67 def _setCssText(self, cssText): # noqa: C901 68 """ 69 :param cssText: 70 a parseable string or a tuple of (cssText, dict-of-namespaces) 71 :Exceptions: 72 - :exc:`~xml.dom.NamespaceErr`: 73 Raised if a specified selector uses an unknown namespace 74 prefix. 75 - :exc:`~xml.dom.SyntaxErr`: 76 Raised if the specified CSS string value has a syntax error and 77 is unparsable. 78 - :exc:`~xml.dom.InvalidModificationErr`: 79 Raised if the specified CSS string value represents a different 80 type of rule than the current one. 81 - :exc:`~xml.dom.HierarchyRequestErr`: 82 Raised if the rule cannot be inserted at this point in the 83 style sheet. 84 - :exc:`~xml.dom.NoModificationAllowedErr`: 85 Raised if the rule is readonly. 86 """ 87 # media "name"? { cssRules } 88 super(CSSMediaRule, self)._setCssText(cssText) 89 90 # might be (cssText, namespaces) 91 cssText, namespaces = self._splitNamespacesOff(cssText) 92 93 tokenizer = self._tokenize2(cssText) 94 attoken = self._nexttoken(tokenizer, None) 95 if self._type(attoken) != self._prods.MEDIA_SYM: 96 self._log.error( 97 'CSSMediaRule: No CSSMediaRule found: %s' % self._valuestr(cssText), 98 error=xml.dom.InvalidModificationErr, 99 ) 100 101 else: 102 # save if parse goes wrong 103 oldMedia = self._media 104 oldCssRules = self._cssRules 105 106 ok = True 107 108 # media 109 mediatokens, end = self._tokensupto2( 110 tokenizer, mediaqueryendonly=True, separateEnd=True 111 ) 112 if '{' == self._tokenvalue(end) or self._prods.STRING == self._type(end): 113 self.media = cssutils.stylesheets.MediaList(parentRule=self) 114 # TODO: remove special case 115 self.media.mediaText = mediatokens 116 ok = ok and self.media.wellformed 117 else: 118 ok = False 119 120 # name (optional) 121 name = None 122 nameseq = self._tempSeq() 123 if self._prods.STRING == self._type(end): 124 name = self._stringtokenvalue(end) 125 # TODO: for now comments are lost after name 126 nametokens, end = self._tokensupto2( 127 tokenizer, blockstartonly=True, separateEnd=True 128 ) 129 wellformed, expected = self._parse(None, nameseq, nametokens, {}) 130 if not wellformed: 131 ok = False 132 self._log.error( 133 'CSSMediaRule: Syntax Error: %s' % self._valuestr(cssText) 134 ) 135 136 # check for { 137 if '{' != self._tokenvalue(end): 138 self._log.error( 139 'CSSMediaRule: No "{" found: %s' % self._valuestr(cssText) 140 ) 141 return 142 143 # cssRules 144 cssrulestokens, braceOrEOF = self._tokensupto2( 145 tokenizer, mediaendonly=True, separateEnd=True 146 ) 147 nonetoken = self._nexttoken(tokenizer, None) 148 if 'EOF' == self._type(braceOrEOF): 149 # HACK!!! 150 # TODO: Not complete, add EOF to rule and } to @media 151 cssrulestokens.append(braceOrEOF) 152 braceOrEOF = ('CHAR', '}', 0, 0) 153 self._log.debug( 154 'CSSMediaRule: Incomplete, adding "}".', 155 token=braceOrEOF, 156 neverraise=True, 157 ) 158 159 if '}' != self._tokenvalue(braceOrEOF): 160 self._log.error('CSSMediaRule: No "}" found.', token=braceOrEOF) 161 elif nonetoken: 162 self._log.error( 163 'CSSMediaRule: Trailing content found.', token=nonetoken 164 ) 165 else: 166 # for closures: must be a mutable 167 new = {'wellformed': True} 168 169 def COMMENT(expected, seq, token, tokenizer=None): 170 self.insertRule( 171 cssutils.css.CSSComment( 172 [token], 173 parentRule=self, 174 parentStyleSheet=self.parentStyleSheet, 175 ) 176 ) 177 return expected 178 179 def ruleset(expected, seq, token, tokenizer): 180 rule = cssutils.css.CSSStyleRule( 181 parentRule=self, parentStyleSheet=self.parentStyleSheet 182 ) 183 rule.cssText = self._tokensupto2(tokenizer, token) 184 if rule.wellformed: 185 self.insertRule(rule) 186 return expected 187 188 def atrule(expected, seq, token, tokenizer): 189 # TODO: get complete rule! 190 tokens = self._tokensupto2(tokenizer, token) 191 atval = self._tokenvalue(token) 192 factories = { 193 '@page': cssutils.css.CSSPageRule, 194 '@media': CSSMediaRule, 195 } 196 if atval in ( 197 '@charset ', 198 '@font-face', 199 '@import', 200 '@namespace', 201 '@variables', 202 ): 203 self._log.error( 204 'CSSMediaRule: This rule is not ' 205 'allowed in CSSMediaRule - ignored: ' 206 '%s.' % self._valuestr(tokens), 207 token=token, 208 error=xml.dom.HierarchyRequestErr, 209 ) 210 elif atval in factories: 211 rule = factories[atval]( 212 parentRule=self, parentStyleSheet=self.parentStyleSheet 213 ) 214 rule.cssText = tokens 215 if rule.wellformed: 216 self.insertRule(rule) 217 else: 218 rule = cssutils.css.CSSUnknownRule( 219 tokens, 220 parentRule=self, 221 parentStyleSheet=self.parentStyleSheet, 222 ) 223 if rule.wellformed: 224 self.insertRule(rule) 225 return expected 226 227 # save for possible reset 228 oldCssRules = self.cssRules 229 230 self.cssRules = cssutils.css.CSSRuleList() 231 seq = [] # not used really 232 233 tokenizer = iter(cssrulestokens) 234 wellformed, expected = self._parse( 235 braceOrEOF, 236 seq, 237 tokenizer, 238 { 239 'COMMENT': COMMENT, 240 'CHARSET_SYM': atrule, 241 'FONT_FACE_SYM': atrule, 242 'IMPORT_SYM': atrule, 243 'NAMESPACE_SYM': atrule, 244 'PAGE_SYM': atrule, 245 'MEDIA_SYM': atrule, 246 'ATKEYWORD': atrule, 247 }, 248 default=ruleset, 249 new=new, 250 ) 251 ok = ok and wellformed 252 253 if ok: 254 self.name = name 255 self._setSeq(nameseq) 256 else: 257 self._media = oldMedia 258 self._cssRules = oldCssRules 259 260 cssText = property( 261 _getCssText, 262 _setCssText, 263 doc="(DOM) The parsable textual representation of this " "rule.", 264 ) 265 266 def _setName(self, name): 267 if isinstance(name, str) or name is None: 268 # "" or '' 269 if not name: 270 name = None 271 272 self._name = name 273 else: 274 self._log.error('CSSImportRule: Not a valid name: %s' % name) 275 276 name = property( 277 lambda self: self._name, _setName, doc="An optional name for this media rule." 278 ) 279 280 def _setMedia(self, media): 281 """ 282 :param media: 283 a :class:`~cssutils.stylesheets.MediaList` or string 284 """ 285 self._checkReadonly() 286 if isinstance(media, str): 287 self._media = cssutils.stylesheets.MediaList( 288 mediaText=media, parentRule=self 289 ) 290 else: 291 media._parentRule = self 292 self._media = media 293 294 # NOT IN @media seq at all?! 295 296 # # update seq 297 # for i, item in enumerate(self.seq): 298 # if item.type == 'media': 299 # self._seq[i] = (self._media, 'media', None, None) 300 # break 301 # else: 302 # # insert after @media if not in seq at all 303 # self.seq.insert(0, 304 # self._media, 'media', None, None) 305 306 media = property( 307 lambda self: self._media, 308 _setMedia, 309 doc="(DOM) A list of media types for this rule " 310 "of type :class:`~cssutils.stylesheets.MediaList`.", 311 ) 312 313 def insertRule(self, rule, index=None): 314 """Implements base ``insertRule``.""" 315 rule, index = self._prepareInsertRule(rule, index) 316 317 if rule is False or rule is True: 318 # done or error 319 return 320 321 # check hierarchy 322 if ( 323 isinstance(rule, cssutils.css.CSSCharsetRule) 324 or isinstance(rule, cssutils.css.CSSFontFaceRule) 325 or isinstance(rule, cssutils.css.CSSImportRule) 326 or isinstance(rule, cssutils.css.CSSNamespaceRule) 327 or isinstance(rule, cssutils.css.MarginRule) 328 ): 329 self._log.error( 330 '%s: This type of rule is not allowed here: %s' 331 % (self.__class__.__name__, rule.cssText), 332 error=xml.dom.HierarchyRequestErr, 333 ) 334 return 335 336 return self._finishInsertRule(rule, index) 337 338 type = property( 339 lambda self: self.MEDIA_RULE, 340 doc="The type of this rule, as defined by a CSSRule " "type constant.", 341 ) 342 343 wellformed = property(lambda self: self.media.wellformed) 344