1 """base classes for css and stylesheets packages
2 """
3 __all__ = []
4 __docformat__ = 'restructuredtext'
5 __author__ = '$LastChangedBy: cthedot $'
6 __date__ = '$LastChangedDate: 2007-11-06 22:23:55 +0100 (Di, 06 Nov 2007) $'
7 __version__ = '$LastChangedRevision: 653 $'
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 list like sequence of (value, type) used in almost all cssutils classes
19
20 behaves almost like a list but keeps extra attribute "type" for
21 each value in the list
22
23 types are tokens types like e.g. "COMMENT" (value='/*...*/', all uppercase)
24 or productions like e.g. "universal" (value='*', all lowercase)
25 """
27 self.values = []
28 self.types = []
29
31 return item in self.values
32
34 return self.values[index]
35
37 "might be set with tuple (value, type) or a single value"
38 if type(value_type) == tuple:
39 val = value_type[0]
40 typ = value_type[1]
41 else:
42 val = value_type
43 typ = None
44 self.values[index] = val
45 self.types[index] = typ
46
48 "returns an iterator of values only "
49 return iter(self.values)
50
52 "same as len(list)"
53 return len(self.values)
54
56 "returns a repr same as a list of tuples of (value, type)"
57 return u'[%s]' % u',\n '.join([u'(%r, %r)' % (value, self.types[i])
58 for i, value in enumerate(self.values)])
60 "returns a concanated string of all values"
61 items = []
62 for i, value in enumerate(self.values):
63 if self.types[i]:
64 if self.types[i] != 'COMMENT':
65 items.append(value)
66 items.append(value)
67 return u''.join(items)
68
69 - def append(self, value, type=None):
70 """
71 same as list.append but not a simple value but a SeqItem is appended
72 """
73 self.values.append(value)
74 self.types.append(type)
75
76
78 """
79 Base class for most CSS and StyleSheets classes
80
81 Contains helper methods for inheriting classes helping parsing
82
83 ``_normalize`` is static as used be Preferences.
84 """
85 __tokenizer2 = Tokenizer()
86 _log = cssutils.log
87 _prods = cssutils.tokenize2.CSSProductions
88
89
90
91
92 _SHORTHANDPROPERTIES = {
93 u'background': [],
94 u'border': [],
95 u'border-left': [],
96 u'border-right': [],
97 u'border-top': [],
98 u'border-bottom': [],
99 u'border-color': [],
100 u'border-style': [],
101 u'border-width': [],
102 u'cue': [],
103 u'font': [('font-weight', True),
104 ('font-size', True),
105 ('line-height', False),
106 ('font-family', True)],
107 u'list-style': [],
108 u'margin': [],
109 u'outline': [],
110 u'padding': [],
111 u'pause': []
112 }
113
114
115 __escapes = re.compile(ur'(\\[^0-9a-fA-F])').sub
116
117 __unicodes = re.compile(ur'\\[0-9a-fA-F]{1,6}[\t|\r|\n|\f|\x20]?').sub
118
119 @staticmethod
121 """
122 normalizes x, namely:
123
124 - remove any \ before non unicode sequences (0-9a-zA-Z) so for
125 x=="c\olor\" return "color" (unicode escape sequences should have
126 been resolved by the tokenizer already)
127 - lowercase
128 """
129 if x:
130 def removeescape(matchobj):
131 return matchobj.group(0)[1:]
132 x = Base.__escapes(removeescape, x)
133 return x.lower()
134 else:
135 return x
136
138 "raises xml.dom.NoModificationAllowedErr if rule/... is readonly"
139 if hasattr(self, '_readonly') and self._readonly:
140 raise xml.dom.NoModificationAllowedErr(
141 u'%s is readonly.' % self.__class__)
142 return True
143 return False
144
148
150 """
151 returns string value of t (t may be a string, a list of token tuples
152 or a single tuple in format (type, value, line, col) or a
153 tokenlist[old])
154 """
155 if not t:
156 return u''
157 elif isinstance(t, basestring):
158 return t
159 elif isinstance(t, list) and isinstance(t[0], tuple):
160 return u''.join([x[1] for x in t])
161 elif isinstance(t, tuple):
162 return self._tokenvalue(t)
163 else:
164 return u''.join([x.value for x in t])
165
166 - def _tokenize2(self, textortokens, aslist=False, fullsheet=False):
167 """
168 returns tokens of textortokens which may already be tokens in which
169 case simply returns input
170 """
171 if not textortokens:
172 return None
173 if types.GeneratorType == type(textortokens) and not aslist:
174
175 return textortokens
176 if isinstance(textortokens, basestring):
177 if aslist:
178 return [t for t in self.__tokenizer2.tokenize(
179 textortokens, fullsheet=fullsheet)]
180 else:
181 return self.__tokenizer2.tokenize(
182 textortokens, fullsheet=fullsheet)
183 elif isinstance(textortokens, tuple):
184
185 return [textortokens]
186 else:
187
188 return (x for x in textortokens)
189
191 "returns next token in generator tokenizer or the default value"
192 try:
193 return tokenizer.next()
194 except (StopIteration, AttributeError):
195 return default
196
198 "type of Tokenizer token"
199 if not token:
200 return None
201 else:
202 return token[0]
203
205 "value of Tokenizer token"
206 if not token:
207 return None
208 elif normalize:
209 return Base._normalize(token[1])
210 else:
211 return token[1]
212
213 - def _tokensupto2(self,
214 tokenizer,
215 starttoken=None,
216 blockstartonly=False,
217 blockendonly=False,
218 mediaendonly=False,
219 semicolon=False,
220 propertynameendonly=False,
221 propertyvalueendonly=False,
222 propertypriorityendonly=False,
223 selectorattendonly=False,
224 funcendonly=False,
225 listseponly=False,
226 keepEnd=True,
227 keepEOF=True):
228 """
229 returns tokens upto end of atrule and end index
230 end is defined by parameters, might be ; } ) or other
231
232 default looks for ending "}" and ";"
233 """
234 ends = u';}'
235 brace = bracket = parant = 0
236
237 if blockstartonly:
238 ends = u'{'
239 brace = -1
240 elif blockendonly:
241 ends = u'}'
242 elif mediaendonly:
243 ends = u'}'
244 brace = 1
245 elif semicolon:
246 ends = u';'
247 elif propertynameendonly:
248 ends = u':;'
249 elif propertyvalueendonly:
250 ends = (u';', u'!')
251 elif propertypriorityendonly:
252 ends = u';'
253 elif selectorattendonly:
254 ends = u']'
255 if starttoken and self._tokenvalue(starttoken) == u'[':
256 bracket = 1
257 elif funcendonly:
258 ends = u')'
259 parant = 1
260 elif listseponly:
261 ends = u','
262
263 resulttokens = []
264
265
266 if starttoken:
267 resulttokens.append(starttoken)
268
269 if not tokenizer:
270 return resulttokens
271 else:
272 for token in tokenizer:
273 if self._type(token) == 'EOF':
274 if keepEOF and keepEnd:
275 resulttokens.append(token)
276 break
277 val = self._tokenvalue(token)
278 if u'{' == val: brace += 1
279 elif u'}' == val: brace -= 1
280 elif u'[' == val: bracket += 1
281 elif u']' == val: bracket -= 1
282
283 elif u'(' == val or \
284 Base._prods.FUNCTION == self._type(token): parant += 1
285 elif u')' == val: parant -= 1
286 if val in ends and (brace == bracket == parant == 0):
287 if keepEnd:
288 resulttokens.append(token)
289 break
290 else:
291 resulttokens.append(token)
292
293 return resulttokens
294
296 """
297 each production should return the next expected token
298 normaly a name like "uri" or "EOF"
299 some have no expectation like S or COMMENT, so simply return
300 the current value of self.__expected
301 """
302 def ATKEYWORD(expected, seq, token, tokenizer=None):
303 "TODO: add default impl for unexpected @rule"
304 return expected
305
306 def COMMENT(expected, seq, token, tokenizer=None):
307 "default implementation for COMMENT token"
308 seq.append(cssutils.css.CSSComment([token]))
309 return expected
310
311 def S(expected, seq, token, tokenizer=None):
312 "default implementation for S token"
313 return expected
314
315 def EOF(expected=None, seq=None, token=None, tokenizer=None):
316 "default implementation for EOF token"
317 return 'EOF'
318
319 p = {'COMMENT': COMMENT,
320 'S': S,
321 'ATKEYWORD': ATKEYWORD,
322 'EOF': EOF
323 }
324 p.update(productions)
325 return p
326
327 - def _parse(self, expected, seq, tokenizer, productions, default=None):
328 """
329 puts parsed tokens in seq by calling a production with
330 (seq, tokenizer, token)
331
332 expected
333 a name what token or value is expected next, e.g. 'uri'
334 seq
335 to add rules etc to
336 tokenizer
337 call tokenizer.next() to get next token
338 productions
339 callbacks {tokentype: callback}
340 default
341 default callback if tokentype not in productions
342
343 returns (wellformed, expected) which the last prod might have set
344 """
345 wellformed = True
346
347 if not tokenizer:
348 return wellformed, expected
349
350 prods = self._getProductions(productions)
351 for token in tokenizer:
352 typ, val, lin, col = token
353 p = prods.get(typ, default)
354 if p:
355 expected = p(expected, seq, token, tokenizer)
356 else:
357 wellformed = False
358 self._log.error(u'Unexpected token (%s, %s, %s, %s)' % token)
359
360 return wellformed, expected
361
362
364 """This is a decorator which can be used to mark functions
365 as deprecated. It will result in a warning being emitted
366 when the function is used.
367
368 It accepts a single paramter ``msg`` which is shown with the warning.
369 It should contain information which function or method to use instead.
370 """
373
375 def newFunc(*args, **kwargs):
376 import warnings
377 warnings.warn("Call to deprecated method %r. %s" %
378 (func.__name__, self.msg),
379 category=DeprecationWarning,
380 stacklevel=2)
381 return func(*args, **kwargs)
382 newFunc.__name__ = func.__name__
383 newFunc.__doc__ = func.__doc__
384 newFunc.__dict__.update(func.__dict__)
385 return newFunc
386