Statistics
| Revision:

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__