1 """base classes for css and stylesheets packages
2 """
3 __all__ = []
4 __docformat__ = 'restructuredtext'
5 __author__ = '$LastChangedBy: cthedot $'
6 __date__ = '$LastChangedDate: 2008-01-13 00:06:08 +0100 (So, 13 Jan 2008) $'
7 __version__ = '$LastChangedRevision: 838 $'
8
9 import re
10 import types
11 import xml.dom
12 import cssutils
13 from tokenize2 import Tokenizer
14
16 """
17 (EXPERIMENTAL)
18 A base class used for list classes like css.SelectorList or
19 stylesheets.MediaList
20
21 adds list like behaviour running on inhering class' property ``seq``
22
23 - item in x => bool
24 - len(x) => integer
25 - get, set and del x[i]
26 - for item in x
27 - append(item)
28
29 some methods must be overwritten in inheriting class
30 """
33
36
39
41 return self.seq[index]
42
45
48
50 "must be overwritten"
51 raise NotImplementedError
52
54 "must be overwritten"
55 raise NotImplementedError
56
57
59 """
60 Base class for most CSS and StyleSheets classes
61
62 **Superceded by Base2 which is used for new seq handling class.**
63 See cssutils.util.Base2
64
65 Contains helper methods for inheriting classes helping parsing
66
67 ``_normalize`` is static as used by Preferences.
68 """
69 __tokenizer2 = Tokenizer()
70 _log = cssutils.log
71 _prods = cssutils.tokenize2.CSSProductions
72
73
74
75
76 _SHORTHANDPROPERTIES = {
77 u'background': [],
78 u'border': [],
79 u'border-left': [],
80 u'border-right': [],
81 u'border-top': [],
82 u'border-bottom': [],
83 u'border-color': [],
84 u'border-style': [],
85 u'border-width': [],
86 u'cue': [],
87 u'font': [],
88
89
90
91
92 u'list-style': [],
93 u'margin': [],
94 u'outline': [],
95 u'padding': [],
96 u'pause': []
97 }
98
99
100 __escapes = re.compile(ur'(\\[^0-9a-fA-F])').sub
101
102 __unicodes = re.compile(ur'\\[0-9a-fA-F]{1,6}[\t|\r|\n|\f|\x20]?').sub
103
104 @staticmethod
106 """
107 normalizes x, namely:
108
109 - remove any \ before non unicode sequences (0-9a-zA-Z) so for
110 x=="c\olor\" return "color" (unicode escape sequences should have
111 been resolved by the tokenizer already)
112 - lowercase
113 """
114 if x:
115 def removeescape(matchobj):
116 return matchobj.group(0)[1:]
117 x = Base.__escapes(removeescape, x)
118 return x.lower()
119 else:
120 return x
121
123 "raises xml.dom.NoModificationAllowedErr if rule/... is readonly"
124 if hasattr(self, '_readonly') and self._readonly:
125 raise xml.dom.NoModificationAllowedErr(
126 u'%s is readonly.' % self.__class__)
127 return True
128 return False
129
131 """
132 returns tokens of textortokens which may already be tokens in which
133 case simply returns input
134 """
135 if not textortokens:
136 return None
137 elif isinstance(textortokens, basestring):
138
139 return self.__tokenizer2.tokenize(
140 textortokens)
141 elif types.GeneratorType == type(textortokens):
142
143 return textortokens
144 elif isinstance(textortokens, tuple):
145
146 return [textortokens]
147 else:
148
149 return (x for x in textortokens)
150
152 "returns next token in generator tokenizer or the default value"
153 try:
154 return tokenizer.next()
155 except (StopIteration, AttributeError):
156 return default
157
159 "returns type of Tokenizer token"
160 if token:
161 return token[0]
162 else:
163 return None
164
166 "returns value of Tokenizer token"
167 if token and normalize:
168 return Base._normalize(token[1])
169 elif token:
170 return token[1]
171 else:
172 return None
173
174 - def _tokensupto2(self,
175 tokenizer,
176 starttoken=None,
177 blockstartonly=False,
178 blockendonly=False,
179 mediaendonly=False,
180 semicolon=False,
181 propertynameendonly=False,
182 propertyvalueendonly=False,
183 propertypriorityendonly=False,
184 selectorattendonly=False,
185 funcendonly=False,
186 listseponly=False,
187 keepEnd=True,
188 keepEOF=True):
189 """
190 returns tokens upto end of atrule and end index
191 end is defined by parameters, might be ; } ) or other
192
193 default looks for ending "}" and ";"
194 """
195 ends = u';}'
196 brace = bracket = parant = 0
197
198 if blockstartonly:
199 ends = u'{'
200 brace = -1
201 elif blockendonly:
202 ends = u'}'
203 elif mediaendonly:
204 ends = u'}'
205 brace = 1
206 elif semicolon:
207 ends = u';'
208 elif propertynameendonly:
209 ends = u':;'
210 elif propertyvalueendonly:
211 ends = (u';', u'!')
212 elif propertypriorityendonly:
213 ends = u';'
214 elif selectorattendonly:
215 ends = u']'
216 if starttoken and self._tokenvalue(starttoken) == u'[':
217 bracket = 1
218 elif funcendonly:
219 ends = u')'
220 parant = 1
221 elif listseponly:
222 ends = u','
223
224 resulttokens = []
225 if starttoken:
226 resulttokens.append(starttoken)
227 if tokenizer:
228 for token in tokenizer:
229 typ, val, line, col = token
230 if 'EOF' == typ:
231 if keepEOF and keepEnd:
232 resulttokens.append(token)
233 break
234 if u'{' == val:
235 brace += 1
236 elif u'}' == val:
237 brace -= 1
238 elif u'[' == val:
239 bracket += 1
240 elif u']' == val:
241 bracket -= 1
242
243 elif u'(' == val or \
244 Base._prods.FUNCTION == typ:
245 parant += 1
246 elif u')' == val:
247 parant -= 1
248
249 if val in ends and (brace == bracket == parant == 0):
250 if keepEnd:
251 resulttokens.append(token)
252 break
253 else:
254 resulttokens.append(token)
255
256 return resulttokens
257
259 """
260 returns string value of t (t may be a string, a list of token tuples
261 or a single tuple in format (type, value, line, col).
262 Mainly used to get a string value of t for error messages.
263 """
264 if not t:
265 return u''
266 elif isinstance(t, basestring):
267 return t
268 else:
269 return u''.join([x[1] for x in t])
270
272 """
273 adds default productions if not already present, used by
274 _parse only
275
276 each production should return the next expected token
277 normaly a name like "uri" or "EOF"
278 some have no expectation like S or COMMENT, so simply return
279 the current value of self.__expected
280 """
281 def ATKEYWORD(expected, seq, token, tokenizer=None):
282 "TODO: add default impl for unexpected @rule?"
283 return expected
284
285 def COMMENT(expected, seq, token, tokenizer=None):
286 "default implementation for COMMENT token adds CSSCommentRule"
287 seq.append(cssutils.css.CSSComment([token]))
288 return expected
289
290 def S(expected, seq, token, tokenizer=None):
291 "default implementation for S token, does nothing"
292 return expected
293
294 def EOF(expected=None, seq=None, token=None, tokenizer=None):
295 "default implementation for EOF token"
296 return 'EOF'
297
298 p = {'ATKEYWORD': ATKEYWORD,
299 'COMMENT': COMMENT,
300 'S': S,
301 'EOF': EOF
302 }
303 p.update(productions)
304 return p
305
306 - def _parse(self, expected, seq, tokenizer, productions, default=None):
307 """
308 puts parsed tokens in seq by calling a production with
309 (seq, tokenizer, token)
310
311 expected
312 a name what token or value is expected next, e.g. 'uri'
313 seq
314 to add rules etc to
315 tokenizer
316 call tokenizer.next() to get next token
317 productions
318 callbacks {tokentype: callback}
319 default
320 default callback if tokentype not in productions
321
322 returns (wellformed, expected) which the last prod might have set
323 """
324 wellformed = True
325 if tokenizer:
326 prods = self.__adddefaultproductions(productions)
327 for token in tokenizer:
328 p = prods.get(token[0], default)
329 if p:
330 expected = p(expected, seq, token, tokenizer)
331 else:
332 wellformed = False
333 self._log.error(u'Unexpected token (%s, %s, %s, %s)' % token)
334
335 return wellformed, expected
336
337
339 """
340 property seq of Base2 inheriting classes, holds a list of Item objects.
341
342 used only by Selector for now
343
344 is normally readonly, only writable during parsing
345 """
347 """
348 only way to write to a Seq is to initialize it with new items
349 each itemtuple has (value, type, line) where line is optional
350 """
351 self.__seq = []
352 self._readonly = readonly
353
356
359
361 return iter(self.__seq)
362
364 return len(self.__seq)
365
366 - def append(self, val, typ, line=None):
367 "if not readonly add new Item()"
368 if self._readonly:
369 raise AttributeError('Seq is readonly.')
370 else:
371 self.__seq.append(Item(val, typ, line))
372
373 - def replace(self, index=-1, val=None, typ=None, line=None):
374 """
375 if not readonly replace Item at index with new Item or
376 simply replace value or type
377 """
378 if self._readonly:
379 raise AttributeError('Seq is readonly.')
380 else:
381 self.__seq[index] = Item(val, typ, line)
382
384 "returns a repr same as a list of tuples of (value, type)"
385 return u'cssutils.%s.%s([\n %s])' % (self.__module__,
386 self.__class__.__name__,
387 u',\n '.join([u'(%r, %r)' % (item.type, item.value)
388 for item in self.__seq]
389 ))
391 return "<cssutils.%s.%s object length=%r at 0x%x>" % (
392 self.__module__, self.__class__.__name__, len(self), id(self))
393
395 """
396 an item in the seq list of classes (successor to tuple items in old seq)
397
398 each item has attributes:
399
400 type
401 a sematic type like "element", "attribute"
402 value
403 the actual value which may be a string, number etc or an instance
404 of e.g. a CSSComment
405 *line*
406 **NOT IMPLEMENTED YET, may contain the line in the source later**
407 """
408 - def __init__(self, val, typ, line=None):
409 self.__value = val
410 self.__type = typ
411 self.__line = line
412
413 type = property(lambda self: self.__type)
414 value = property(lambda self: self.__value)
415 line = property(lambda self: self.__line)
416
417
419 """
420 Base class for new seq handling, used by Selector for now only
421 """
424
426 """
427 sets newseq and makes it readonly
428 """
429 newseq._readonly = True
430 self._seq = newseq
431
432 seq = property(fget=lambda self: self._seq,
433 fset=_setSeq,
434 doc="seq for most classes")
435
437 "get a writeable Seq() which is added later"
438 return Seq(readonly=readonly)
439
440
442 """This is a decorator which can be used to mark functions
443 as deprecated. It will result in a warning being emitted
444 when the function is used.
445
446 It accepts a single paramter ``msg`` which is shown with the warning.
447 It should contain information which function or method to use instead.
448 """
451
453 def newFunc(*args, **kwargs):
454 import warnings
455 warnings.warn("Call to deprecated method %r. %s" %
456 (func.__name__, self.msg),
457 category=DeprecationWarning,
458 stacklevel=2)
459 return func(*args, **kwargs)
460 newFunc.__name__ = func.__name__
461 newFunc.__doc__ = func.__doc__
462 newFunc.__dict__.update(func.__dict__)
463 return newFunc
464