1 """base classes for css and stylesheets packages
2 """
3 __all__ = []
4 __docformat__ = 'restructuredtext'
5 __author__ = '$LastChangedBy: cthedot $'
6 __date__ = '$LastChangedDate: 2008-02-02 23:52:55 +0100 (Sa, 02 Feb 2008) $'
7 __version__ = '$LastChangedRevision: 973 $'
8
9 from itertools import ifilter
10 import re
11 import types
12 import xml.dom
13 import cssutils
14 from tokenize2 import Tokenizer
17 """
18 (EXPERIMENTAL)
19 A base class used for list classes like css.SelectorList or
20 stylesheets.MediaList
21
22 adds list like behaviour running on inhering class' property ``seq``
23
24 - item in x => bool
25 - len(x) => integer
26 - get, set and del x[i]
27 - for item in x
28 - append(item)
29
30 some methods must be overwritten in inheriting class
31 """
34
37
40
42 return self.seq[index]
43
45 def gen():
46 for x in self.seq:
47 yield x
48 return gen()
49
52
54 "must be overwritten"
55 raise NotImplementedError
56
58 "must be overwritten"
59 raise NotImplementedError
60
61
62 -class Base(object):
63 """
64 Base class for most CSS and StyleSheets classes
65
66 **Superceded by Base2 which is used for new seq handling class.**
67 See cssutils.util.Base2
68
69 Contains helper methods for inheriting classes helping parsing
70
71 ``_normalize`` is static as used by Preferences.
72 """
73 __tokenizer2 = Tokenizer()
74 _log = cssutils.log
75 _prods = cssutils.tokenize2.CSSProductions
76
77
78
79
80 _SHORTHANDPROPERTIES = {
81 u'background': [],
82 u'border': [],
83 u'border-left': [],
84 u'border-right': [],
85 u'border-top': [],
86 u'border-bottom': [],
87 u'border-color': [],
88 u'border-style': [],
89 u'border-width': [],
90 u'cue': [],
91 u'font': [],
92
93
94
95
96 u'list-style': [],
97 u'margin': [],
98 u'outline': [],
99 u'padding': [],
100 u'pause': []
101 }
102
103
104 __escapes = re.compile(ur'(\\[^0-9a-fA-F])').sub
105
106 __unicodes = re.compile(ur'\\[0-9a-fA-F]{1,6}[\t|\r|\n|\f|\x20]?').sub
107
108 @staticmethod
110 """
111 normalizes x, namely:
112
113 - remove any \ before non unicode sequences (0-9a-zA-Z) so for
114 x=="c\olor\" return "color" (unicode escape sequences should have
115 been resolved by the tokenizer already)
116 - lowercase
117 """
118 if x:
119 def removeescape(matchobj):
120 return matchobj.group(0)[1:]
121 x = Base.__escapes(removeescape, x)
122 return x.lower()
123 else:
124 return x
125
127 "raises xml.dom.NoModificationAllowedErr if rule/... is readonly"
128 if hasattr(self, '_readonly') and self._readonly:
129 raise xml.dom.NoModificationAllowedErr(
130 u'%s is readonly.' % self.__class__)
131 return True
132 return False
133
135 """
136 returns tuple (text, dict-of-namespaces) or if no namespaces are
137 in cssText returns (cssText, {})
138
139 used in Selector, SelectorList, CSSStyleRule, CSSMediaRule and
140 CSSStyleSheet
141 """
142 if isinstance(text_namespaces_tuple, tuple):
143 return text_namespaces_tuple[0], _SimpleNamespaces(
144 text_namespaces_tuple[1])
145 else:
146 return text_namespaces_tuple, _SimpleNamespaces()
147
149 """
150 returns tokens of textortokens which may already be tokens in which
151 case simply returns input
152 """
153 if not textortokens:
154 return None
155 elif isinstance(textortokens, basestring):
156
157 return self.__tokenizer2.tokenize(
158 textortokens)
159 elif types.GeneratorType == type(textortokens):
160
161 return textortokens
162 elif isinstance(textortokens, tuple):
163
164 return [textortokens]
165 else:
166
167 return (x for x in textortokens)
168
170 "returns next token in generator tokenizer or the default value"
171 try:
172 return tokenizer.next()
173 except (StopIteration, AttributeError):
174 return default
175
177 "returns type of Tokenizer token"
178 if token:
179 return token[0]
180 else:
181 return None
182
184 "returns value of Tokenizer token"
185 if token and normalize:
186 return Base._normalize(token[1])
187 elif token:
188 return token[1]
189 else:
190 return None
191
192 - def _tokensupto2(self,
193 tokenizer,
194 starttoken=None,
195 blockstartonly=False,
196 blockendonly=False,
197 mediaendonly=False,
198 semicolon=False,
199 propertynameendonly=False,
200 propertyvalueendonly=False,
201 propertypriorityendonly=False,
202 selectorattendonly=False,
203 funcendonly=False,
204 listseponly=False,
205 keepEnd=True,
206 keepEOF=True):
207 """
208 returns tokens upto end of atrule and end index
209 end is defined by parameters, might be ; } ) or other
210
211 default looks for ending "}" and ";"
212 """
213 ends = u';}'
214 brace = bracket = parant = 0
215
216 if blockstartonly:
217 ends = u'{'
218 brace = -1
219 elif blockendonly:
220 ends = u'}'
221 brace = 1
222 elif mediaendonly:
223 ends = u'}'
224 brace = 1
225 elif semicolon:
226 ends = u';'
227 elif propertynameendonly:
228 ends = u':;'
229 elif propertyvalueendonly:
230 ends = (u';', u'!')
231 elif propertypriorityendonly:
232 ends = u';'
233 elif selectorattendonly:
234 ends = u']'
235 if starttoken and self._tokenvalue(starttoken) == u'[':
236 bracket = 1
237 elif funcendonly:
238 ends = u')'
239 parant = 1
240 elif listseponly:
241 ends = u','
242
243 resulttokens = []
244 if starttoken:
245 resulttokens.append(starttoken)
246 if tokenizer:
247 for token in tokenizer:
248 typ, val, line, col = token
249 if 'EOF' == typ:
250 if keepEOF and keepEnd:
251 resulttokens.append(token)
252 break
253 if u'{' == val:
254 brace += 1
255 elif u'}' == val:
256 brace -= 1
257 elif u'[' == val:
258 bracket += 1
259 elif u']' == val:
260 bracket -= 1
261
262 elif u'(' == val or \
263 Base._prods.FUNCTION == typ:
264 parant += 1
265 elif u')' == val:
266 parant -= 1
267
268 if val in ends and (brace == bracket == parant == 0):
269 if keepEnd:
270 resulttokens.append(token)
271 break
272 else:
273 resulttokens.append(token)
274
275 return resulttokens
276
278 """
279 returns string value of t (t may be a string, a list of token tuples
280 or a single tuple in format (type, value, line, col).
281 Mainly used to get a string value of t for error messages.
282 """
283 if not t:
284 return u''
285 elif isinstance(t, basestring):
286 return t
287 else:
288 return u''.join([x[1] for x in t])
289
291 """
292 adds default productions if not already present, used by
293 _parse only
294
295 each production should return the next expected token
296 normaly a name like "uri" or "EOF"
297 some have no expectation like S or COMMENT, so simply return
298 the current value of self.__expected
299 """
300 def ATKEYWORD(expected, seq, token, tokenizer=None):
301 "TODO: add default impl for unexpected @rule?"
302 return expected
303
304 def COMMENT(expected, seq, token, tokenizer=None):
305 "default implementation for COMMENT token adds CSSCommentRule"
306 seq.append(cssutils.css.CSSComment([token]))
307 return expected
308
309 def S(expected, seq, token, tokenizer=None):
310 "default implementation for S token, does nothing"
311 return expected
312
313 def EOF(expected=None, seq=None, token=None, tokenizer=None):
314 "default implementation for EOF token"
315 return 'EOF'
316
317 p = {'ATKEYWORD': ATKEYWORD,
318 'COMMENT': COMMENT,
319 'S': S,
320 'EOF': EOF
321 }
322 p.update(productions)
323 return p
324
325 - def _parse(self, expected, seq, tokenizer, productions, default=None):
326 """
327 puts parsed tokens in seq by calling a production with
328 (seq, tokenizer, token)
329
330 expected
331 a name what token or value is expected next, e.g. 'uri'
332 seq
333 to add rules etc to
334 tokenizer
335 call tokenizer.next() to get next token
336 productions
337 callbacks {tokentype: callback}
338 default
339 default callback if tokentype not in productions
340
341 returns (wellformed, expected) which the last prod might have set
342 """
343 wellformed = True
344 if tokenizer:
345 prods = self.__adddefaultproductions(productions)
346 for token in tokenizer:
347 p = prods.get(token[0], default)
348 if p:
349 expected = p(expected, seq, token, tokenizer)
350 else:
351 wellformed = False
352 self._log.error(u'Unexpected token (%s, %s, %s, %s)' % token)
353
354 return wellformed, expected
355
356
357 -class Seq(object):
358 """
359 property seq of Base2 inheriting classes, holds a list of Item objects.
360
361 used only by Selector for now
362
363 is normally readonly, only writable during parsing
364 """
366 """
367 only way to write to a Seq is to initialize it with new items
368 each itemtuple has (value, type, line) where line is optional
369 """
370 self._seq = []
371 self._readonly = readonly
372
375
378
380 return iter(self._seq)
381
383 return len(self._seq)
384
385 - def append(self, val, typ, line=None):
386 "if not readonly add new Item()"
387 if self._readonly:
388 raise AttributeError('Seq is readonly.')
389 else:
390 self._seq.append(Item(val, typ, line))
391
392 - def replace(self, index=-1, val=None, typ=None, line=None):
393 """
394 if not readonly replace Item at index with new Item or
395 simply replace value or type
396 """
397 if self._readonly:
398 raise AttributeError('Seq is readonly.')
399 else:
400 self._seq[index] = Item(val, typ, line)
401
403 "returns a repr same as a list of tuples of (value, type)"
404 return u'cssutils.%s.%s([\n %s])' % (self.__module__,
405 self.__class__.__name__,
406 u',\n '.join([u'(%r, %r)' % (item.type, item.value)
407 for item in self._seq]
408 ))
410 return "<cssutils.%s.%s object length=%r at 0x%x>" % (
411 self.__module__, self.__class__.__name__, len(self), id(self))
412
414 """
415 an item in the seq list of classes (successor to tuple items in old seq)
416
417 each item has attributes:
418
419 type
420 a sematic type like "element", "attribute"
421 value
422 the actual value which may be a string, number etc or an instance
423 of e.g. a CSSComment
424 *line*
425 **NOT IMPLEMENTED YET, may contain the line in the source later**
426 """
427 - def __init__(self, value, type, line=None):
428 self.__value = value
429 self.__type = type
430 self.__line = line
431
432 type = property(lambda self: self.__type)
433 value = property(lambda self: self.__value)
434 line = property(lambda self: self.__line)
435
437 return "cssutils.%s.%s(value=%r, type=%r, line=%r)" % (
438 self.__module__, self.__class__.__name__,
439 self.__value, self.__type, self.__line)
440
442 """
443 Base class for new seq handling, used by Selector for now only
444 """
447
449 """
450 sets newseq and makes it readonly
451 """
452 newseq._readonly = True
453 self._seq = newseq
454
455 seq = property(fget=lambda self: self._seq,
456 fset=_setSeq,
457 doc="seq for most classes")
458
460 "get a writeable Seq() which is added later"
461 return Seq(readonly=readonly)
462
465 """This is a decorator which can be used to mark functions
466 as deprecated. It will result in a warning being emitted
467 when the function is used.
468
469 It accepts a single paramter ``msg`` which is shown with the warning.
470 It should contain information which function or method to use instead.
471 """
474
476 def newFunc(*args, **kwargs):
477 import warnings
478 warnings.warn("Call to deprecated method %r. %s" %
479 (func.__name__, self.msg),
480 category=DeprecationWarning,
481 stacklevel=2)
482 return func(*args, **kwargs)
483 newFunc.__name__ = func.__name__
484 newFunc.__doc__ = func.__doc__
485 newFunc.__dict__.update(func.__dict__)
486 return newFunc
487
489 """
490 A dictionary like wrapper for @namespace rules used in a CSSStyleSheet.
491 Works on effective namespaces, so e.g. if::
492
493 @namespace p1 "uri";
494 @namespace p2 "uri";
495
496 only the second rule is effective and kept.
497
498 namespaces
499 a dictionary {prefix: namespaceURI} containing the effective namespaces
500 only. These are the latest set in the CSSStyleSheet.
501 parentStyleSheet
502 the parent CSSStyleSheet
503 """
504 - def __init__(self, parentStyleSheet, *args):
507
510
521
523 try:
524 return self.namespaces[prefix]
525 except KeyError, e:
526 raise xml.dom.NamespaceErr('Prefix %r not found.' % prefix)
527
530
533
547
554
562
563 namespaces = property(__getNamespaces,
564 doc=u'Holds only effetive @namespace rules in self.parentStyleSheets'
565 '@namespace rules.')
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590 - def get(self, prefix, default):
592
595
598
601
603 """
604 returns effective prefix for given namespaceURI or raises IndexError
605 if this cannot be found"""
606 for prefix, uri in self.namespaces.items():
607 if uri == namespaceURI:
608 return prefix
609 raise IndexError(u'NamespaceURI %r not found.' % namespaceURI)
610
612 return u"<cssutils.util.%s object parentStyleSheet=%r namespaces=%r "\
613 u"at 0x%x>" % (
614 self.__class__.__name__, str(self.parentStyleSheet),
615 self.namespaces, id(self))
616
619 """
620 namespaces used in objects like Selector as long as they are not connected
621 to a CSSStyleSheet
622 """
624 self.__namespaces = dict(*args)
625
628
629 namespaces = property(lambda self: self.__namespaces,
630 doc=u'Dict Wrapper for self.sheets @namespace rules.')
631
633 return u"<cssutils.util.%s object namespaces=%r at 0x%x>" % (
634 self.__class__.__name__, self.namespaces, id(self))
635