1 """base classes for css and stylesheets packages
2 """
3 __all__ = []
4 __docformat__ = 'restructuredtext'
5 __author__ = '$LastChangedBy: cthedot $'
6 __date__ = '$LastChangedDate: 2008-01-15 21:59:49 +0100 (Di, 15 Jan 2008) $'
7 __version__ = '$LastChangedRevision: 863 $'
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 brace = 1
204 elif mediaendonly:
205 ends = u'}'
206 brace = 1
207 elif semicolon:
208 ends = u';'
209 elif propertynameendonly:
210 ends = u':;'
211 elif propertyvalueendonly:
212 ends = (u';', u'!')
213 elif propertypriorityendonly:
214 ends = u';'
215 elif selectorattendonly:
216 ends = u']'
217 if starttoken and self._tokenvalue(starttoken) == u'[':
218 bracket = 1
219 elif funcendonly:
220 ends = u')'
221 parant = 1
222 elif listseponly:
223 ends = u','
224
225 resulttokens = []
226 if starttoken:
227 resulttokens.append(starttoken)
228 if tokenizer:
229 for token in tokenizer:
230 typ, val, line, col = token
231 if 'EOF' == typ:
232 if keepEOF and keepEnd:
233 resulttokens.append(token)
234 break
235 if u'{' == val:
236 brace += 1
237 elif u'}' == val:
238 brace -= 1
239 elif u'[' == val:
240 bracket += 1
241 elif u']' == val:
242 bracket -= 1
243
244 elif u'(' == val or \
245 Base._prods.FUNCTION == typ:
246 parant += 1
247 elif u')' == val:
248 parant -= 1
249
250 if val in ends and (brace == bracket == parant == 0):
251 if keepEnd:
252 resulttokens.append(token)
253 break
254 else:
255 resulttokens.append(token)
256
257 return resulttokens
258
260 """
261 returns string value of t (t may be a string, a list of token tuples
262 or a single tuple in format (type, value, line, col).
263 Mainly used to get a string value of t for error messages.
264 """
265 if not t:
266 return u''
267 elif isinstance(t, basestring):
268 return t
269 else:
270 return u''.join([x[1] for x in t])
271
273 """
274 adds default productions if not already present, used by
275 _parse only
276
277 each production should return the next expected token
278 normaly a name like "uri" or "EOF"
279 some have no expectation like S or COMMENT, so simply return
280 the current value of self.__expected
281 """
282 def ATKEYWORD(expected, seq, token, tokenizer=None):
283 "TODO: add default impl for unexpected @rule?"
284 return expected
285
286 def COMMENT(expected, seq, token, tokenizer=None):
287 "default implementation for COMMENT token adds CSSCommentRule"
288 seq.append(cssutils.css.CSSComment([token]))
289 return expected
290
291 def S(expected, seq, token, tokenizer=None):
292 "default implementation for S token, does nothing"
293 return expected
294
295 def EOF(expected=None, seq=None, token=None, tokenizer=None):
296 "default implementation for EOF token"
297 return 'EOF'
298
299 p = {'ATKEYWORD': ATKEYWORD,
300 'COMMENT': COMMENT,
301 'S': S,
302 'EOF': EOF
303 }
304 p.update(productions)
305 return p
306
307 - def _parse(self, expected, seq, tokenizer, productions, default=None):
308 """
309 puts parsed tokens in seq by calling a production with
310 (seq, tokenizer, token)
311
312 expected
313 a name what token or value is expected next, e.g. 'uri'
314 seq
315 to add rules etc to
316 tokenizer
317 call tokenizer.next() to get next token
318 productions
319 callbacks {tokentype: callback}
320 default
321 default callback if tokentype not in productions
322
323 returns (wellformed, expected) which the last prod might have set
324 """
325 wellformed = True
326 if tokenizer:
327 prods = self.__adddefaultproductions(productions)
328 for token in tokenizer:
329 p = prods.get(token[0], default)
330 if p:
331 expected = p(expected, seq, token, tokenizer)
332 else:
333 wellformed = False
334 self._log.error(u'Unexpected token (%s, %s, %s, %s)' % token)
335
336 return wellformed, expected
337
338
340 """
341 property seq of Base2 inheriting classes, holds a list of Item objects.
342
343 used only by Selector for now
344
345 is normally readonly, only writable during parsing
346 """
348 """
349 only way to write to a Seq is to initialize it with new items
350 each itemtuple has (value, type, line) where line is optional
351 """
352 self.__seq = []
353 self._readonly = readonly
354
357
360
362 return iter(self.__seq)
363
365 return len(self.__seq)
366
367 - def append(self, val, typ, line=None):
368 "if not readonly add new Item()"
369 if self._readonly:
370 raise AttributeError('Seq is readonly.')
371 else:
372 self.__seq.append(Item(val, typ, line))
373
374 - def replace(self, index=-1, val=None, typ=None, line=None):
375 """
376 if not readonly replace Item at index with new Item or
377 simply replace value or type
378 """
379 if self._readonly:
380 raise AttributeError('Seq is readonly.')
381 else:
382 self.__seq[index] = Item(val, typ, line)
383
385 "returns a repr same as a list of tuples of (value, type)"
386 return u'cssutils.%s.%s([\n %s])' % (self.__module__,
387 self.__class__.__name__,
388 u',\n '.join([u'(%r, %r)' % (item.type, item.value)
389 for item in self.__seq]
390 ))
392 return "<cssutils.%s.%s object length=%r at 0x%x>" % (
393 self.__module__, self.__class__.__name__, len(self), id(self))
394
396 """
397 an item in the seq list of classes (successor to tuple items in old seq)
398
399 each item has attributes:
400
401 type
402 a sematic type like "element", "attribute"
403 value
404 the actual value which may be a string, number etc or an instance
405 of e.g. a CSSComment
406 *line*
407 **NOT IMPLEMENTED YET, may contain the line in the source later**
408 """
409 - def __init__(self, val, typ, line=None):
410 self.__value = val
411 self.__type = typ
412 self.__line = line
413
414 type = property(lambda self: self.__type)
415 value = property(lambda self: self.__value)
416 line = property(lambda self: self.__line)
417
418
420 """
421 Base class for new seq handling, used by Selector for now only
422 """
425
427 """
428 sets newseq and makes it readonly
429 """
430 newseq._readonly = True
431 self._seq = newseq
432
433 seq = property(fget=lambda self: self._seq,
434 fset=_setSeq,
435 doc="seq for most classes")
436
438 "get a writeable Seq() which is added later"
439 return Seq(readonly=readonly)
440
441
443 """This is a decorator which can be used to mark functions
444 as deprecated. It will result in a warning being emitted
445 when the function is used.
446
447 It accepts a single paramter ``msg`` which is shown with the warning.
448 It should contain information which function or method to use instead.
449 """
452
454 def newFunc(*args, **kwargs):
455 import warnings
456 warnings.warn("Call to deprecated method %r. %s" %
457 (func.__name__, self.msg),
458 category=DeprecationWarning,
459 stacklevel=2)
460 return func(*args, **kwargs)
461 newFunc.__name__ = func.__name__
462 newFunc.__doc__ = func.__doc__
463 newFunc.__dict__.update(func.__dict__)
464 return newFunc
465