gvsig-scripting / org.gvsig.scripting / trunk / org.gvsig.scripting / org.gvsig.scripting.app / org.gvsig.scripting.app.mainplugin / src / main / resources-plugin / scripting / lib / cssutils / __init__.py @ 475
History | View | Annotate | Download (14.6 KB)
1 |
#!/usr/bin/env python
|
---|---|
2 |
"""cssutils - CSS Cascading Style Sheets library for Python
|
3 |
|
4 |
Copyright (C) 2004-2013 Christof Hoeke
|
5 |
|
6 |
cssutils is free software: you can redistribute it and/or modify
|
7 |
it under the terms of the GNU Lesser General Public License as published by
|
8 |
the Free Software Foundation, either version 3 of the License, or
|
9 |
(at your option) any later version.
|
10 |
|
11 |
This program is distributed in the hope that it will be useful,
|
12 |
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
13 |
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
14 |
GNU Lesser General Public License for more details.
|
15 |
|
16 |
You should have received a copy of the GNU Lesser General Public License
|
17 |
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
18 |
|
19 |
|
20 |
A Python package to parse and build CSS Cascading Style Sheets. DOM only, not
|
21 |
any rendering facilities!
|
22 |
|
23 |
Based upon and partly implementing the following specifications :
|
24 |
|
25 |
`CSS 2.1 <http://www.w3.org/TR/CSS2/>`__
|
26 |
General CSS rules and properties are defined here
|
27 |
`CSS 2.1 Errata <http://www.w3.org/Style/css2-updates/CR-CSS21-20070719-errata.html>`__
|
28 |
A few errata, mainly the definition of CHARSET_SYM tokens
|
29 |
`CSS3 Module: Syntax <http://www.w3.org/TR/css3-syntax/>`__
|
30 |
Used in parts since cssutils 0.9.4. cssutils tries to use the features from
|
31 |
CSS 2.1 and CSS 3 with preference to CSS3 but as this is not final yet some
|
32 |
parts are from CSS 2.1
|
33 |
`MediaQueries <http://www.w3.org/TR/css3-mediaqueries/>`__
|
34 |
MediaQueries are part of ``stylesheets.MediaList`` since v0.9.4, used in
|
35 |
@import and @media rules.
|
36 |
`Namespaces <http://dev.w3.org/csswg/css3-namespace/>`__
|
37 |
Added in v0.9.1, updated to definition in CSSOM in v0.9.4, updated in 0.9.5
|
38 |
for dev version
|
39 |
`CSS3 Module: Pages Media <http://www.w3.org/TR/css3-page/>`__
|
40 |
Most properties of this spec are implemented including MarginRules
|
41 |
`Selectors <http://www.w3.org/TR/css3-selectors/>`__
|
42 |
The selector syntax defined here (and not in CSS 2.1) should be parsable
|
43 |
with cssutils (*should* mind though ;) )
|
44 |
|
45 |
`DOM Level 2 Style CSS <http://www.w3.org/TR/DOM-Level-2-Style/css.html>`__
|
46 |
DOM for package css. 0.9.8 removes support for CSSValue and related API,
|
47 |
see PropertyValue and Value API for now
|
48 |
`DOM Level 2 Style Stylesheets <http://www.w3.org/TR/DOM-Level-2-Style/stylesheets.html>`__
|
49 |
DOM for package stylesheets
|
50 |
`CSSOM <http://dev.w3.org/csswg/cssom/>`__
|
51 |
A few details (mainly the NamespaceRule DOM) is taken from here. Plan is
|
52 |
to move implementation to the stuff defined here which is newer but still
|
53 |
no REC so might change anytime...
|
54 |
|
55 |
|
56 |
The cssutils tokenizer is a customized implementation of `CSS3 Module: Syntax
|
57 |
(W3C Working Draft 13 August 2003) <http://www.w3.org/TR/css3-syntax/>`__ which
|
58 |
itself is based on the CSS 2.1 tokenizer. It tries to be as compliant as
|
59 |
possible but uses some (helpful) parts of the CSS 2.1 tokenizer.
|
60 |
|
61 |
I guess cssutils is neither CSS 2.1 nor CSS 3 compliant but tries to at least
|
62 |
be able to parse both grammars including some more real world cases (some CSS
|
63 |
hacks are actually parsed and serialized). Both official grammars are not final
|
64 |
nor bugfree but still feasible. cssutils aim is not to be fully compliant to
|
65 |
any CSS specification (the specifications seem to be in a constant flow anyway)
|
66 |
but cssutils *should* be able to read and write as many as possible CSS
|
67 |
stylesheets "in the wild" while at the same time implement the official APIs
|
68 |
which are well documented. Some minor extensions are provided as well.
|
69 |
|
70 |
Please visit http://cthedot.de/cssutils/ for more details.
|
71 |
|
72 |
|
73 |
Tested with Python 2.7.6 and 3.3.3 on Windows 8.1 64bit.
|
74 |
|
75 |
|
76 |
This library may be used ``from cssutils import *`` which
|
77 |
import subpackages ``css`` and ``stylesheets``, CSSParser and
|
78 |
CSSSerializer classes only.
|
79 |
|
80 |
Usage may be::
|
81 |
|
82 |
>>> from cssutils import *
|
83 |
>>> parser = CSSParser()
|
84 |
>>> sheet = parser.parseString(u'a { color: red}')
|
85 |
>>> print sheet.cssText
|
86 |
a {
|
87 |
color: red
|
88 |
}
|
89 |
|
90 |
"""
|
91 |
__all__ = ['css', 'stylesheets', 'CSSParser', 'CSSSerializer'] |
92 |
__docformat__ = 'restructuredtext'
|
93 |
__author__ = 'Christof Hoeke with contributions by Walter Doerwald'
|
94 |
__date__ = '$LastChangedDate:: $:'
|
95 |
|
96 |
VERSION = '1.0'
|
97 |
|
98 |
__version__ = '%s $Id$' % VERSION
|
99 |
|
100 |
import sys |
101 |
if sys.version_info < (2,6): |
102 |
bytes = str
|
103 |
|
104 |
import codec |
105 |
import os.path |
106 |
import urllib |
107 |
import urlparse |
108 |
import xml.dom |
109 |
|
110 |
# order of imports is important (partly circular)
|
111 |
from . import util
|
112 |
import errorhandler |
113 |
log = errorhandler.ErrorHandler() |
114 |
|
115 |
import css |
116 |
import stylesheets |
117 |
from parse import CSSParser |
118 |
|
119 |
from serialize import CSSSerializer |
120 |
ser = CSSSerializer() |
121 |
|
122 |
from profiles import Profiles |
123 |
profile = Profiles(log=log) |
124 |
|
125 |
# used by Selector defining namespace prefix '*'
|
126 |
_ANYNS = -1
|
127 |
|
128 |
class DOMImplementationCSS(object): |
129 |
"""This interface allows the DOM user to create a CSSStyleSheet
|
130 |
outside the context of a document. There is no way to associate
|
131 |
the new CSSStyleSheet with a document in DOM Level 2.
|
132 |
|
133 |
This class is its *own factory*, as it is given to
|
134 |
xml.dom.registerDOMImplementation which simply calls it and receives
|
135 |
an instance of this class then.
|
136 |
"""
|
137 |
_features = [ |
138 |
('css', '1.0'), |
139 |
('css', '2.0'), |
140 |
('stylesheets', '1.0'), |
141 |
('stylesheets', '2.0') |
142 |
] |
143 |
|
144 |
def createCSSStyleSheet(self, title, media): |
145 |
"""
|
146 |
Creates a new CSSStyleSheet.
|
147 |
|
148 |
title of type DOMString
|
149 |
The advisory title. See also the Style Sheet Interfaces
|
150 |
section.
|
151 |
media of type DOMString
|
152 |
The comma-separated list of media associated with the new style
|
153 |
sheet. See also the Style Sheet Interfaces section.
|
154 |
|
155 |
returns
|
156 |
CSSStyleSheet: A new CSS style sheet.
|
157 |
|
158 |
TODO: DOMException
|
159 |
SYNTAX_ERR: Raised if the specified media string value has a
|
160 |
syntax error and is unparsable.
|
161 |
"""
|
162 |
return css.CSSStyleSheet(title=title, media=media)
|
163 |
|
164 |
def createDocument(self, *args): |
165 |
# not needed to HTML, also not for CSS?
|
166 |
raise NotImplementedError |
167 |
|
168 |
def createDocumentType(self, *args): |
169 |
# not needed to HTML, also not for CSS?
|
170 |
raise NotImplementedError |
171 |
|
172 |
def hasFeature(self, feature, version): |
173 |
return (feature.lower(), unicode(version)) in self._features |
174 |
|
175 |
xml.dom.registerDOMImplementation('cssutils', DOMImplementationCSS)
|
176 |
|
177 |
|
178 |
def parseString(*a, **k): |
179 |
return CSSParser().parseString(*a, **k)
|
180 |
parseString.__doc__ = CSSParser.parseString.__doc__ |
181 |
|
182 |
def parseFile(*a, **k): |
183 |
return CSSParser().parseFile(*a, **k)
|
184 |
parseFile.__doc__ = CSSParser.parseFile.__doc__ |
185 |
|
186 |
def parseUrl(*a, **k): |
187 |
return CSSParser().parseUrl(*a, **k)
|
188 |
parseUrl.__doc__ = CSSParser.parseUrl.__doc__ |
189 |
|
190 |
def parseStyle(*a, **k): |
191 |
return CSSParser().parseStyle(*a, **k)
|
192 |
parseStyle.__doc__ = CSSParser.parseStyle.__doc__ |
193 |
|
194 |
# set "ser", default serializer
|
195 |
def setSerializer(serializer): |
196 |
"""Set the global serializer used by all class in cssutils."""
|
197 |
global ser
|
198 |
ser = serializer |
199 |
|
200 |
def getUrls(sheet): |
201 |
"""Retrieve all ``url(urlstring)`` values (in e.g.
|
202 |
:class:`cssutils.css.CSSImportRule` or :class:`cssutils.css.CSSValue`
|
203 |
objects of given `sheet`.
|
204 |
|
205 |
:param sheet:
|
206 |
:class:`cssutils.css.CSSStyleSheet` object whose URLs are yielded
|
207 |
|
208 |
This function is a generator. The generated URL values exclude ``url(`` and
|
209 |
``)`` and surrounding single or double quotes.
|
210 |
"""
|
211 |
for importrule in (r for r in sheet if r.type == r.IMPORT_RULE): |
212 |
yield importrule.href
|
213 |
|
214 |
def styleDeclarations(base): |
215 |
"recursive generator to find all CSSStyleDeclarations"
|
216 |
if hasattr(base, 'cssRules'): |
217 |
for rule in base.cssRules: |
218 |
for s in styleDeclarations(rule): |
219 |
yield s
|
220 |
elif hasattr(base, 'style'): |
221 |
yield base.style
|
222 |
|
223 |
for style in styleDeclarations(sheet): |
224 |
for p in style.getProperties(all=True): |
225 |
for v in p.propertyValue: |
226 |
if v.type == 'URI': |
227 |
yield v.uri
|
228 |
|
229 |
def replaceUrls(sheetOrStyle, replacer, ignoreImportRules=False): |
230 |
"""Replace all URLs in :class:`cssutils.css.CSSImportRule` or
|
231 |
:class:`cssutils.css.CSSValue` objects of given `sheetOrStyle`.
|
232 |
|
233 |
:param sheetOrStyle:
|
234 |
a :class:`cssutils.css.CSSStyleSheet` or a
|
235 |
:class:`cssutils.css.CSSStyleDeclaration` which is changed in place
|
236 |
:param replacer:
|
237 |
a function which is called with a single argument `url` which
|
238 |
is the current value of each url() excluding ``url(``, ``)`` and
|
239 |
surrounding (single or double) quotes.
|
240 |
:param ignoreImportRules:
|
241 |
if ``True`` does not call `replacer` with URLs from @import rules.
|
242 |
"""
|
243 |
if not ignoreImportRules and not isinstance(sheetOrStyle, |
244 |
css.CSSStyleDeclaration): |
245 |
for importrule in (r for r in sheetOrStyle if r.type == r.IMPORT_RULE): |
246 |
importrule.href = replacer(importrule.href) |
247 |
|
248 |
def styleDeclarations(base): |
249 |
"recursive generator to find all CSSStyleDeclarations"
|
250 |
if hasattr(base, 'cssRules'): |
251 |
for rule in base.cssRules: |
252 |
for s in styleDeclarations(rule): |
253 |
yield s
|
254 |
elif hasattr(base, 'style'): |
255 |
yield base.style
|
256 |
elif isinstance(sheetOrStyle, css.CSSStyleDeclaration): |
257 |
# base is a style already
|
258 |
yield base
|
259 |
|
260 |
for style in styleDeclarations(sheetOrStyle): |
261 |
for p in style.getProperties(all=True): |
262 |
for v in p.propertyValue: |
263 |
if v.type == v.URI:
|
264 |
v.uri = replacer(v.uri) |
265 |
|
266 |
def resolveImports(sheet, target=None): |
267 |
"""Recurcively combine all rules in given `sheet` into a `target` sheet.
|
268 |
@import rules which use media information are tried to be wrapped into
|
269 |
@media rules so keeping the media information. This may not work in
|
270 |
all instances (if e.g. an @import rule itself contains an @import rule
|
271 |
with different media infos or if it contains rules which may not be
|
272 |
used inside an @media block like @namespace rules.). In these cases
|
273 |
the @import rule is kept as in the original sheet and a WARNING is issued.
|
274 |
|
275 |
:param sheet:
|
276 |
in this given :class:`cssutils.css.CSSStyleSheet` all import rules are
|
277 |
resolved and added to a resulting *flat* sheet.
|
278 |
:param target:
|
279 |
A :class:`cssutils.css.CSSStyleSheet` object which will be the
|
280 |
resulting *flat* sheet if given
|
281 |
:returns: given `target` or a new :class:`cssutils.css.CSSStyleSheet`
|
282 |
object
|
283 |
"""
|
284 |
if not target: |
285 |
target = css.CSSStyleSheet(href=sheet.href, |
286 |
media=sheet.media, |
287 |
title=sheet.title) |
288 |
|
289 |
def getReplacer(targetbase): |
290 |
"Return a replacer which uses base to return adjusted URLs"
|
291 |
basesch, baseloc, basepath, basequery, basefrag = urlparse.urlsplit(targetbase) |
292 |
basepath, basepathfilename = os.path.split(basepath) |
293 |
|
294 |
def replacer(uri): |
295 |
scheme, location, path, query, fragment = urlparse.urlsplit(uri) |
296 |
if not scheme and not location and not path.startswith(u'/'): |
297 |
# relative
|
298 |
path, filename = os.path.split(path) |
299 |
combined = os.path.normpath(os.path.join(basepath, path, filename)) |
300 |
return urllib.pathname2url(combined)
|
301 |
else:
|
302 |
# keep anything absolute
|
303 |
return uri
|
304 |
|
305 |
return replacer
|
306 |
|
307 |
for rule in sheet.cssRules: |
308 |
if rule.type == rule.CHARSET_RULE:
|
309 |
pass
|
310 |
elif rule.type == rule.IMPORT_RULE:
|
311 |
log.info(u'Processing @import %r' % rule.href, neverraise=True) |
312 |
|
313 |
if rule.hrefFound:
|
314 |
# add all rules of @import to current sheet
|
315 |
target.add(css.CSSComment(cssText=u'/* START @import "%s" */'
|
316 |
% rule.href)) |
317 |
|
318 |
try:
|
319 |
# nested imports
|
320 |
importedSheet = resolveImports(rule.styleSheet) |
321 |
except xml.dom.HierarchyRequestErr, e:
|
322 |
log.warn(u'@import: Cannot resolve target, keeping rule: %s'
|
323 |
% e, neverraise=True)
|
324 |
target.add(rule) |
325 |
else:
|
326 |
# adjust relative URI references
|
327 |
log.info(u'@import: Adjusting paths for %r' % rule.href,
|
328 |
neverraise=True)
|
329 |
replaceUrls(importedSheet, |
330 |
getReplacer(rule.href), |
331 |
ignoreImportRules=True)
|
332 |
|
333 |
# might have to wrap rules in @media if media given
|
334 |
if rule.media.mediaText == u'all': |
335 |
mediaproxy = None
|
336 |
else:
|
337 |
keepimport = False
|
338 |
for r in importedSheet: |
339 |
# check if rules present which may not be
|
340 |
# combined with media
|
341 |
if r.type not in (r.COMMENT, |
342 |
r.STYLE_RULE, |
343 |
r.IMPORT_RULE): |
344 |
keepimport = True
|
345 |
break
|
346 |
if keepimport:
|
347 |
log.warn(u'Cannot combine imported sheet with'
|
348 |
u' given media as other rules then'
|
349 |
u' comments or stylerules found %r,'
|
350 |
u' keeping %r' % (r,
|
351 |
rule.cssText), |
352 |
neverraise=True)
|
353 |
target.add(rule) |
354 |
continue
|
355 |
|
356 |
# wrap in @media if media is not `all`
|
357 |
log.info(u'@import: Wrapping some rules in @media '
|
358 |
u' to keep media: %s'
|
359 |
% rule.media.mediaText, neverraise=True)
|
360 |
mediaproxy = css.CSSMediaRule(rule.media.mediaText) |
361 |
|
362 |
for r in importedSheet: |
363 |
if mediaproxy:
|
364 |
mediaproxy.add(r) |
365 |
else:
|
366 |
# add to top sheet directly but are difficult anyway
|
367 |
target.add(r) |
368 |
|
369 |
if mediaproxy:
|
370 |
target.add(mediaproxy) |
371 |
|
372 |
else:
|
373 |
# keep @import as it is
|
374 |
log.error(u'Cannot get referenced stylesheet %r, keeping rule'
|
375 |
% rule.href, neverraise=True)
|
376 |
target.add(rule) |
377 |
|
378 |
else:
|
379 |
target.add(rule) |
380 |
|
381 |
return target
|
382 |
|
383 |
|
384 |
if __name__ == '__main__': |
385 |
print __doc__
|