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