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