Browse Source

Simplify minifier

fixes #1744
pull/1757/head
Ruud 12 years ago
parent
commit
0e1cea1034
  1. 36
      couchpotato/core/_base/clientscript/main.py
  2. 20
      libs/cssprefixer/__init__.py
  3. 115
      libs/cssprefixer/engine.py
  4. 271
      libs/cssprefixer/rules.py
  5. 385
      libs/cssutils/__init__.py
  6. 584
      libs/cssutils/_codec2.py
  7. 608
      libs/cssutils/_codec3.py
  8. 44
      libs/cssutils/_fetch.py
  9. 68
      libs/cssutils/_fetchgae.py
  10. 16
      libs/cssutils/codec.py
  11. 80
      libs/cssutils/css/__init__.py
  12. 184
      libs/cssutils/css/colors.py
  13. 159
      libs/cssutils/css/csscharsetrule.py
  14. 87
      libs/cssutils/css/csscomment.py
  15. 184
      libs/cssutils/css/cssfontfacerule.py
  16. 396
      libs/cssutils/css/cssimportrule.py
  17. 302
      libs/cssutils/css/cssmediarule.py
  18. 295
      libs/cssutils/css/cssnamespacerule.py
  19. 436
      libs/cssutils/css/csspagerule.py
  20. 122
      libs/cssutils/css/cssproperties.py
  21. 304
      libs/cssutils/css/cssrule.py
  22. 53
      libs/cssutils/css/cssrulelist.py
  23. 697
      libs/cssutils/css/cssstyledeclaration.py
  24. 234
      libs/cssutils/css/cssstylerule.py
  25. 804
      libs/cssutils/css/cssstylesheet.py
  26. 209
      libs/cssutils/css/cssunknownrule.py
  27. 1251
      libs/cssutils/css/cssvalue.py
  28. 330
      libs/cssutils/css/cssvariablesdeclaration.py
  29. 198
      libs/cssutils/css/cssvariablesrule.py
  30. 215
      libs/cssutils/css/marginrule.py
  31. 510
      libs/cssutils/css/property.py
  32. 813
      libs/cssutils/css/selector.py
  33. 234
      libs/cssutils/css/selectorlist.py
  34. 871
      libs/cssutils/css/value.py
  35. 131
      libs/cssutils/css2productions.py
  36. 124
      libs/cssutils/cssproductions.py
  37. 118
      libs/cssutils/errorhandler.py
  38. 137
      libs/cssutils/helper.py
  39. 232
      libs/cssutils/parse.py
  40. 733
      libs/cssutils/prodparser.py
  41. 791
      libs/cssutils/profiles.py
  42. 428
      libs/cssutils/sac.py
  43. 362
      libs/cssutils/script.py
  44. 4
      libs/cssutils/scripts/__init__.py
  45. 69
      libs/cssutils/scripts/csscapture.py
  46. 94
      libs/cssutils/scripts/csscombine.py
  47. 62
      libs/cssutils/scripts/cssparse.py
  48. 1138
      libs/cssutils/serialize.py
  49. 15
      libs/cssutils/settings.py
  50. 11
      libs/cssutils/stylesheets/__init__.py
  51. 235
      libs/cssutils/stylesheets/medialist.py
  52. 207
      libs/cssutils/stylesheets/mediaquery.py
  53. 123
      libs/cssutils/stylesheets/stylesheet.py
  54. 32
      libs/cssutils/stylesheets/stylesheetlist.py
  55. 223
      libs/cssutils/tokenize2.py
  56. 884
      libs/cssutils/util.py
  57. 690
      libs/encutils/__init__.py
  58. 202
      libs/minify/cssmin.py

36
couchpotato/core/_base/clientscript/main.py

@ -4,9 +4,11 @@ from couchpotato.core.helpers.variable import tryInt
from couchpotato.core.logger import CPLog
from couchpotato.core.plugins.base import Plugin
from couchpotato.environment import Env
from minify.cssmin import cssmin
from minify.jsmin import jsmin
import cssprefixer
import os
import re
import time
import traceback
log = CPLog(__name__)
@ -102,6 +104,8 @@ class ClientScript(Plugin):
out_name = 'minified_' + out
out = os.path.join(cache, out_name)
start = time.time()
raw = []
for file_path in files:
f = open(file_path, 'r').read()
@ -109,13 +113,16 @@ class ClientScript(Plugin):
if file_type == 'script':
data = jsmin(f)
else:
data = cssprefixer.process(f, debug = False, minify = True)
data = self.prefix(f)
data = cssmin(f)
data = data.replace('../images/', '../static/images/')
data = data.replace('../fonts/', '../static/fonts/')
data = data.replace('../../static/', '../static/') # Replace inside plugins
raw.append({'file': file_path, 'date': int(os.path.getmtime(file_path)), 'data': data})
print file_type, time.time() - start
# Combine all files together with some comments
data = ''
for r in raw:
@ -170,3 +177,28 @@ class ClientScript(Plugin):
if not self.paths[type].get(location):
self.paths[type][location] = []
self.paths[type][location].append(file_path)
prefix_properties = ['border-radius', 'transform', 'transition', 'box-shadow']
prefix_tags = ['ms', 'moz', 'webkit']
def prefix(self, data):
trimmed_data = re.sub('(\t|\n|\r)+', '', data)
new_data = ''
colon_split = trimmed_data.split(';')
for splt in colon_split:
curl_split = splt.strip().split('{')
for curly in curl_split:
curly = curly.strip()
for prop in self.prefix_properties:
if curly[:len(prop) + 1] == prop + ':':
for tag in self.prefix_tags:
new_data += ' -%s-%s; ' % (tag, curly)
new_data += curly + (' { ' if len(curl_split) > 1 else ' ')
new_data += '; '
new_data = new_data.replace('{ ;', '; ').replace('} ;', '} ')
return new_data

20
libs/cssprefixer/__init__.py

@ -1,20 +0,0 @@
# CSSPrefixer
# Copyright 2010-2012 Greg V. <floatboth@me.com>
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
# http://www.apache.org/licenses/LICENSE-2.0
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import engine
import rules
from engine import process
__all__ = ('process', 'engine', 'rules')

115
libs/cssprefixer/engine.py

@ -1,115 +0,0 @@
# CSSPrefixer
# Copyright 2010-2012 Greg V. <floatboth@me.com>
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
# http://www.apache.org/licenses/LICENSE-2.0
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from rules import prefixRegex, rules as tr_rules
import cssutils
import re
keyframesRegex = re.compile(r'@keyframes\s?\w+\s?{(.*)}')
blockRegex = re.compile(r'\w+\s?\{(.*)\}')
def magic(ruleset, debug, minify, filt, parser):
if isinstance(ruleset, cssutils.css.CSSUnknownRule):
if ruleset.cssText.startswith('@keyframes'):
inner = parser.parseString(keyframesRegex.split(ruleset.cssText.replace('\n', ''))[1])
# BUG: doesn't work when minified
s = '' if minify else '\n'
return '@-webkit-keyframes {' + s + \
''.join([magic(rs, debug, minify, ['webkit'], parser) for rs in inner]) \
+ '}' + s + '@-moz-keyframes {' + s + \
''.join([magic(rs, debug, minify, ['moz'], parser) for rs in inner]) \
+ '}' + s + ruleset.cssText
elif ruleset.cssText.startswith('from') or ruleset.cssText.startswith('to'):
return ''.join([magic(rs, debug, minify, filt, parser)
for rs in parser.parseString(blockRegex.sub(r'\1', ruleset.cssText.replace('\n', ''))[1])])
else:
return
elif hasattr(ruleset, 'style'): # Comments don't
ruleSet = set()
rules = list()
children = list(ruleset.style.children())
ruleset.style = cssutils.css.CSSStyleDeclaration() # clear out the styles that were there
for rule in children:
if not hasattr(rule, 'name'): # comments don't have name
rules.append(rule)
continue
name = prefixRegex.sub('', rule.name)
if name in tr_rules:
rule.name = name
if rule.cssText in ruleSet:
continue
ruleSet.add(rule.cssText)
rules.append(rule)
ruleset.style.seq._readonly = False
for rule in rules:
if not hasattr(rule, 'name'):
ruleset.style.seq.append(rule, 'Comment')
continue
processor = None
try: # try except so if anything goes wrong we don't lose the original property
if rule.name in tr_rules:
processor = tr_rules[rule.name](rule)
[ruleset.style.seq.append(prop, 'Property') for prop in processor.get_prefixed_props(filt) if prop]
# always add the original rule
if processor and hasattr(processor, 'get_base_prop'):
ruleset.style.seq.append(processor.get_base_prop(), 'Property')
else:
ruleset.style.seq.append(rule, 'Property')
except:
if debug:
print 'warning with ' + str(rule)
ruleset.style.seq.append(rule, 'Property')
ruleset.style.seq._readonly = True
elif hasattr(ruleset, 'cssRules'):
for subruleset in ruleset:
magic(subruleset, debug, minify, filt, parser)
cssText = ruleset.cssText
if not cssText: # blank rules return None so return an empty string
return
if minify or not hasattr(ruleset, 'style'):
return cssText
return cssText + '\n'
def process(string, debug = False, minify = False, filt = ['webkit', 'moz', 'o', 'ms'], **prefs):
parser = cssutils.CSSParser(loglevel = 'CRITICAL')
if minify:
cssutils.ser.prefs.useMinified()
else:
cssutils.ser.prefs.useDefaults()
# use the passed in prefs
for key, value in prefs.iteritems():
if hasattr(cssutils.ser.prefs, key):
cssutils.ser.prefs.__dict__[key] = value
results = []
sheet = parser.parseString(string)
for ruleset in sheet.cssRules:
cssText = magic(ruleset, debug, minify, filt, parser)
if cssText:
results.append(cssText)
# format with newlines based on minify
joinStr = '' if minify else '\n'
# Not using sheet.cssText - it's buggy:
# it skips some prefixed properties.
return joinStr.join(results).rstrip()
__all__ = ['process']

271
libs/cssprefixer/rules.py

@ -1,271 +0,0 @@
# CSSPrefixer
# Copyright 2010-2012 Greg V. <floatboth@me.com>
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
# http://www.apache.org/licenses/LICENSE-2.0
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import re
import cssutils
prefixRegex = re.compile('^(-o-|-ms-|-moz-|-webkit-)')
class BaseReplacementRule(object):
vendor_prefixes = ['moz', 'webkit']
def __init__(self, prop):
self.prop = prop
def get_prefixed_props(self, filt):
for prefix in [p for p in self.vendor_prefixes if p in filt]:
yield cssutils.css.Property(
name='-%s-%s' % (prefix, self.prop.name),
value=self.prop.value,
priority=self.prop.priority
)
@staticmethod
def should_prefix():
return True
class FullReplacementRule(BaseReplacementRule):
vendor_prefixes = sorted(BaseReplacementRule.vendor_prefixes + ['o', 'ms'])
class BaseAndIEReplacementRule(BaseReplacementRule):
vendor_prefixes = sorted(BaseReplacementRule.vendor_prefixes + ['ms'])
class BaseAndOperaReplacementRule(BaseReplacementRule):
vendor_prefixes = sorted(BaseReplacementRule.vendor_prefixes + ['o'])
class WebkitReplacementRule(BaseReplacementRule):
vendor_prefixes = ['webkit']
class OperaAndIEReplacementRule(BaseReplacementRule):
vendor_prefixes = ['ms', 'o']
class MozReplacementRule(BaseReplacementRule):
vendor_prefixes = ['moz']
class BorderRadiusReplacementRule(BaseReplacementRule):
"""
Mozilla's Gecko engine uses different syntax for rounded corners.
"""
vendor_prefixes = ['webkit']
def get_prefixed_props(self, filt):
for prop in BaseReplacementRule.get_prefixed_props(self, filt):
yield prop
if 'moz' in filt:
name = '-moz-' + self.prop.name.replace('top-left-radius', 'radius-topleft') \
.replace('top-right-radius', 'radius-topright') \
.replace('bottom-right-radius', 'radius-bottomright') \
.replace('bottom-left-radius', 'radius-bottomleft')
yield cssutils.css.Property(
name=name,
value=self.prop.value,
priority=self.prop.priority
)
class DisplayReplacementRule(BaseReplacementRule):
"""
Flexible Box Model stuff.
CSSUtils parser doesn't support duplicate properties, so that's dirty.
"""
def get_prefixed_props(self, filt):
if self.prop.value == 'box': # only add prefixes if the value is box
for prefix in [p for p in self.vendor_prefixes if p in filt]:
yield cssutils.css.Property(
name='display',
value='-%s-box' % prefix,
priority=self.prop.priority
)
class TransitionReplacementRule(BaseReplacementRule):
vendor_prefixes = ['moz', 'o', 'webkit']
def __get_prefixed_prop(self, prefix=None):
name = self.prop.name
if prefix:
name = '-%s-%s' % (prefix, self.prop.name)
newValues = []
for value in self.prop.value.split(','):
parts = value.strip().split(' ')
parts[0] = prefixRegex.sub('', parts[0])
if parts[0] in rules and prefix and rules[parts[0]].should_prefix():
parts[0] = '-%s-%s' % (prefix, parts[0])
newValues.append(' '.join(parts))
return cssutils.css.Property(
name=name,
value=', '.join(newValues),
priority=self.prop.priority
)
def get_prefixed_props(self, filt):
for prefix in [p for p in self.vendor_prefixes if p in filt]:
yield self.__get_prefixed_prop(prefix)
def get_base_prop(self):
return self.__get_prefixed_prop()
class GradientReplacementRule(BaseReplacementRule):
vendor_prefixes = ['moz', 'o', 'webkit']
def __iter_values(self):
valueSplit = self.prop.value.split(',')
index = 0
# currentString = ''
while(True):
if index >= len(valueSplit):
break
rawValue = valueSplit[index].strip()
snip = prefixRegex.sub('', rawValue)
if snip.startswith('linear-gradient'):
values = [re.sub('^linear-gradient\(', '', snip)]
if valueSplit[index + 1].strip().endswith(')'):
values.append(re.sub('\)+$', '', valueSplit[index + 1].strip()))
else:
values.append(valueSplit[index + 1].strip())
values.append(re.sub('\)+$', '', valueSplit[index + 2].strip()))
if len(values) == 2:
yield {
'start': values[0],
'end': values[1]
}
else:
yield {
'pos': values[0],
'start': values[1],
'end': values[2]
}
index += len(values)
elif snip.startswith('gradient'):
yield {
'start': re.sub('\)+$', '', valueSplit[index + 4].strip()),
'end': re.sub('\)+$', '', valueSplit[index + 6].strip()),
}
index += 7
else:
# not a gradient so just yield the raw string
yield rawValue
index += 1
def __get_prefixed_prop(self, values, prefix=None):
gradientName = 'linear-gradient'
if prefix:
gradientName = '-%s-%s' % (prefix, gradientName)
newValues = []
for value in values:
if isinstance(value, dict):
if 'pos' in value:
newValues.append(gradientName + '(%(pos)s, %(start)s, %(end)s)' % value)
else:
newValues.append(gradientName + '(%(start)s, %(end)s)' % value)
else:
newValues.append(value)
return cssutils.css.Property(
name=self.prop.name,
value=', '.join(newValues),
priority=self.prop.priority
)
def get_prefixed_props(self, filt):
values = list(self.__iter_values())
needPrefix = False
for value in values: # check if there are any gradients
if isinstance(value, dict):
needPrefix = True
break
if needPrefix:
for prefix in [p for p in self.vendor_prefixes if p in filt]:
yield self.__get_prefixed_prop(values, prefix)
if prefix == 'webkit':
newValues = []
for value in values:
if isinstance(value, dict):
newValues.append('-webkit-gradient(linear, left top, left bottom, color-stop(0, %(start)s), color-stop(1, %(end)s))' % value)
else:
newValues.append(value)
yield cssutils.css.Property(
name=self.prop.name,
value=', '.join(newValues),
priority=self.prop.priority
)
else:
yield None
def get_base_prop(self):
values = self.__iter_values()
return self.__get_prefixed_prop(values)
rules = {
'border-radius': BaseReplacementRule,
'border-top-left-radius': BorderRadiusReplacementRule,
'border-top-right-radius': BorderRadiusReplacementRule,
'border-bottom-right-radius': BorderRadiusReplacementRule,
'border-bottom-left-radius': BorderRadiusReplacementRule,
'border-image': FullReplacementRule,
'box-shadow': BaseReplacementRule,
'box-sizing': MozReplacementRule,
'box-orient': BaseAndIEReplacementRule,
'box-direction': BaseAndIEReplacementRule,
'box-ordinal-group': BaseAndIEReplacementRule,
'box-align': BaseAndIEReplacementRule,
'box-flex': BaseAndIEReplacementRule,
'box-flex-group': BaseReplacementRule,
'box-pack': BaseAndIEReplacementRule,
'box-lines': BaseAndIEReplacementRule,
'user-select': BaseReplacementRule,
'user-modify': BaseReplacementRule,
'margin-start': BaseReplacementRule,
'margin-end': BaseReplacementRule,
'padding-start': BaseReplacementRule,
'padding-end': BaseReplacementRule,
'column-count': BaseReplacementRule,
'column-gap': BaseReplacementRule,
'column-rule': BaseReplacementRule,
'column-rule-color': BaseReplacementRule,
'column-rule-style': BaseReplacementRule,
'column-rule-width': BaseReplacementRule,
'column-span': WebkitReplacementRule,
'column-width': BaseReplacementRule,
'columns': WebkitReplacementRule,
'background-clip': WebkitReplacementRule,
'background-origin': WebkitReplacementRule,
'background-size': WebkitReplacementRule,
'background-image': GradientReplacementRule,
'background': GradientReplacementRule,
'text-overflow': OperaAndIEReplacementRule,
'transition': TransitionReplacementRule,
'transition-delay': BaseAndOperaReplacementRule,
'transition-duration': BaseAndOperaReplacementRule,
'transition-property': TransitionReplacementRule,
'transition-timing-function': BaseAndOperaReplacementRule,
'transform': FullReplacementRule,
'transform-origin': FullReplacementRule,
'display': DisplayReplacementRule,
'appearance': WebkitReplacementRule,
'hyphens': BaseReplacementRule,
}

385
libs/cssutils/__init__.py

@ -1,385 +0,0 @@
#!/usr/bin/env python
"""cssutils - CSS Cascading Style Sheets library for Python
Copyright (C) 2004-2013 Christof Hoeke
cssutils is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
A Python package to parse and build CSS Cascading Style Sheets. DOM only, not
any rendering facilities!
Based upon and partly implementing the following specifications :
`CSS 2.1 <http://www.w3.org/TR/CSS2/>`__
General CSS rules and properties are defined here
`CSS 2.1 Errata <http://www.w3.org/Style/css2-updates/CR-CSS21-20070719-errata.html>`__
A few errata, mainly the definition of CHARSET_SYM tokens
`CSS3 Module: Syntax <http://www.w3.org/TR/css3-syntax/>`__
Used in parts since cssutils 0.9.4. cssutils tries to use the features from
CSS 2.1 and CSS 3 with preference to CSS3 but as this is not final yet some
parts are from CSS 2.1
`MediaQueries <http://www.w3.org/TR/css3-mediaqueries/>`__
MediaQueries are part of ``stylesheets.MediaList`` since v0.9.4, used in
@import and @media rules.
`Namespaces <http://dev.w3.org/csswg/css3-namespace/>`__
Added in v0.9.1, updated to definition in CSSOM in v0.9.4, updated in 0.9.5
for dev version
`CSS3 Module: Pages Media <http://www.w3.org/TR/css3-page/>`__
Most properties of this spec are implemented including MarginRules
`Selectors <http://www.w3.org/TR/css3-selectors/>`__
The selector syntax defined here (and not in CSS 2.1) should be parsable
with cssutils (*should* mind though ;) )
`DOM Level 2 Style CSS <http://www.w3.org/TR/DOM-Level-2-Style/css.html>`__
DOM for package css. 0.9.8 removes support for CSSValue and related API,
see PropertyValue and Value API for now
`DOM Level 2 Style Stylesheets <http://www.w3.org/TR/DOM-Level-2-Style/stylesheets.html>`__
DOM for package stylesheets
`CSSOM <http://dev.w3.org/csswg/cssom/>`__
A few details (mainly the NamespaceRule DOM) is taken from here. Plan is
to move implementation to the stuff defined here which is newer but still
no REC so might change anytime...
The cssutils tokenizer is a customized implementation of `CSS3 Module: Syntax
(W3C Working Draft 13 August 2003) <http://www.w3.org/TR/css3-syntax/>`__ which
itself is based on the CSS 2.1 tokenizer. It tries to be as compliant as
possible but uses some (helpful) parts of the CSS 2.1 tokenizer.
I guess cssutils is neither CSS 2.1 nor CSS 3 compliant but tries to at least
be able to parse both grammars including some more real world cases (some CSS
hacks are actually parsed and serialized). Both official grammars are not final
nor bugfree but still feasible. cssutils aim is not to be fully compliant to
any CSS specification (the specifications seem to be in a constant flow anyway)
but cssutils *should* be able to read and write as many as possible CSS
stylesheets "in the wild" while at the same time implement the official APIs
which are well documented. Some minor extensions are provided as well.
Please visit http://cthedot.de/cssutils/ for more details.
Tested with Python 2.7.3 and 3.3 on Windows 8 64bit.
This library may be used ``from cssutils import *`` which
import subpackages ``css`` and ``stylesheets``, CSSParser and
CSSSerializer classes only.
Usage may be::
>>> from cssutils import *
>>> parser = CSSParser()
>>> sheet = parser.parseString(u'a { color: red}')
>>> print sheet.cssText
a {
color: red
}
"""
__all__ = ['css', 'stylesheets', 'CSSParser', 'CSSSerializer']
__docformat__ = 'restructuredtext'
__author__ = 'Christof Hoeke with contributions by Walter Doerwald'
__date__ = '$LastChangedDate:: $:'
VERSION = '0.9.10'
__version__ = '%s $Id$' % VERSION
import sys
if sys.version_info < (2,6):
bytes = str
import codec
import os.path
import urllib
import urlparse
import xml.dom
# order of imports is important (partly circular)
from . import util
import errorhandler
log = errorhandler.ErrorHandler()
import css
import stylesheets
from parse import CSSParser
from serialize import CSSSerializer
ser = CSSSerializer()
from profiles import Profiles
profile = Profiles(log=log)
# used by Selector defining namespace prefix '*'
_ANYNS = -1
class DOMImplementationCSS(object):
"""This interface allows the DOM user to create a CSSStyleSheet
outside the context of a document. There is no way to associate
the new CSSStyleSheet with a document in DOM Level 2.
This class is its *own factory*, as it is given to
xml.dom.registerDOMImplementation which simply calls it and receives
an instance of this class then.
"""
_features = [
('css', '1.0'),
('css', '2.0'),
('stylesheets', '1.0'),
('stylesheets', '2.0')
]
def createCSSStyleSheet(self, title, media):
"""
Creates a new CSSStyleSheet.
title of type DOMString
The advisory title. See also the Style Sheet Interfaces
section.
media of type DOMString
The comma-separated list of media associated with the new style
sheet. See also the Style Sheet Interfaces section.
returns
CSSStyleSheet: A new CSS style sheet.
TODO: DOMException
SYNTAX_ERR: Raised if the specified media string value has a
syntax error and is unparsable.
"""
return css.CSSStyleSheet(title=title, media=media)
def createDocument(self, *args):
# not needed to HTML, also not for CSS?
raise NotImplementedError
def createDocumentType(self, *args):
# not needed to HTML, also not for CSS?
raise NotImplementedError
def hasFeature(self, feature, version):
return (feature.lower(), unicode(version)) in self._features
xml.dom.registerDOMImplementation('cssutils', DOMImplementationCSS)
def parseString(*a, **k):
return CSSParser().parseString(*a, **k)
parseString.__doc__ = CSSParser.parseString.__doc__
def parseFile(*a, **k):
return CSSParser().parseFile(*a, **k)
parseFile.__doc__ = CSSParser.parseFile.__doc__
def parseUrl(*a, **k):
return CSSParser().parseUrl(*a, **k)
parseUrl.__doc__ = CSSParser.parseUrl.__doc__
def parseStyle(*a, **k):
return CSSParser().parseStyle(*a, **k)
parseStyle.__doc__ = CSSParser.parseStyle.__doc__
# set "ser", default serializer
def setSerializer(serializer):
"""Set the global serializer used by all class in cssutils."""
global ser
ser = serializer
def getUrls(sheet):
"""Retrieve all ``url(urlstring)`` values (in e.g.
:class:`cssutils.css.CSSImportRule` or :class:`cssutils.css.CSSValue`
objects of given `sheet`.
:param sheet:
:class:`cssutils.css.CSSStyleSheet` object whose URLs are yielded
This function is a generator. The generated URL values exclude ``url(`` and
``)`` and surrounding single or double quotes.
"""
for importrule in (r for r in sheet if r.type == r.IMPORT_RULE):
yield importrule.href
def styleDeclarations(base):
"recursive generator to find all CSSStyleDeclarations"
if hasattr(base, 'cssRules'):
for rule in base.cssRules:
for s in styleDeclarations(rule):
yield s
elif hasattr(base, 'style'):
yield base.style
for style in styleDeclarations(sheet):
for p in style.getProperties(all=True):
for v in p.propertyValue:
if v.type == 'URI':
yield v.uri
def replaceUrls(sheetOrStyle, replacer, ignoreImportRules=False):
"""Replace all URLs in :class:`cssutils.css.CSSImportRule` or
:class:`cssutils.css.CSSValue` objects of given `sheetOrStyle`.
:param sheetOrStyle:
a :class:`cssutils.css.CSSStyleSheet` or a
:class:`cssutils.css.CSSStyleDeclaration` which is changed in place
:param replacer:
a function which is called with a single argument `url` which
is the current value of each url() excluding ``url(``, ``)`` and
surrounding (single or double) quotes.
:param ignoreImportRules:
if ``True`` does not call `replacer` with URLs from @import rules.
"""
if not ignoreImportRules and not isinstance(sheetOrStyle,
css.CSSStyleDeclaration):
for importrule in (r for r in sheetOrStyle if r.type == r.IMPORT_RULE):
importrule.href = replacer(importrule.href)
def styleDeclarations(base):
"recursive generator to find all CSSStyleDeclarations"
if hasattr(base, 'cssRules'):
for rule in base.cssRules:
for s in styleDeclarations(rule):
yield s
elif hasattr(base, 'style'):
yield base.style
elif isinstance(sheetOrStyle, css.CSSStyleDeclaration):
# base is a style already
yield base
for style in styleDeclarations(sheetOrStyle):
for p in style.getProperties(all=True):
for v in p.propertyValue:
if v.type == v.URI:
v.uri = replacer(v.uri)
def resolveImports(sheet, target=None):
"""Recurcively combine all rules in given `sheet` into a `target` sheet.
@import rules which use media information are tried to be wrapped into
@media rules so keeping the media information. This may not work in
all instances (if e.g. an @import rule itself contains an @import rule
with different media infos or if it contains rules which may not be
used inside an @media block like @namespace rules.). In these cases
the @import rule is kept as in the original sheet and a WARNING is issued.
:param sheet:
in this given :class:`cssutils.css.CSSStyleSheet` all import rules are
resolved and added to a resulting *flat* sheet.
:param target:
A :class:`cssutils.css.CSSStyleSheet` object which will be the
resulting *flat* sheet if given
:returns: given `target` or a new :class:`cssutils.css.CSSStyleSheet`
object
"""
if not target:
target = css.CSSStyleSheet(href=sheet.href,
media=sheet.media,
title=sheet.title)
def getReplacer(targetbase):
"Return a replacer which uses base to return adjusted URLs"
basesch, baseloc, basepath, basequery, basefrag = urlparse.urlsplit(targetbase)
basepath, basepathfilename = os.path.split(basepath)
def replacer(uri):
scheme, location, path, query, fragment = urlparse.urlsplit(uri)
if not scheme and not location and not path.startswith(u'/'):
# relative
path, filename = os.path.split(path)
combined = os.path.normpath(os.path.join(basepath, path, filename))
return urllib.pathname2url(combined)
else:
# keep anything absolute
return uri
return replacer
for rule in sheet.cssRules:
if rule.type == rule.CHARSET_RULE:
pass
elif rule.type == rule.IMPORT_RULE:
log.info(u'Processing @import %r' % rule.href, neverraise=True)
if rule.hrefFound:
# add all rules of @import to current sheet
target.add(css.CSSComment(cssText=u'/* START @import "%s" */'
% rule.href))
try:
# nested imports
importedSheet = resolveImports(rule.styleSheet)
except xml.dom.HierarchyRequestErr, e:
log.warn(u'@import: Cannot resolve target, keeping rule: %s'
% e, neverraise=True)
target.add(rule)
else:
# adjust relative URI references
log.info(u'@import: Adjusting paths for %r' % rule.href,
neverraise=True)
replaceUrls(importedSheet,
getReplacer(rule.href),
ignoreImportRules=True)
# might have to wrap rules in @media if media given
if rule.media.mediaText == u'all':
mediaproxy = None
else:
keepimport = False
for r in importedSheet:
# check if rules present which may not be
# combined with media
if r.type not in (r.COMMENT,
r.STYLE_RULE,
r.IMPORT_RULE):
keepimport = True
break
if keepimport:
log.warn(u'Cannot combine imported sheet with'
u' given media as other rules then'
u' comments or stylerules found %r,'
u' keeping %r' % (r,
rule.cssText),
neverraise=True)
target.add(rule)
continue
# wrap in @media if media is not `all`
log.info(u'@import: Wrapping some rules in @media '
u' to keep media: %s'
% rule.media.mediaText, neverraise=True)
mediaproxy = css.CSSMediaRule(rule.media.mediaText)
for r in importedSheet:
if mediaproxy:
mediaproxy.add(r)
else:
# add to top sheet directly but are difficult anyway
target.add(r)
if mediaproxy:
target.add(mediaproxy)
else:
# keep @import as it is
log.error(u'Cannot get referenced stylesheet %r, keeping rule'
% rule.href, neverraise=True)
target.add(rule)
else:
target.add(rule)
return target
if __name__ == '__main__':
print __doc__

584
libs/cssutils/_codec2.py

@ -1,584 +0,0 @@
#!/usr/bin/env python
"""Python codec for CSS."""
__docformat__ = 'restructuredtext'
__author__ = 'Walter Doerwald'
__version__ = '$Id: util.py 1114 2008-03-05 13:22:59Z cthedot $'
import codecs
import marshal
# We're using bits to store all possible candidate encodings (or variants, i.e.
# we have two bits for the variants of UTF-16 and two for the
# variants of UTF-32).
#
# Prefixes for various CSS encodings
# UTF-8-SIG xEF xBB xBF
# UTF-16 (LE) xFF xFE ~x00|~x00
# UTF-16 (BE) xFE xFF
# UTF-16-LE @ x00 @ x00
# UTF-16-BE x00 @
# UTF-32 (LE) xFF xFE x00 x00
# UTF-32 (BE) x00 x00 xFE xFF
# UTF-32-LE @ x00 x00 x00
# UTF-32-BE x00 x00 x00 @
# CHARSET @ c h a ...
def detectencoding_str(input, final=False):
"""
Detect the encoding of the byte string ``input``, which contains the
beginning of a CSS file. This function returns the detected encoding (or
``None`` if it hasn't got enough data), and a flag that indicates whether
that encoding has been detected explicitely or implicitely. To detect the
encoding the first few bytes are used (or if ``input`` is ASCII compatible
and starts with a charset rule the encoding name from the rule). "Explicit"
detection means that the bytes start with a BOM or a charset rule.
If the encoding can't be detected yet, ``None`` is returned as the encoding.
``final`` specifies whether more data will be available in later calls or
not. If ``final`` is true, ``detectencoding_str()`` will never return
``None`` as the encoding.
"""
# A bit for every candidate
CANDIDATE_UTF_8_SIG = 1
CANDIDATE_UTF_16_AS_LE = 2
CANDIDATE_UTF_16_AS_BE = 4
CANDIDATE_UTF_16_LE = 8
CANDIDATE_UTF_16_BE = 16
CANDIDATE_UTF_32_AS_LE = 32
CANDIDATE_UTF_32_AS_BE = 64
CANDIDATE_UTF_32_LE = 128
CANDIDATE_UTF_32_BE = 256
CANDIDATE_CHARSET = 512
candidates = 1023 # all candidates
li = len(input)
if li>=1:
# Check first byte
c = input[0]
if c != "\xef":
candidates &= ~CANDIDATE_UTF_8_SIG
if c != "\xff":
candidates &= ~(CANDIDATE_UTF_32_AS_LE|CANDIDATE_UTF_16_AS_LE)
if c != "\xfe":
candidates &= ~CANDIDATE_UTF_16_AS_BE
if c != "@":
candidates &= ~(CANDIDATE_UTF_32_LE|CANDIDATE_UTF_16_LE|CANDIDATE_CHARSET)
if c != "\x00":
candidates &= ~(CANDIDATE_UTF_32_AS_BE|CANDIDATE_UTF_32_BE|CANDIDATE_UTF_16_BE)
if li>=2:
# Check second byte
c = input[1]
if c != "\xbb":
candidates &= ~CANDIDATE_UTF_8_SIG
if c != "\xfe":
candidates &= ~(CANDIDATE_UTF_16_AS_LE|CANDIDATE_UTF_32_AS_LE)
if c != "\xff":
candidates &= ~CANDIDATE_UTF_16_AS_BE
if c != "\x00":
candidates &= ~(CANDIDATE_UTF_16_LE|CANDIDATE_UTF_32_AS_BE|CANDIDATE_UTF_32_LE|CANDIDATE_UTF_32_BE)
if c != "@":
candidates &= ~CANDIDATE_UTF_16_BE
if c != "c":
candidates &= ~CANDIDATE_CHARSET
if li>=3:
# Check third byte
c = input[2]
if c != "\xbf":
candidates &= ~CANDIDATE_UTF_8_SIG
if c != "c":
candidates &= ~CANDIDATE_UTF_16_LE
if c != "\x00":
candidates &= ~(CANDIDATE_UTF_32_AS_LE|CANDIDATE_UTF_32_LE|CANDIDATE_UTF_32_BE)
if c != "\xfe":
candidates &= ~CANDIDATE_UTF_32_AS_BE
if c != "h":
candidates &= ~CANDIDATE_CHARSET
if li>=4:
# Check fourth byte
c = input[3]
if input[2:4] == "\x00\x00":
candidates &= ~CANDIDATE_UTF_16_AS_LE
if c != "\x00":
candidates &= ~(CANDIDATE_UTF_16_LE|CANDIDATE_UTF_32_AS_LE|CANDIDATE_UTF_32_LE)
if c != "\xff":
candidates &= ~CANDIDATE_UTF_32_AS_BE
if c != "@":
candidates &= ~CANDIDATE_UTF_32_BE
if c != "a":
candidates &= ~CANDIDATE_CHARSET
if candidates == 0:
return ("utf-8", False)
if not (candidates & (candidates-1)): # only one candidate remaining
if candidates == CANDIDATE_UTF_8_SIG and li >= 3:
return ("utf-8-sig", True)
elif candidates == CANDIDATE_UTF_16_AS_LE and li >= 2:
return ("utf-16", True)
elif candidates == CANDIDATE_UTF_16_AS_BE and li >= 2:
return ("utf-16", True)
elif candidates == CANDIDATE_UTF_16_LE and li >= 4:
return ("utf-16-le", False)
elif candidates == CANDIDATE_UTF_16_BE and li >= 2:
return ("utf-16-be", False)
elif candidates == CANDIDATE_UTF_32_AS_LE and li >= 4:
return ("utf-32", True)
elif candidates == CANDIDATE_UTF_32_AS_BE and li >= 4:
return ("utf-32", True)
elif candidates == CANDIDATE_UTF_32_LE and li >= 4:
return ("utf-32-le", False)
elif candidates == CANDIDATE_UTF_32_BE and li >= 4:
return ("utf-32-be", False)
elif candidates == CANDIDATE_CHARSET and li >= 4:
prefix = '@charset "'
if input[:len(prefix)] == prefix:
pos = input.find('"', len(prefix))
if pos >= 0:
return (input[len(prefix):pos], True)
# if this is the last call, and we haven't determined an encoding yet,
# we default to UTF-8
if final:
return ("utf-8", False)
return (None, False) # dont' know yet
def detectencoding_unicode(input, final=False):
"""
Detect the encoding of the unicode string ``input``, which contains the
beginning of a CSS file. The encoding is detected from the charset rule
at the beginning of ``input``. If there is no charset rule, ``"utf-8"``
will be returned.
If the encoding can't be detected yet, ``None`` is returned. ``final``
specifies whether more data will be available in later calls or not. If
``final`` is true, ``detectencoding_unicode()`` will never return ``None``.
"""
prefix = u'@charset "'
if input.startswith(prefix):
pos = input.find(u'"', len(prefix))
if pos >= 0:
return (input[len(prefix):pos], True)
elif final or not prefix.startswith(input):
# if this is the last call, and we haven't determined an encoding yet,
# (or the string definitely doesn't start with prefix) we default to UTF-8
return ("utf-8", False)
return (None, False) # don't know yet
def _fixencoding(input, encoding, final=False):
"""
Replace the name of the encoding in the charset rule at the beginning of
``input`` with ``encoding``. If ``input`` doesn't starts with a charset
rule, ``input`` will be returned unmodified.
If the encoding can't be found yet, ``None`` is returned. ``final``
specifies whether more data will be available in later calls or not.
If ``final`` is true, ``_fixencoding()`` will never return ``None``.
"""
prefix = u'@charset "'
if len(input) > len(prefix):
if input.startswith(prefix):
pos = input.find(u'"', len(prefix))
if pos >= 0:
if encoding.replace("_", "-").lower() == "utf-8-sig":
encoding = u"utf-8"
return prefix + encoding + input[pos:]
# we haven't seen the end of the encoding name yet => fall through
else:
return input # doesn't start with prefix, so nothing to fix
elif not prefix.startswith(input) or final:
# can't turn out to be a @charset rule later (or there is no "later")
return input
if final:
return input
return None # don't know yet
def decode(input, errors="strict", encoding=None, force=True):
if encoding is None or not force:
(_encoding, explicit) = detectencoding_str(input, True)
if _encoding == "css":
raise ValueError("css not allowed as encoding name")
if (explicit and not force) or encoding is None: # Take the encoding from the input
encoding = _encoding
(input, consumed) = codecs.getdecoder(encoding)(input, errors)
return (_fixencoding(input, unicode(encoding), True), consumed)
def encode(input, errors="strict", encoding=None):
consumed = len(input)
if encoding is None:
encoding = detectencoding_unicode(input, True)[0]
if encoding.replace("_", "-").lower() == "utf-8-sig":
input = _fixencoding(input, u"utf-8", True)
else:
input = _fixencoding(input, unicode(encoding), True)
if encoding == "css":
raise ValueError("css not allowed as encoding name")
encoder = codecs.getencoder(encoding)
return (encoder(input, errors)[0], consumed)
def _bytes2int(bytes):
# Helper: convert an 8 bit string into an ``int``.
i = 0
for byte in bytes:
i = (i<<8) + ord(byte)
return i
def _int2bytes(i):
# Helper: convert an ``int`` into an 8-bit string.
v = []
while i:
v.insert(0, chr(i&0xff))
i >>= 8
return "".join(v)
if hasattr(codecs, "IncrementalDecoder"):
class IncrementalDecoder(codecs.IncrementalDecoder):
def __init__(self, errors="strict", encoding=None, force=True):
self.decoder = None
self.encoding = encoding
self.force = force
codecs.IncrementalDecoder.__init__(self, errors)
# Store ``errors`` somewhere else,
# because we have to hide it in a property
self._errors = errors
self.buffer = u"".encode()
self.headerfixed = False
def iterdecode(self, input):
for part in input:
result = self.decode(part, False)
if result:
yield result
result = self.decode("", True)
if result:
yield result
def decode(self, input, final=False):
# We're doing basically the same as a ``BufferedIncrementalDecoder``,
# but since the buffer is only relevant until the encoding has been
# detected (in which case the buffer of the underlying codec might
# kick in), we're implementing buffering ourselves to avoid some
# overhead.
if self.decoder is None:
input = self.buffer + input
# Do we have to detect the encoding from the input?
if self.encoding is None or not self.force:
(encoding, explicit) = detectencoding_str(input, final)
if encoding is None: # no encoding determined yet
self.buffer = input # retry the complete input on the next call
return u"" # no encoding determined yet, so no output
elif encoding == "css":
raise ValueError("css not allowed as encoding name")
if (explicit and not self.force) or self.encoding is None: # Take the encoding from the input
self.encoding = encoding
self.buffer = "" # drop buffer, as the decoder might keep its own
decoder = codecs.getincrementaldecoder(self.encoding)
self.decoder = decoder(self._errors)
if self.headerfixed:
return self.decoder.decode(input, final)
# If we haven't fixed the header yet,
# the content of ``self.buffer`` is a ``unicode`` object
output = self.buffer + self.decoder.decode(input, final)
encoding = self.encoding
if encoding.replace("_", "-").lower() == "utf-8-sig":
encoding = "utf-8"
newoutput = _fixencoding(output, unicode(encoding), final)
if newoutput is None:
# retry fixing the @charset rule (but keep the decoded stuff)
self.buffer = output
return u""
self.headerfixed = True
return newoutput
def reset(self):
codecs.IncrementalDecoder.reset(self)
self.decoder = None
self.buffer = u"".encode()
self.headerfixed = False
def _geterrors(self):
return self._errors
def _seterrors(self, errors):
# Setting ``errors`` must be done on the real decoder too
if self.decoder is not None:
self.decoder.errors = errors
self._errors = errors
errors = property(_geterrors, _seterrors)
def getstate(self):
if self.decoder is not None:
state = (self.encoding, self.buffer, self.headerfixed, True, self.decoder.getstate())
else:
state = (self.encoding, self.buffer, self.headerfixed, False, None)
return ("", _bytes2int(marshal.dumps(state)))
def setstate(self, state):
state = _int2bytes(marshal.loads(state[1])) # ignore buffered input
self.encoding = state[0]
self.buffer = state[1]
self.headerfixed = state[2]
if state[3] is not None:
self.decoder = codecs.getincrementaldecoder(self.encoding)(self._errors)
self.decoder.setstate(state[4])
else:
self.decoder = None
if hasattr(codecs, "IncrementalEncoder"):
class IncrementalEncoder(codecs.IncrementalEncoder):
def __init__(self, errors="strict", encoding=None):
self.encoder = None
self.encoding = encoding
codecs.IncrementalEncoder.__init__(self, errors)
# Store ``errors`` somewhere else,
# because we have to hide it in a property
self._errors = errors
self.buffer = u""
def iterencode(self, input):
for part in input:
result = self.encode(part, False)
if result:
yield result
result = self.encode(u"", True)
if result:
yield result
def encode(self, input, final=False):
if self.encoder is None:
input = self.buffer + input
if self.encoding is not None:
# Replace encoding in the @charset rule with the specified one
encoding = self.encoding
if encoding.replace("_", "-").lower() == "utf-8-sig":
encoding = "utf-8"
newinput = _fixencoding(input, unicode(encoding), final)
if newinput is None: # @charset rule incomplete => Retry next time
self.buffer = input
return ""
input = newinput
else:
# Use encoding from the @charset declaration
self.encoding = detectencoding_unicode(input, final)[0]
if self.encoding is not None:
if self.encoding == "css":
raise ValueError("css not allowed as encoding name")
info = codecs.lookup(self.encoding)
encoding = self.encoding
if self.encoding.replace("_", "-").lower() == "utf-8-sig":
input = _fixencoding(input, u"utf-8", True)
self.encoder = info.incrementalencoder(self._errors)
self.buffer = u""
else:
self.buffer = input
return ""
return self.encoder.encode(input, final)
def reset(self):
codecs.IncrementalEncoder.reset(self)
self.encoder = None
self.buffer = u""
def _geterrors(self):
return self._errors
def _seterrors(self, errors):
# Setting ``errors ``must be done on the real encoder too
if self.encoder is not None:
self.encoder.errors = errors
self._errors = errors
errors = property(_geterrors, _seterrors)
def getstate(self):
if self.encoder is not None:
state = (self.encoding, self.buffer, True, self.encoder.getstate())
else:
state = (self.encoding, self.buffer, False, None)
return _bytes2int(marshal.dumps(state))
def setstate(self, state):
state = _int2bytes(marshal.loads(state))
self.encoding = state[0]
self.buffer = state[1]
if state[2] is not None:
self.encoder = codecs.getincrementalencoder(self.encoding)(self._errors)
self.encoder.setstate(state[4])
else:
self.encoder = None
class StreamWriter(codecs.StreamWriter):
def __init__(self, stream, errors="strict", encoding=None, header=False):
codecs.StreamWriter.__init__(self, stream, errors)
self.streamwriter = None
self.encoding = encoding
self._errors = errors
self.buffer = u""
def encode(self, input, errors='strict'):
li = len(input)
if self.streamwriter is None:
input = self.buffer + input
li = len(input)
if self.encoding is not None:
# Replace encoding in the @charset rule with the specified one
encoding = self.encoding
if encoding.replace("_", "-").lower() == "utf-8-sig":
encoding = "utf-8"
newinput = _fixencoding(input, unicode(encoding), False)
if newinput is None: # @charset rule incomplete => Retry next time
self.buffer = input
return ("", 0)
input = newinput
else:
# Use encoding from the @charset declaration
self.encoding = detectencoding_unicode(input, False)[0]
if self.encoding is not None:
if self.encoding == "css":
raise ValueError("css not allowed as encoding name")
self.streamwriter = codecs.getwriter(self.encoding)(self.stream, self._errors)
encoding = self.encoding
if self.encoding.replace("_", "-").lower() == "utf-8-sig":
input = _fixencoding(input, u"utf-8", True)
self.buffer = u""
else:
self.buffer = input
return ("", 0)
return (self.streamwriter.encode(input, errors)[0], li)
def _geterrors(self):
return self._errors
def _seterrors(self, errors):
# Setting ``errors`` must be done on the streamwriter too
if self.streamwriter is not None:
self.streamwriter.errors = errors
self._errors = errors
errors = property(_geterrors, _seterrors)
class StreamReader(codecs.StreamReader):
def __init__(self, stream, errors="strict", encoding=None, force=True):
codecs.StreamReader.__init__(self, stream, errors)
self.streamreader = None
self.encoding = encoding
self.force = force
self._errors = errors
def decode(self, input, errors='strict'):
if self.streamreader is None:
if self.encoding is None or not self.force:
(encoding, explicit) = detectencoding_str(input, False)
if encoding is None: # no encoding determined yet
return (u"", 0) # no encoding determined yet, so no output
elif encoding == "css":
raise ValueError("css not allowed as encoding name")
if (explicit and not self.force) or self.encoding is None: # Take the encoding from the input
self.encoding = encoding
streamreader = codecs.getreader(self.encoding)
streamreader = streamreader(self.stream, self._errors)
(output, consumed) = streamreader.decode(input, errors)
encoding = self.encoding
if encoding.replace("_", "-").lower() == "utf-8-sig":
encoding = "utf-8"
newoutput = _fixencoding(output, unicode(encoding), False)
if newoutput is not None:
self.streamreader = streamreader
return (newoutput, consumed)
return (u"", 0) # we will create a new streamreader on the next call
return self.streamreader.decode(input, errors)
def _geterrors(self):
return self._errors
def _seterrors(self, errors):
# Setting ``errors`` must be done on the streamreader too
if self.streamreader is not None:
self.streamreader.errors = errors
self._errors = errors
errors = property(_geterrors, _seterrors)
if hasattr(codecs, "CodecInfo"):
# We're running on Python 2.5 or better
def search_function(name):
if name == "css":
return codecs.CodecInfo(
name="css",
encode=encode,
decode=decode,
incrementalencoder=IncrementalEncoder,
incrementaldecoder=IncrementalDecoder,
streamwriter=StreamWriter,
streamreader=StreamReader,
)
else:
# If we're running on Python 2.4, define the utf-8-sig codec here
def utf8sig_encode(input, errors='strict'):
return (codecs.BOM_UTF8 + codecs.utf_8_encode(input, errors)[0], len(input))
def utf8sig_decode(input, errors='strict'):
prefix = 0
if input[:3] == codecs.BOM_UTF8:
input = input[3:]
prefix = 3
(output, consumed) = codecs.utf_8_decode(input, errors, True)
return (output, consumed+prefix)
class UTF8SigStreamWriter(codecs.StreamWriter):
def reset(self):
codecs.StreamWriter.reset(self)
try:
del self.encode
except AttributeError:
pass
def encode(self, input, errors='strict'):
self.encode = codecs.utf_8_encode
return utf8sig_encode(input, errors)
class UTF8SigStreamReader(codecs.StreamReader):
def reset(self):
codecs.StreamReader.reset(self)
try:
del self.decode
except AttributeError:
pass
def decode(self, input, errors='strict'):
if len(input) < 3 and codecs.BOM_UTF8.startswith(input):
# not enough data to decide if this is a BOM
# => try again on the next call
return (u"", 0)
self.decode = codecs.utf_8_decode
return utf8sig_decode(input, errors)
def search_function(name):
import encodings
name = encodings.normalize_encoding(name)
if name == "css":
return (encode, decode, StreamReader, StreamWriter)
elif name == "utf_8_sig":
return (utf8sig_encode, utf8sig_decode, UTF8SigStreamReader, UTF8SigStreamWriter)
codecs.register(search_function)
# Error handler for CSS escaping
def cssescape(exc):
if not isinstance(exc, UnicodeEncodeError):
raise TypeError("don't know how to handle %r" % exc)
return (u"".join(u"\\%06x" % ord(c) for c in exc.object[exc.start:exc.end]), exc.end)
codecs.register_error("cssescape", cssescape)

608
libs/cssutils/_codec3.py

@ -1,608 +0,0 @@
#!/usr/bin/env python
"""Python codec for CSS."""
__docformat__ = 'restructuredtext'
__author__ = 'Walter Doerwald'
__version__ = '$Id: util.py 1114 2008-03-05 13:22:59Z cthedot $'
import sys
import codecs
import marshal
# We're using bits to store all possible candidate encodings (or variants, i.e.
# we have two bits for the variants of UTF-16 and two for the
# variants of UTF-32).
#
# Prefixes for various CSS encodings
# UTF-8-SIG xEF xBB xBF
# UTF-16 (LE) xFF xFE ~x00|~x00
# UTF-16 (BE) xFE xFF
# UTF-16-LE @ x00 @ x00
# UTF-16-BE x00 @
# UTF-32 (LE) xFF xFE x00 x00
# UTF-32 (BE) x00 x00 xFE xFF
# UTF-32-LE @ x00 x00 x00
# UTF-32-BE x00 x00 x00 @
# CHARSET @ c h a ...
def chars(bytestring):
return ''.join(chr(byte) for byte in bytestring)
def detectencoding_str(input, final=False):
"""
Detect the encoding of the byte string ``input``, which contains the
beginning of a CSS file. This function returns the detected encoding (or
``None`` if it hasn't got enough data), and a flag that indicates whether
that encoding has been detected explicitely or implicitely. To detect the
encoding the first few bytes are used (or if ``input`` is ASCII compatible
and starts with a charset rule the encoding name from the rule). "Explicit"
detection means that the bytes start with a BOM or a charset rule.
If the encoding can't be detected yet, ``None`` is returned as the encoding.
``final`` specifies whether more data will be available in later calls or
not. If ``final`` is true, ``detectencoding_str()`` will never return
``None`` as the encoding.
"""
# A bit for every candidate
CANDIDATE_UTF_8_SIG = 1
CANDIDATE_UTF_16_AS_LE = 2
CANDIDATE_UTF_16_AS_BE = 4
CANDIDATE_UTF_16_LE = 8
CANDIDATE_UTF_16_BE = 16
CANDIDATE_UTF_32_AS_LE = 32
CANDIDATE_UTF_32_AS_BE = 64
CANDIDATE_UTF_32_LE = 128
CANDIDATE_UTF_32_BE = 256
CANDIDATE_CHARSET = 512
candidates = 1023 # all candidates
#input = chars(input)
li = len(input)
if li>=1:
# Check first byte
c = input[0]
if c != b"\xef"[0]:
candidates &= ~CANDIDATE_UTF_8_SIG
if c != b"\xff"[0]:
candidates &= ~(CANDIDATE_UTF_32_AS_LE|CANDIDATE_UTF_16_AS_LE)
if c != b"\xfe"[0]:
candidates &= ~CANDIDATE_UTF_16_AS_BE
if c != b"@"[0]:
candidates &= ~(CANDIDATE_UTF_32_LE|CANDIDATE_UTF_16_LE|CANDIDATE_CHARSET)
if c != b"\x00"[0]:
candidates &= ~(CANDIDATE_UTF_32_AS_BE|CANDIDATE_UTF_32_BE|CANDIDATE_UTF_16_BE)
if li>=2:
# Check second byte
c = input[1]
if c != b"\xbb"[0]:
candidates &= ~CANDIDATE_UTF_8_SIG
if c != b"\xfe"[0]:
candidates &= ~(CANDIDATE_UTF_16_AS_LE|CANDIDATE_UTF_32_AS_LE)
if c != b"\xff"[0]:
candidates &= ~CANDIDATE_UTF_16_AS_BE
if c != b"\x00"[0]:
candidates &= ~(CANDIDATE_UTF_16_LE|CANDIDATE_UTF_32_AS_BE|CANDIDATE_UTF_32_LE|CANDIDATE_UTF_32_BE)
if c != b"@"[0]:
candidates &= ~CANDIDATE_UTF_16_BE
if c != b"c"[0]:
candidates &= ~CANDIDATE_CHARSET
if li>=3:
# Check third byte
c = input[2]
if c != b"\xbf"[0]:
candidates &= ~CANDIDATE_UTF_8_SIG
if c != b"c"[0]:
candidates &= ~CANDIDATE_UTF_16_LE
if c != b"\x00"[0]:
candidates &= ~(CANDIDATE_UTF_32_AS_LE|CANDIDATE_UTF_32_LE|CANDIDATE_UTF_32_BE)
if c != b"\xfe"[0]:
candidates &= ~CANDIDATE_UTF_32_AS_BE
if c != b"h"[0]:
candidates &= ~CANDIDATE_CHARSET
if li>=4:
# Check fourth byte
c = input[3]
if input[2:4] == b"\x00\x00"[0:2]:
candidates &= ~CANDIDATE_UTF_16_AS_LE
if c != b"\x00"[0]:
candidates &= ~(CANDIDATE_UTF_16_LE|CANDIDATE_UTF_32_AS_LE|CANDIDATE_UTF_32_LE)
if c != b"\xff"[0]:
candidates &= ~CANDIDATE_UTF_32_AS_BE
if c != b"@"[0]:
candidates &= ~CANDIDATE_UTF_32_BE
if c != b"a"[0]:
candidates &= ~CANDIDATE_CHARSET
if candidates == 0:
return ("utf-8", False)
if not (candidates & (candidates-1)): # only one candidate remaining
if candidates == CANDIDATE_UTF_8_SIG and li >= 3:
return ("utf-8-sig", True)
elif candidates == CANDIDATE_UTF_16_AS_LE and li >= 2:
return ("utf-16", True)
elif candidates == CANDIDATE_UTF_16_AS_BE and li >= 2:
return ("utf-16", True)
elif candidates == CANDIDATE_UTF_16_LE and li >= 4:
return ("utf-16-le", False)
elif candidates == CANDIDATE_UTF_16_BE and li >= 2:
return ("utf-16-be", False)
elif candidates == CANDIDATE_UTF_32_AS_LE and li >= 4:
return ("utf-32", True)
elif candidates == CANDIDATE_UTF_32_AS_BE and li >= 4:
return ("utf-32", True)
elif candidates == CANDIDATE_UTF_32_LE and li >= 4:
return ("utf-32-le", False)
elif candidates == CANDIDATE_UTF_32_BE and li >= 4:
return ("utf-32-be", False)
elif candidates == CANDIDATE_CHARSET and li >= 4:
prefix = '@charset "'
charsinput = chars(input)
if charsinput[:len(prefix)] == prefix:
pos = charsinput.find('"', len(prefix))
if pos >= 0:
# TODO: return str and not bytes!
return (charsinput[len(prefix):pos], True)
# if this is the last call, and we haven't determined an encoding yet,
# we default to UTF-8
if final:
return ("utf-8", False)
return (None, False) # dont' know yet
def detectencoding_unicode(input, final=False):
"""
Detect the encoding of the unicode string ``input``, which contains the
beginning of a CSS file. The encoding is detected from the charset rule
at the beginning of ``input``. If there is no charset rule, ``"utf-8"``
will be returned.
If the encoding can't be detected yet, ``None`` is returned. ``final``
specifies whether more data will be available in later calls or not. If
``final`` is true, ``detectencoding_unicode()`` will never return ``None``.
"""
prefix = '@charset "'
if input.startswith(prefix):
pos = input.find('"', len(prefix))
if pos >= 0:
return (input[len(prefix):pos], True)
elif final or not prefix.startswith(input):
# if this is the last call, and we haven't determined an encoding yet,
# (or the string definitely doesn't start with prefix) we default to UTF-8
return ("utf-8", False)
return (None, False) # don't know yet
def _fixencoding(input, encoding, final=False):
"""
Replace the name of the encoding in the charset rule at the beginning of
``input`` with ``encoding``. If ``input`` doesn't starts with a charset
rule, ``input`` will be returned unmodified.
If the encoding can't be found yet, ``None`` is returned. ``final``
specifies whether more data will be available in later calls or not.
If ``final`` is true, ``_fixencoding()`` will never return ``None``.
"""
prefix = '@charset "'
if len(input) > len(prefix):
if input.startswith(prefix):
pos = input.find('"', len(prefix))
if pos >= 0:
if encoding.replace("_", "-").lower() == "utf-8-sig":
encoding = "utf-8"
return prefix + encoding + input[pos:]
# we haven't seen the end of the encoding name yet => fall through
else:
return input # doesn't start with prefix, so nothing to fix
elif not prefix.startswith(input) or final:
# can't turn out to be a @charset rule later (or there is no "later")
return input
if final:
return input
return None # don't know yet
def decode(input, errors="strict", encoding=None, force=True):
try:
# py 3 only, memory?! object to bytes
input = input.tobytes()
except AttributeError as e:
pass
if encoding is None or not force:
(_encoding, explicit) = detectencoding_str(input, True)
if _encoding == "css":
raise ValueError("css not allowed as encoding name")
if (explicit and not force) or encoding is None: # Take the encoding from the input
encoding = _encoding
# NEEDS: change in parse.py (str to bytes!)
(input, consumed) = codecs.getdecoder(encoding)(input, errors)
return (_fixencoding(input, str(encoding), True), consumed)
def encode(input, errors="strict", encoding=None):
consumed = len(input)
if encoding is None:
encoding = detectencoding_unicode(input, True)[0]
if encoding.replace("_", "-").lower() == "utf-8-sig":
input = _fixencoding(input, "utf-8", True)
else:
input = _fixencoding(input, str(encoding), True)
if encoding == "css":
raise ValueError("css not allowed as encoding name")
encoder = codecs.getencoder(encoding)
return (encoder(input, errors)[0], consumed)
def _bytes2int(bytes):
# Helper: convert an 8 bit string into an ``int``.
i = 0
for byte in bytes:
i = (i<<8) + ord(byte)
return i
def _int2bytes(i):
# Helper: convert an ``int`` into an 8-bit string.
v = []
while i:
v.insert(0, chr(i&0xff))
i >>= 8
return "".join(v)
if hasattr(codecs, "IncrementalDecoder"):
class IncrementalDecoder(codecs.IncrementalDecoder):
def __init__(self, errors="strict", encoding=None, force=True):
self.decoder = None
self.encoding = encoding
self.force = force
codecs.IncrementalDecoder.__init__(self, errors)
# Store ``errors`` somewhere else,
# because we have to hide it in a property
self._errors = errors
self.buffer = b""
self.headerfixed = False
def iterdecode(self, input):
for part in input:
result = self.decode(part, False)
if result:
yield result
result = self.decode("", True)
if result:
yield result
def decode(self, input, final=False):
# We're doing basically the same as a ``BufferedIncrementalDecoder``,
# but since the buffer is only relevant until the encoding has been
# detected (in which case the buffer of the underlying codec might
# kick in), we're implementing buffering ourselves to avoid some
# overhead.
if self.decoder is None:
input = self.buffer + input
# Do we have to detect the encoding from the input?
if self.encoding is None or not self.force:
(encoding, explicit) = detectencoding_str(input, final)
if encoding is None: # no encoding determined yet
self.buffer = input # retry the complete input on the next call
return "" # no encoding determined yet, so no output
elif encoding == "css":
raise ValueError("css not allowed as encoding name")
if (explicit and not self.force) or self.encoding is None: # Take the encoding from the input
self.encoding = encoding
self.buffer = "" # drop buffer, as the decoder might keep its own
decoder = codecs.getincrementaldecoder(self.encoding)
self.decoder = decoder(self._errors)
if self.headerfixed:
return self.decoder.decode(input, final)
# If we haven't fixed the header yet,
# the content of ``self.buffer`` is a ``unicode`` object
output = self.buffer + self.decoder.decode(input, final)
encoding = self.encoding
if encoding.replace("_", "-").lower() == "utf-8-sig":
encoding = "utf-8"
newoutput = _fixencoding(output, str(encoding), final)
if newoutput is None:
# retry fixing the @charset rule (but keep the decoded stuff)
self.buffer = output
return ""
self.headerfixed = True
return newoutput
def reset(self):
codecs.IncrementalDecoder.reset(self)
self.decoder = None
self.buffer = b""
self.headerfixed = False
def _geterrors(self):
return self._errors
def _seterrors(self, errors):
# Setting ``errors`` must be done on the real decoder too
if self.decoder is not None:
self.decoder.errors = errors
self._errors = errors
errors = property(_geterrors, _seterrors)
def getstate(self):
if self.decoder is not None:
state = (self.encoding, self.buffer, self.headerfixed, True, self.decoder.getstate())
else:
state = (self.encoding, self.buffer, self.headerfixed, False, None)
return ("", _bytes2int(marshal.dumps(state)))
def setstate(self, state):
state = _int2bytes(marshal.loads(state[1])) # ignore buffered input
self.encoding = state[0]
self.buffer = state[1]
self.headerfixed = state[2]
if state[3] is not None:
self.decoder = codecs.getincrementaldecoder(self.encoding)(self._errors)
self.decoder.setstate(state[4])
else:
self.decoder = None
if hasattr(codecs, "IncrementalEncoder"):
class IncrementalEncoder(codecs.IncrementalEncoder):
def __init__(self, errors="strict", encoding=None):
self.encoder = None
self.encoding = encoding
codecs.IncrementalEncoder.__init__(self, errors)
# Store ``errors`` somewhere else,
# because we have to hide it in a property
self._errors = errors
self.buffer = ""
def iterencode(self, input):
for part in input:
result = self.encode(part, False)
if result:
yield result
result = self.encode("", True)
if result:
yield result
def encode(self, input, final=False):
if self.encoder is None:
input = self.buffer + input
if self.encoding is not None:
# Replace encoding in the @charset rule with the specified one
encoding = self.encoding
if encoding.replace("_", "-").lower() == "utf-8-sig":
encoding = "utf-8"
newinput = _fixencoding(input, str(encoding), final)
if newinput is None: # @charset rule incomplete => Retry next time
self.buffer = input
return ""
input = newinput
else:
# Use encoding from the @charset declaration
self.encoding = detectencoding_unicode(input, final)[0]
if self.encoding is not None:
if self.encoding == "css":
raise ValueError("css not allowed as encoding name")
info = codecs.lookup(self.encoding)
encoding = self.encoding
if self.encoding.replace("_", "-").lower() == "utf-8-sig":
input = _fixencoding(input, "utf-8", True)
self.encoder = info.incrementalencoder(self._errors)
self.buffer = ""
else:
self.buffer = input
return ""
return self.encoder.encode(input, final)
def reset(self):
codecs.IncrementalEncoder.reset(self)
self.encoder = None
self.buffer = ""
def _geterrors(self):
return self._errors
def _seterrors(self, errors):
# Setting ``errors ``must be done on the real encoder too
if self.encoder is not None:
self.encoder.errors = errors
self._errors = errors
errors = property(_geterrors, _seterrors)
def getstate(self):
if self.encoder is not None:
state = (self.encoding, self.buffer, True, self.encoder.getstate())
else:
state = (self.encoding, self.buffer, False, None)
return _bytes2int(marshal.dumps(state))
def setstate(self, state):
state = _int2bytes(marshal.loads(state))
self.encoding = state[0]
self.buffer = state[1]
if state[2] is not None:
self.encoder = codecs.getincrementalencoder(self.encoding)(self._errors)
self.encoder.setstate(state[4])
else:
self.encoder = None
class StreamWriter(codecs.StreamWriter):
def __init__(self, stream, errors="strict", encoding=None, header=False):
codecs.StreamWriter.__init__(self, stream, errors)
self.streamwriter = None
self.encoding = encoding
self._errors = errors
self.buffer = ""
def encode(self, input, errors='strict'):
li = len(input)
if self.streamwriter is None:
input = self.buffer + input
li = len(input)
if self.encoding is not None:
# Replace encoding in the @charset rule with the specified one
encoding = self.encoding
if encoding.replace("_", "-").lower() == "utf-8-sig":
encoding = "utf-8"
newinput = _fixencoding(input, str(encoding), False)
if newinput is None: # @charset rule incomplete => Retry next time
self.buffer = input
return ("", 0)
input = newinput
else:
# Use encoding from the @charset declaration
self.encoding = detectencoding_unicode(input, False)[0]
if self.encoding is not None:
if self.encoding == "css":
raise ValueError("css not allowed as encoding name")
self.streamwriter = codecs.getwriter(self.encoding)(self.stream, self._errors)
encoding = self.encoding
if self.encoding.replace("_", "-").lower() == "utf-8-sig":
input = _fixencoding(input, "utf-8", True)
self.buffer = ""
else:
self.buffer = input
return ("", 0)
return (self.streamwriter.encode(input, errors)[0], li)
def _geterrors(self):
return self._errors
def _seterrors(self, errors):
# Setting ``errors`` must be done on the streamwriter too
try:
if self.streamwriter is not None:
self.streamwriter.errors = errors
except AttributeError as e:
# TODO: py3 only exception?
pass
self._errors = errors
errors = property(_geterrors, _seterrors)
class StreamReader(codecs.StreamReader):
def __init__(self, stream, errors="strict", encoding=None, force=True):
codecs.StreamReader.__init__(self, stream, errors)
self.streamreader = None
self.encoding = encoding
self.force = force
self._errors = errors
def decode(self, input, errors='strict'):
if self.streamreader is None:
if self.encoding is None or not self.force:
(encoding, explicit) = detectencoding_str(input, False)
if encoding is None: # no encoding determined yet
return ("", 0) # no encoding determined yet, so no output
elif encoding == "css":
raise ValueError("css not allowed as encoding name")
if (explicit and not self.force) or self.encoding is None: # Take the encoding from the input
self.encoding = encoding
streamreader = codecs.getreader(self.encoding)
streamreader = streamreader(self.stream, self._errors)
(output, consumed) = streamreader.decode(input, errors)
encoding = self.encoding
if encoding.replace("_", "-").lower() == "utf-8-sig":
encoding = "utf-8"
newoutput = _fixencoding(output, str(encoding), False)
if newoutput is not None:
self.streamreader = streamreader
return (newoutput, consumed)
return ("", 0) # we will create a new streamreader on the next call
return self.streamreader.decode(input, errors)
def _geterrors(self):
return self._errors
def _seterrors(self, errors):
# Setting ``errors`` must be done on the streamreader too
try:
if self.streamreader is not None:
self.streamreader.errors = errors
except AttributeError as e:
# TODO: py3 only exception?
pass
self._errors = errors
errors = property(_geterrors, _seterrors)
if hasattr(codecs, "CodecInfo"):
# We're running on Python 2.5 or better
def search_function(name):
if name == "css":
return codecs.CodecInfo(
name="css",
encode=encode,
decode=decode,
incrementalencoder=IncrementalEncoder,
incrementaldecoder=IncrementalDecoder,
streamwriter=StreamWriter,
streamreader=StreamReader,
)
else:
# If we're running on Python 2.4, define the utf-8-sig codec here
def utf8sig_encode(input, errors='strict'):
return (codecs.BOM_UTF8 + codecs.utf_8_encode(input, errors)[0], len(input))
def utf8sig_decode(input, errors='strict'):
prefix = 0
if input[:3] == codecs.BOM_UTF8:
input = input[3:]
prefix = 3
(output, consumed) = codecs.utf_8_decode(input, errors, True)
return (output, consumed+prefix)
class UTF8SigStreamWriter(codecs.StreamWriter):
def reset(self):
codecs.StreamWriter.reset(self)
try:
del self.encode
except AttributeError:
pass
def encode(self, input, errors='strict'):
self.encode = codecs.utf_8_encode
return utf8sig_encode(input, errors)
class UTF8SigStreamReader(codecs.StreamReader):
def reset(self):
codecs.StreamReader.reset(self)
try:
del self.decode
except AttributeError:
pass
def decode(self, input, errors='strict'):
if len(input) < 3 and codecs.BOM_UTF8.startswith(input):
# not enough data to decide if this is a BOM
# => try again on the next call
return ("", 0)
self.decode = codecs.utf_8_decode
return utf8sig_decode(input, errors)
def search_function(name):
import encodings
name = encodings.normalize_encoding(name)
if name == "css":
return (encode, decode, StreamReader, StreamWriter)
elif name == "utf_8_sig":
return (utf8sig_encode, utf8sig_decode, UTF8SigStreamReader, UTF8SigStreamWriter)
codecs.register(search_function)
# Error handler for CSS escaping
def cssescape(exc):
if not isinstance(exc, UnicodeEncodeError):
raise TypeError("don't know how to handle %r" % exc)
return ("".join("\\%06x" % ord(c) for c in exc.object[exc.start:exc.end]), exc.end)
codecs.register_error("cssescape", cssescape)

44
libs/cssutils/_fetch.py

@ -1,44 +0,0 @@
"""Default URL reading functions"""
__all__ = ['_defaultFetcher']
__docformat__ = 'restructuredtext'
__version__ = '$Id: tokenize2.py 1547 2008-12-10 20:42:26Z cthedot $'
import cssutils
from cssutils import VERSION
import encutils
import errorhandler
import urllib2
log = errorhandler.ErrorHandler()
def _defaultFetcher(url):
"""Retrieve data from ``url``. cssutils default implementation of fetch
URL function.
Returns ``(encoding, string)`` or ``None``
"""
try:
request = urllib2.Request(url)
request.add_header('User-agent',
'cssutils %s (http://www.cthedot.de/cssutils/)' % VERSION)
res = urllib2.urlopen(request)
except OSError, e:
# e.g if file URL and not found
log.warn(e, error=OSError)
except (OSError, ValueError), e:
# invalid url, e.g. "1"
log.warn(u'ValueError, %s' % e.args[0], error=ValueError)
except urllib2.HTTPError, e:
# http error, e.g. 404, e can be raised
log.warn(u'HTTPError opening url=%s: %s %s' %
(url, e.code, e.msg), error=e)
except urllib2.URLError, e:
# URLError like mailto: or other IO errors, e can be raised
log.warn(u'URLError, %s' % e.reason, error=e)
else:
if res:
mimeType, encoding = encutils.getHTTPInfo(res)
if mimeType != u'text/css':
log.error(u'Expected "text/css" mime type for url=%r but found: %r' %
(url, mimeType), error=ValueError)
return encoding, res.read()

68
libs/cssutils/_fetchgae.py

@ -1,68 +0,0 @@
"""GAE specific URL reading functions"""
__all__ = ['_defaultFetcher']
__docformat__ = 'restructuredtext'
__version__ = '$Id: tokenize2.py 1547 2008-12-10 20:42:26Z cthedot $'
# raises ImportError of not on GAE
from google.appengine.api import urlfetch
import cgi
import errorhandler
import util
log = errorhandler.ErrorHandler()
def _defaultFetcher(url):
"""
uses GoogleAppEngine (GAE)
fetch(url, payload=None, method=GET, headers={}, allow_truncated=False)
Response
content
The body content of the response.
content_was_truncated
True if the allow_truncated parameter to fetch() was True and
the response exceeded the maximum response size. In this case,
the content attribute contains the truncated response.
status_code
The HTTP status code.
headers
The HTTP response headers, as a mapping of names to values.
Exceptions
exception InvalidURLError()
The URL of the request was not a valid URL, or it used an
unsupported method. Only http and https URLs are supported.
exception DownloadError()
There was an error retrieving the data.
This exception is not raised if the server returns an HTTP
error code: In that case, the response data comes back intact,
including the error code.
exception ResponseTooLargeError()
The response data exceeded the maximum allowed size, and the
allow_truncated parameter passed to fetch() was False.
"""
#from google.appengine.api import urlfetch
try:
r = urlfetch.fetch(url, method=urlfetch.GET)
except urlfetch.Error, e:
log.warn(u'Error opening url=%r: %s' % (url, e),
error=IOError)
else:
if r.status_code == 200:
# find mimetype and encoding
mimetype = 'application/octet-stream'
try:
mimetype, params = cgi.parse_header(r.headers['content-type'])
encoding = params['charset']
except KeyError:
encoding = None
if mimetype != u'text/css':
log.error(u'Expected "text/css" mime type for url %r but found: %r' %
(url, mimetype), error=ValueError)
return encoding, r.content
else:
# TODO: 301 etc
log.warn(u'Error opening url=%r: HTTP status %s' %
(url, r.status_code), error=IOError)

16
libs/cssutils/codec.py

@ -1,16 +0,0 @@
#!/usr/bin/env python
"""Python codec for CSS."""
__docformat__ = 'restructuredtext'
__author__ = 'Walter Doerwald'
__version__ = '$Id: util.py 1114 2008-03-05 13:22:59Z cthedot $'
import sys
if sys.version_info < (3,):
from _codec2 import *
# for tests
from _codec2 import _fixencoding
else:
from _codec3 import *
# for tests
from _codec3 import _fixencoding

80
libs/cssutils/css/__init__.py

@ -1,80 +0,0 @@
"""Implements Document Object Model Level 2 CSS
http://www.w3.org/TR/2000/PR-DOM-Level-2-Style-20000927/css.html
currently implemented
- CSSStyleSheet
- CSSRuleList
- CSSRule
- CSSComment (cssutils addon)
- CSSCharsetRule
- CSSFontFaceRule
- CSSImportRule
- CSSMediaRule
- CSSNamespaceRule (WD)
- CSSPageRule
- CSSStyleRule
- CSSUnkownRule
- Selector and SelectorList
- CSSStyleDeclaration
- CSS2Properties
- CSSValue
- CSSPrimitiveValue
- CSSValueList
- CSSVariablesRule
- CSSVariablesDeclaration
todo
- RGBColor, Rect, Counter
"""
__all__ = [
'CSSStyleSheet',
'CSSRuleList',
'CSSRule',
'CSSComment',
'CSSCharsetRule',
'CSSFontFaceRule'
'CSSImportRule',
'CSSMediaRule',
'CSSNamespaceRule',
'CSSPageRule',
'MarginRule',
'CSSStyleRule',
'CSSUnknownRule',
'CSSVariablesRule',
'CSSVariablesDeclaration',
'Selector', 'SelectorList',
'CSSStyleDeclaration', 'Property',
#'CSSValue', 'CSSPrimitiveValue', 'CSSValueList'
'PropertyValue',
'Value',
'ColorValue',
'DimensionValue',
'URIValue',
'CSSFunction',
'CSSVariable',
'MSValue'
]
__docformat__ = 'restructuredtext'
__version__ = '$Id$'
from cssstylesheet import *
from cssrulelist import *
from cssrule import *
from csscomment import *
from csscharsetrule import *
from cssfontfacerule import *
from cssimportrule import *
from cssmediarule import *
from cssnamespacerule import *
from csspagerule import *
from marginrule import *
from cssstylerule import *
from cssvariablesrule import *
from cssunknownrule import *
from selector import *
from selectorlist import *
from cssstyledeclaration import *
from cssvariablesdeclaration import *
from property import *
#from cssvalue import *
from value import *

184
libs/cssutils/css/colors.py

@ -1,184 +0,0 @@
# -*- coding: utf-8 -*-
"""
Built from something like this:
print [
(
row[2].text_content().strip(),
eval(row[4].text_content().strip())
)
for row in lxml.html.parse('http://www.w3.org/TR/css3-color/')
.xpath("//*[@class='colortable']//tr[position()>1]")
]
by Simon Sapin
"""
COLORS = {
'transparent': (0, 0, 0, 0.0),
'black': (0, 0, 0, 1.0),
'silver': (192, 192, 192, 1.0),
'gray': (128, 128, 128, 1.0),
'white': (255, 255, 255, 1.0),
'maroon': (128, 0, 0, 1.0),
'red': (255, 0, 0, 1.0),
'purple': (128, 0, 128, 1.0),
'fuchsia': (255, 0, 255, 1.0),
'green': (0, 128, 0, 1.0),
'lime': (0, 255, 0, 1.0),
'olive': (128, 128, 0, 1.0),
'yellow': (255, 255, 0, 1.0),
'navy': (0, 0, 128, 1.0),
'blue': (0, 0, 255, 1.0),
'teal': (0, 128, 128, 1.0),
'aqua': (0, 255, 255, 1.0),
'aliceblue': (240, 248, 255, 1.0),
'antiquewhite': (250, 235, 215, 1.0),
'aqua': (0, 255, 255, 1.0),
'aquamarine': (127, 255, 212, 1.0),
'azure': (240, 255, 255, 1.0),
'beige': (245, 245, 220, 1.0),
'bisque': (255, 228, 196, 1.0),
'black': (0, 0, 0, 1.0),
'blanchedalmond': (255, 235, 205, 1.0),
'blue': (0, 0, 255, 1.0),
'blueviolet': (138, 43, 226, 1.0),
'brown': (165, 42, 42, 1.0),
'burlywood': (222, 184, 135, 1.0),
'cadetblue': (95, 158, 160, 1.0),
'chartreuse': (127, 255, 0, 1.0),
'chocolate': (210, 105, 30, 1.0),
'coral': (255, 127, 80, 1.0),
'cornflowerblue': (100, 149, 237, 1.0),
'cornsilk': (255, 248, 220, 1.0),
'crimson': (220, 20, 60, 1.0),
'cyan': (0, 255, 255, 1.0),
'darkblue': (0, 0, 139, 1.0),
'darkcyan': (0, 139, 139, 1.0),
'darkgoldenrod': (184, 134, 11, 1.0),
'darkgray': (169, 169, 169, 1.0),
'darkgreen': (0, 100, 0, 1.0),
'darkgrey': (169, 169, 169, 1.0),
'darkkhaki': (189, 183, 107, 1.0),
'darkmagenta': (139, 0, 139, 1.0),
'darkolivegreen': (85, 107, 47, 1.0),
'darkorange': (255, 140, 0, 1.0),
'darkorchid': (153, 50, 204, 1.0),
'darkred': (139, 0, 0, 1.0),
'darksalmon': (233, 150, 122, 1.0),
'darkseagreen': (143, 188, 143, 1.0),
'darkslateblue': (72, 61, 139, 1.0),
'darkslategray': (47, 79, 79, 1.0),
'darkslategrey': (47, 79, 79, 1.0),
'darkturquoise': (0, 206, 209, 1.0),
'darkviolet': (148, 0, 211, 1.0),
'deeppink': (255, 20, 147, 1.0),
'deepskyblue': (0, 191, 255, 1.0),
'dimgray': (105, 105, 105, 1.0),
'dimgrey': (105, 105, 105, 1.0),
'dodgerblue': (30, 144, 255, 1.0),
'firebrick': (178, 34, 34, 1.0),
'floralwhite': (255, 250, 240, 1.0),
'forestgreen': (34, 139, 34, 1.0),
'fuchsia': (255, 0, 255, 1.0),
'gainsboro': (220, 220, 220, 1.0),
'ghostwhite': (248, 248, 255, 1.0),
'gold': (255, 215, 0, 1.0),
'goldenrod': (218, 165, 32, 1.0),
'gray': (128, 128, 128, 1.0),
'green': (0, 128, 0, 1.0),
'greenyellow': (173, 255, 47, 1.0),
'grey': (128, 128, 128, 1.0),
'honeydew': (240, 255, 240, 1.0),
'hotpink': (255, 105, 180, 1.0),
'indianred': (205, 92, 92, 1.0),
'indigo': (75, 0, 130, 1.0),
'ivory': (255, 255, 240, 1.0),
'khaki': (240, 230, 140, 1.0),
'lavender': (230, 230, 250, 1.0),
'lavenderblush': (255, 240, 245, 1.0),
'lawngreen': (124, 252, 0, 1.0),
'lemonchiffon': (255, 250, 205, 1.0),
'lightblue': (173, 216, 230, 1.0),
'lightcoral': (240, 128, 128, 1.0),
'lightcyan': (224, 255, 255, 1.0),
'lightgoldenrodyellow': (250, 250, 210, 1.0),
'lightgray': (211, 211, 211, 1.0),
'lightgreen': (144, 238, 144, 1.0),
'lightgrey': (211, 211, 211, 1.0),
'lightpink': (255, 182, 193, 1.0),
'lightsalmon': (255, 160, 122, 1.0),
'lightseagreen': (32, 178, 170, 1.0),
'lightskyblue': (135, 206, 250, 1.0),
'lightslategray': (119, 136, 153, 1.0),
'lightslategrey': (119, 136, 153, 1.0),
'lightsteelblue': (176, 196, 222, 1.0),
'lightyellow': (255, 255, 224, 1.0),
'lime': (0, 255, 0, 1.0),
'limegreen': (50, 205, 50, 1.0),
'linen': (250, 240, 230, 1.0),
'magenta': (255, 0, 255, 1.0),
'maroon': (128, 0, 0, 1.0),
'mediumaquamarine': (102, 205, 170, 1.0),
'mediumblue': (0, 0, 205, 1.0),
'mediumorchid': (186, 85, 211, 1.0),
'mediumpurple': (147, 112, 219, 1.0),
'mediumseagreen': (60, 179, 113, 1.0),
'mediumslateblue': (123, 104, 238, 1.0),
'mediumspringgreen': (0, 250, 154, 1.0),
'mediumturquoise': (72, 209, 204, 1.0),
'mediumvioletred': (199, 21, 133, 1.0),
'midnightblue': (25, 25, 112, 1.0),
'mintcream': (245, 255, 250, 1.0),
'mistyrose': (255, 228, 225, 1.0),
'moccasin': (255, 228, 181, 1.0),
'navajowhite': (255, 222, 173, 1.0),
'navy': (0, 0, 128, 1.0),
'oldlace': (253, 245, 230, 1.0),
'olive': (128, 128, 0, 1.0),
'olivedrab': (107, 142, 35, 1.0),
'orange': (255, 165, 0, 1.0),
'orangered': (255, 69, 0, 1.0),
'orchid': (218, 112, 214, 1.0),
'palegoldenrod': (238, 232, 170, 1.0),
'palegreen': (152, 251, 152, 1.0),
'paleturquoise': (175, 238, 238, 1.0),
'palevioletred': (219, 112, 147, 1.0),
'papayawhip': (255, 239, 213, 1.0),
'peachpuff': (255, 218, 185, 1.0),
'peru': (205, 133, 63, 1.0),
'pink': (255, 192, 203, 1.0),
'plum': (221, 160, 221, 1.0),
'powderblue': (176, 224, 230, 1.0),
'purple': (128, 0, 128, 1.0),
'red': (255, 0, 0, 1.0),
'rosybrown': (188, 143, 143, 1.0),
'royalblue': (65, 105, 225, 1.0),
'saddlebrown': (139, 69, 19, 1.0),
'salmon': (250, 128, 114, 1.0),
'sandybrown': (244, 164, 96, 1.0),
'seagreen': (46, 139, 87, 1.0),
'seashell': (255, 245, 238, 1.0),
'sienna': (160, 82, 45, 1.0),
'silver': (192, 192, 192, 1.0),
'skyblue': (135, 206, 235, 1.0),
'slateblue': (106, 90, 205, 1.0),
'slategray': (112, 128, 144, 1.0),
'slategrey': (112, 128, 144, 1.0),
'snow': (255, 250, 250, 1.0),
'springgreen': (0, 255, 127, 1.0),
'steelblue': (70, 130, 180, 1.0),
'tan': (210, 180, 140, 1.0),
'teal': (0, 128, 128, 1.0),
'thistle': (216, 191, 216, 1.0),
'tomato': (255, 99, 71, 1.0),
'turquoise': (64, 224, 208, 1.0),
'violet': (238, 130, 238, 1.0),
'wheat': (245, 222, 179, 1.0),
'white': (255, 255, 255, 1.0),
'whitesmoke': (245, 245, 245, 1.0),
'yellow': (255, 255, 0, 1.0),
'yellowgreen': (154, 205, 50, 1.0),
}

159
libs/cssutils/css/csscharsetrule.py

@ -1,159 +0,0 @@
"""CSSCharsetRule implements DOM Level 2 CSS CSSCharsetRule."""
__all__ = ['CSSCharsetRule']
__docformat__ = 'restructuredtext'
__version__ = '$Id$'
import codecs
import cssrule
import cssutils
import xml.dom
class CSSCharsetRule(cssrule.CSSRule):
"""
The CSSCharsetRule interface represents an @charset rule in a CSS style
sheet. The value of the encoding attribute does not affect the encoding
of text data in the DOM objects; this encoding is always UTF-16
(also in Python?). After a stylesheet is loaded, the value of the
encoding attribute is the value found in the @charset rule. If there
was no @charset in the original document, then no CSSCharsetRule is
created. The value of the encoding attribute may also be used as a hint
for the encoding used on serialization of the style sheet.
The value of the @charset rule (and therefore of the CSSCharsetRule)
may not correspond to the encoding the document actually came in;
character encoding information e.g. in an HTTP header, has priority
(see CSS document representation) but this is not reflected in the
CSSCharsetRule.
This rule is not really needed anymore as setting
:attr:`CSSStyleSheet.encoding` is much easier.
Format::
charsetrule:
CHARSET_SYM S* STRING S* ';'
BUT: Only valid format is (single space, double quotes!)::
@charset "ENCODING";
"""
def __init__(self, encoding=None, parentRule=None,
parentStyleSheet=None, readonly=False):
"""
:param encoding:
a valid character encoding
:param readonly:
defaults to False, not used yet
"""
super(CSSCharsetRule, self).__init__(parentRule=parentRule,
parentStyleSheet=parentStyleSheet)
self._atkeyword = '@charset'
if encoding:
self.encoding = encoding
else:
self._encoding = None
self._readonly = readonly
def __repr__(self):
return u"cssutils.css.%s(encoding=%r)" % (
self.__class__.__name__,
self.encoding)
def __str__(self):
return u"<cssutils.css.%s object encoding=%r at 0x%x>" % (
self.__class__.__name__,
self.encoding,
id(self))
def _getCssText(self):
"""The parsable textual representation."""
return cssutils.ser.do_CSSCharsetRule(self)
def _setCssText(self, cssText):
"""
:param cssText:
A parsable DOMString.
:exceptions:
- :exc:`~xml.dom.SyntaxErr`:
Raised if the specified CSS string value has a syntax error and
is unparsable.
- :exc:`~xml.dom.InvalidModificationErr`:
Raised if the specified CSS string value represents a different
type of rule than the current one.
- :exc:`~xml.dom.HierarchyRequestErr`:
Raised if the rule cannot be inserted at this point in the
style sheet.
- :exc:`~xml.dom.NoModificationAllowedErr`:
Raised if the rule is readonly.
"""
super(CSSCharsetRule, self)._setCssText(cssText)
wellformed = True
tokenizer = self._tokenize2(cssText)
if self._type(self._nexttoken(tokenizer)) != self._prods.CHARSET_SYM:
wellformed = False
self._log.error(u'CSSCharsetRule must start with "@charset "',
error=xml.dom.InvalidModificationErr)
encodingtoken = self._nexttoken(tokenizer)
encodingtype = self._type(encodingtoken)
encoding = self._stringtokenvalue(encodingtoken)
if self._prods.STRING != encodingtype or not encoding:
wellformed = False
self._log.error(u'CSSCharsetRule: no encoding found; %r.' %
self._valuestr(cssText))
semicolon = self._tokenvalue(self._nexttoken(tokenizer))
EOFtype = self._type(self._nexttoken(tokenizer))
if u';' != semicolon or EOFtype not in ('EOF', None):
wellformed = False
self._log.error(u'CSSCharsetRule: Syntax Error: %r.' %
self._valuestr(cssText))
if wellformed:
self.encoding = encoding
cssText = property(fget=_getCssText, fset=_setCssText,
doc=u"(DOM) The parsable textual representation.")
def _setEncoding(self, encoding):
"""
:param encoding:
a valid encoding to be used. Currently only valid Python encodings
are allowed.
:exceptions:
- :exc:`~xml.dom.NoModificationAllowedErr`:
Raised if this encoding rule is readonly.
- :exc:`~xml.dom.SyntaxErr`:
Raised if the specified encoding value has a syntax error and
is unparsable.
"""
self._checkReadonly()
tokenizer = self._tokenize2(encoding)
encodingtoken = self._nexttoken(tokenizer)
unexpected = self._nexttoken(tokenizer)
if not encodingtoken or unexpected or\
self._prods.IDENT != self._type(encodingtoken):
self._log.error(u'CSSCharsetRule: Syntax Error in encoding value '
u'%r.' % encoding)
else:
try:
codecs.lookup(encoding)
except LookupError:
self._log.error(u'CSSCharsetRule: Unknown (Python) encoding %r.'
% encoding)
else:
self._encoding = encoding.lower()
encoding = property(lambda self: self._encoding, _setEncoding,
doc=u"(DOM)The encoding information used in this @charset rule.")
type = property(lambda self: self.CHARSET_RULE,
doc=u"The type of this rule, as defined by a CSSRule "
u"type constant.")
wellformed = property(lambda self: bool(self.encoding))

87
libs/cssutils/css/csscomment.py

@ -1,87 +0,0 @@
"""CSSComment is not defined in DOM Level 2 at all but a cssutils defined
class only.
Implements CSSRule which is also extended for a CSSComment rule type.
"""
__all__ = ['CSSComment']
__docformat__ = 'restructuredtext'
__version__ = '$Id$'
import cssrule
import cssutils
import xml.dom
class CSSComment(cssrule.CSSRule):
"""
Represents a CSS comment (cssutils only).
Format::
/*...*/
"""
def __init__(self, cssText=None, parentRule=None,
parentStyleSheet=None, readonly=False):
super(CSSComment, self).__init__(parentRule=parentRule,
parentStyleSheet=parentStyleSheet)
self._cssText = None
if cssText:
self._setCssText(cssText)
self._readonly = readonly
def __repr__(self):
return u"cssutils.css.%s(cssText=%r)" % (
self.__class__.__name__,
self.cssText)
def __str__(self):
return u"<cssutils.css.%s object cssText=%r at 0x%x>" % (
self.__class__.__name__,
self.cssText,
id(self))
def _getCssText(self):
"""Return serialized property cssText."""
return cssutils.ser.do_CSSComment(self)
def _setCssText(self, cssText):
"""
:param cssText:
textual text to set or tokenlist which is not tokenized
anymore. May also be a single token for this rule
:exceptions:
- :exc:`~xml.dom.SyntaxErr`:
Raised if the specified CSS string value has a syntax error and
is unparsable.
- :exc:`~xml.dom.InvalidModificationErr`:
Raised if the specified CSS string value represents a different
type of rule than the current one.
- :exc:`~xml.dom.NoModificationAllowedErr`:
Raised if the rule is readonly.
"""
super(CSSComment, self)._setCssText(cssText)
tokenizer = self._tokenize2(cssText)
commenttoken = self._nexttoken(tokenizer)
unexpected = self._nexttoken(tokenizer)
if not commenttoken or\
self._type(commenttoken) != self._prods.COMMENT or\
unexpected:
self._log.error(u'CSSComment: Not a CSSComment: %r' %
self._valuestr(cssText),
error=xml.dom.InvalidModificationErr)
else:
self._cssText = self._tokenvalue(commenttoken)
cssText = property(_getCssText, _setCssText,
doc=u"The parsable textual representation of this rule.")
type = property(lambda self: self.COMMENT,
doc=u"The type of this rule, as defined by a CSSRule "
u"type constant.")
# constant but needed:
wellformed = property(lambda self: True)

184
libs/cssutils/css/cssfontfacerule.py

@ -1,184 +0,0 @@
"""CSSFontFaceRule implements DOM Level 2 CSS CSSFontFaceRule.
From cssutils 0.9.6 additions from CSS Fonts Module Level 3 are
added http://www.w3.org/TR/css3-fonts/.
"""
__all__ = ['CSSFontFaceRule']
__docformat__ = 'restructuredtext'
__version__ = '$Id$'
from cssstyledeclaration import CSSStyleDeclaration
import cssrule
import cssutils
import xml.dom
class CSSFontFaceRule(cssrule.CSSRule):
"""
The CSSFontFaceRule interface represents a @font-face rule in a CSS
style sheet. The @font-face rule is used to hold a set of font
descriptions.
Format::
font_face
: FONT_FACE_SYM S*
'{' S* declaration [ ';' S* declaration ]* '}' S*
;
cssutils uses a :class:`~cssutils.css.CSSStyleDeclaration` to
represent the font descriptions. For validation a specific profile
is used though were some properties have other valid values than
when used in e.g. a :class:`~cssutils.css.CSSStyleRule`.
"""
def __init__(self, style=None, parentRule=None,
parentStyleSheet=None, readonly=False):
"""
If readonly allows setting of properties in constructor only.
:param style:
CSSStyleDeclaration used to hold any font descriptions
for this CSSFontFaceRule
"""
super(CSSFontFaceRule, self).__init__(parentRule=parentRule,
parentStyleSheet=parentStyleSheet)
self._atkeyword = u'@font-face'
if style:
self.style = style
else:
self.style = CSSStyleDeclaration()
self._readonly = readonly
def __repr__(self):
return u"cssutils.css.%s(style=%r)" % (
self.__class__.__name__,
self.style.cssText)
def __str__(self):
return u"<cssutils.css.%s object style=%r valid=%r at 0x%x>" % (
self.__class__.__name__,
self.style.cssText,
self.valid,
id(self))
def _getCssText(self):
"""Return serialized property cssText."""
return cssutils.ser.do_CSSFontFaceRule(self)
def _setCssText(self, cssText):
"""
:exceptions:
- :exc:`~xml.dom.SyntaxErr`:
Raised if the specified CSS string value has a syntax error and
is unparsable.
- :exc:`~xml.dom.InvalidModificationErr`:
Raised if the specified CSS string value represents a different
type of rule than the current one.
- :exc:`~xml.dom.HierarchyRequestErr`:
Raised if the rule cannot be inserted at this point in the
style sheet.
- :exc:`~xml.dom.NoModificationAllowedErr`:
Raised if the rule is readonly.
"""
super(CSSFontFaceRule, self)._setCssText(cssText)
tokenizer = self._tokenize2(cssText)
attoken = self._nexttoken(tokenizer, None)
if self._type(attoken) != self._prods.FONT_FACE_SYM:
self._log.error(u'CSSFontFaceRule: No CSSFontFaceRule found: %s' %
self._valuestr(cssText),
error=xml.dom.InvalidModificationErr)
else:
newStyle = CSSStyleDeclaration(parentRule=self)
ok = True
beforetokens, brace = self._tokensupto2(tokenizer,
blockstartonly=True,
separateEnd=True)
if self._tokenvalue(brace) != u'{':
ok = False
self._log.error(u'CSSFontFaceRule: No start { of style '
u'declaration found: %r'
% self._valuestr(cssText), brace)
# parse stuff before { which should be comments and S only
new = {'wellformed': True}
newseq = self._tempSeq()
beforewellformed, expected = self._parse(expected=':',
seq=newseq, tokenizer=self._tokenize2(beforetokens),
productions={})
ok = ok and beforewellformed and new['wellformed']
styletokens, braceorEOFtoken = self._tokensupto2(tokenizer,
blockendonly=True,
separateEnd=True)
val, type_ = self._tokenvalue(braceorEOFtoken),\
self._type(braceorEOFtoken)
if val != u'}' and type_ != 'EOF':
ok = False
self._log.error(u'CSSFontFaceRule: No "}" after style '
u'declaration found: %r'
% self._valuestr(cssText))
nonetoken = self._nexttoken(tokenizer)
if nonetoken:
ok = False
self._log.error(u'CSSFontFaceRule: Trailing content found.',
token=nonetoken)
if 'EOF' == type_:
# add again as style needs it
styletokens.append(braceorEOFtoken)
# SET, may raise:
newStyle.cssText = styletokens
if ok:
# contains probably comments only (upto ``{``)
self._setSeq(newseq)
self.style = newStyle
cssText = property(_getCssText, _setCssText,
doc=u"(DOM) The parsable textual representation of this "
u"rule.")
def _setStyle(self, style):
"""
:param style:
a CSSStyleDeclaration or string
"""
self._checkReadonly()
if isinstance(style, basestring):
self._style = CSSStyleDeclaration(cssText=style, parentRule=self)
else:
style._parentRule = self
self._style = style
style = property(lambda self: self._style, _setStyle,
doc=u"(DOM) The declaration-block of this rule set, "
u"a :class:`~cssutils.css.CSSStyleDeclaration`.")
type = property(lambda self: self.FONT_FACE_RULE,
doc=u"The type of this rule, as defined by a CSSRule "
u"type constant.")
def _getValid(self):
needed = ['font-family', 'src']
for p in self.style.getProperties(all=True):
if not p.valid:
return False
try:
needed.remove(p.name)
except ValueError:
pass
return not bool(needed)
valid = property(_getValid,
doc=u"CSSFontFace is valid if properties `font-family` "
u"and `src` are set and all properties are valid.")
# constant but needed:
wellformed = property(lambda self: True)

396
libs/cssutils/css/cssimportrule.py

@ -1,396 +0,0 @@
"""CSSImportRule implements DOM Level 2 CSS CSSImportRule plus the
``name`` property from http://www.w3.org/TR/css3-cascade/#cascading."""
__all__ = ['CSSImportRule']
__docformat__ = 'restructuredtext'
__version__ = '$Id$'
import cssrule
import cssutils
import os
import urlparse
import xml.dom
class CSSImportRule(cssrule.CSSRule):
"""
Represents an @import rule within a CSS style sheet. The @import rule
is used to import style rules from other style sheets.
Format::
import
: IMPORT_SYM S*
[STRING|URI] S* [ medium [ COMMA S* medium]* ]? S* STRING? S* ';' S*
;
"""
def __init__(self, href=None, mediaText=None, name=None,
parentRule=None, parentStyleSheet=None, readonly=False):
"""
If readonly allows setting of properties in constructor only
:param href:
location of the style sheet to be imported.
:param mediaText:
A list of media types for which this style sheet may be used
as a string
:param name:
Additional name of imported style sheet
"""
super(CSSImportRule, self).__init__(parentRule=parentRule,
parentStyleSheet=parentStyleSheet)
self._atkeyword = u'@import'
self._styleSheet = None
# string or uri used for reserialization
self.hreftype = None
# prepare seq
seq = self._tempSeq()
seq.append(None, 'href')
#seq.append(None, 'media')
seq.append(None, 'name')
self._setSeq(seq)
# 1. media
if mediaText:
self.media = mediaText
else:
# must be all for @import
self.media = cssutils.stylesheets.MediaList(mediaText=u'all')
# 2. name
self.name = name
# 3. href and styleSheet
self.href = href
self._readonly = readonly
def __repr__(self):
if self._usemedia:
mediaText = self.media.mediaText
else:
mediaText = None
return u"cssutils.css.%s(href=%r, mediaText=%r, name=%r)" % (
self.__class__.__name__,
self.href,
self.media.mediaText,
self.name)
def __str__(self):
if self._usemedia:
mediaText = self.media.mediaText
else:
mediaText = None
return u"<cssutils.css.%s object href=%r mediaText=%r name=%r at 0x%x>"\
% (self.__class__.__name__,
self.href,
mediaText,
self.name,
id(self))
_usemedia = property(lambda self: self.media.mediaText not in (u'', u'all'),
doc="if self.media is used (or simply empty)")
def _getCssText(self):
"""Return serialized property cssText."""
return cssutils.ser.do_CSSImportRule(self)
def _setCssText(self, cssText):
"""
:exceptions:
- :exc:`~xml.dom.HierarchyRequestErr`:
Raised if the rule cannot be inserted at this point in the
style sheet.
- :exc:`~xml.dom.InvalidModificationErr`:
Raised if the specified CSS string value represents a different
type of rule than the current one.
- :exc:`~xml.dom.NoModificationAllowedErr`:
Raised if the rule is readonly.
- :exc:`~xml.dom.SyntaxErr`:
Raised if the specified CSS string value has a syntax error and
is unparsable.
"""
super(CSSImportRule, self)._setCssText(cssText)
tokenizer = self._tokenize2(cssText)
attoken = self._nexttoken(tokenizer, None)
if self._type(attoken) != self._prods.IMPORT_SYM:
self._log.error(u'CSSImportRule: No CSSImportRule found: %s' %
self._valuestr(cssText),
error=xml.dom.InvalidModificationErr)
else:
# for closures: must be a mutable
new = {'keyword': self._tokenvalue(attoken),
'href': None,
'hreftype': None,
'media': None,
'name': None,
'wellformed': True
}
def __doname(seq, token):
# called by _string or _ident
new['name'] = self._stringtokenvalue(token)
seq.append(new['name'], 'name')
return ';'
def _string(expected, seq, token, tokenizer=None):
if 'href' == expected:
# href
new['href'] = self._stringtokenvalue(token)
new['hreftype'] = 'string'
seq.append(new['href'], 'href')
return 'media name ;'
elif 'name' in expected:
# name
return __doname(seq, token)
else:
new['wellformed'] = False
self._log.error(
u'CSSImportRule: Unexpected string.', token)
return expected
def _uri(expected, seq, token, tokenizer=None):
# href
if 'href' == expected:
uri = self._uritokenvalue(token)
new['hreftype'] = 'uri'
new['href'] = uri
seq.append(new['href'], 'href')
return 'media name ;'
else:
new['wellformed'] = False
self._log.error(
u'CSSImportRule: Unexpected URI.', token)
return expected
def _ident(expected, seq, token, tokenizer=None):
# medialist ending with ; which is checked upon too
if expected.startswith('media'):
mediatokens = self._tokensupto2(
tokenizer, importmediaqueryendonly=True)
mediatokens.insert(0, token) # push found token
last = mediatokens.pop() # retrieve ;
lastval, lasttyp = self._tokenvalue(last), self._type(last)
if lastval != u';' and lasttyp not in ('EOF',
self._prods.STRING):
new['wellformed'] = False
self._log.error(u'CSSImportRule: No ";" found: %s' %
self._valuestr(cssText), token=token)
newMedia = cssutils.stylesheets.MediaList(parentRule=self)
newMedia.mediaText = mediatokens
if newMedia.wellformed:
new['media'] = newMedia
seq.append(newMedia, 'media')
else:
new['wellformed'] = False
self._log.error(u'CSSImportRule: Invalid MediaList: %s' %
self._valuestr(cssText), token=token)
if lasttyp == self._prods.STRING:
# name
return __doname(seq, last)
else:
return 'EOF' # ';' is token "last"
else:
new['wellformed'] = False
self._log.error(u'CSSImportRule: Unexpected ident.', token)
return expected
def _char(expected, seq, token, tokenizer=None):
# final ;
val = self._tokenvalue(token)
if expected.endswith(';') and u';' == val:
return 'EOF'
else:
new['wellformed'] = False
self._log.error(
u'CSSImportRule: Unexpected char.', token)
return expected
# import : IMPORT_SYM S* [STRING|URI]
# S* [ medium [ ',' S* medium]* ]? ';' S*
# STRING? # see http://www.w3.org/TR/css3-cascade/#cascading
# ;
newseq = self._tempSeq()
wellformed, expected = self._parse(expected='href',
seq=newseq, tokenizer=tokenizer,
productions={'STRING': _string,
'URI': _uri,
'IDENT': _ident,
'CHAR': _char},
new=new)
# wellformed set by parse
ok = wellformed and new['wellformed']
# post conditions
if not new['href']:
ok = False
self._log.error(u'CSSImportRule: No href found: %s' %
self._valuestr(cssText))
if expected != 'EOF':
ok = False
self._log.error(u'CSSImportRule: No ";" found: %s' %
self._valuestr(cssText))
# set all
if ok:
self._setSeq(newseq)
self.atkeyword = new['keyword']
self.hreftype = new['hreftype']
self.name = new['name']
if new['media']:
self.media = new['media']
else:
# must be all for @import
self.media = cssutils.stylesheets.MediaList(mediaText=u'all')
# needs new self.media
self.href = new['href']
cssText = property(fget=_getCssText, fset=_setCssText,
doc="(DOM) The parsable textual representation of this rule.")
def _setHref(self, href):
# set new href
self._href = href
# update seq
for i, item in enumerate(self.seq):
val, type_ = item.value, item.type
if 'href' == type_:
self._seq[i] = (href, type_, item.line, item.col)
break
importedSheet = cssutils.css.CSSStyleSheet(media=self.media,
ownerRule=self,
title=self.name)
self.hrefFound = False
# set styleSheet
if href and self.parentStyleSheet:
# loading errors are all catched!
# relative href
parentHref = self.parentStyleSheet.href
if parentHref is None:
# use cwd instead
parentHref = cssutils.helper.path2url(os.getcwd()) + '/'
fullhref = urlparse.urljoin(parentHref, self.href)
# all possible exceptions are ignored
try:
usedEncoding, enctype, cssText = \
self.parentStyleSheet._resolveImport(fullhref)
if cssText is None:
# catched in next except below!
raise IOError('Cannot read Stylesheet.')
# contentEncoding with parentStyleSheet.overrideEncoding,
# HTTP or parent
encodingOverride, encoding = None, None
if enctype == 0:
encodingOverride = usedEncoding
elif 0 < enctype < 5:
encoding = usedEncoding
# inherit fetcher for @imports in styleSheet
importedSheet._href = fullhref
importedSheet._setFetcher(self.parentStyleSheet._fetcher)
importedSheet._setCssTextWithEncodingOverride(
cssText,
encodingOverride=encodingOverride,
encoding=encoding)
except (OSError, IOError, ValueError), e:
self._log.warn(u'CSSImportRule: While processing imported '
u'style sheet href=%s: %r'
% (self.href, e), neverraise=True)
else:
# used by resolveImports if to keep unprocessed href
self.hrefFound = True
self._styleSheet = importedSheet
_href = None # needs to be set
href = property(lambda self: self._href, _setHref,
doc=u"Location of the style sheet to be imported.")
def _setMedia(self, media):
"""
:param media:
a :class:`~cssutils.stylesheets.MediaList` or string
"""
self._checkReadonly()
if isinstance(media, basestring):
self._media = cssutils.stylesheets.MediaList(mediaText=media,
parentRule=self)
else:
media._parentRule = self
self._media = media
# update seq
ihref = 0
for i, item in enumerate(self.seq):
if item.type == 'href':
ihref = i
elif item.type == 'media':
self.seq[i] = (self._media, 'media', None, None)
break
else:
# if no media until now add after href
self.seq.insert(ihref+1,
self._media, 'media', None, None)
media = property(lambda self: self._media, _setMedia,
doc=u"(DOM) A list of media types for this rule "
u"of type :class:`~cssutils.stylesheets.MediaList`.")
def _setName(self, name=u''):
"""Raises xml.dom.SyntaxErr if name is not a string."""
if name is None or isinstance(name, basestring):
# "" or '' handled as None
if not name:
name = None
# save name
self._name = name
# update seq
for i, item in enumerate(self.seq):
val, typ = item.value, item.type
if 'name' == typ:
self._seq[i] = (name, typ, item.line, item.col)
break
# set title of imported sheet
if self.styleSheet:
self.styleSheet.title = name
else:
self._log.error(u'CSSImportRule: Not a valid name: %s' % name)
name = property(lambda self: self._name, _setName,
doc=u"An optional name for the imported sheet.")
styleSheet = property(lambda self: self._styleSheet,
doc=u"(readonly) The style sheet referred to by this "
u"rule.")
type = property(lambda self: self.IMPORT_RULE,
doc=u"The type of this rule, as defined by a CSSRule "
u"type constant.")
def _getWellformed(self):
"Depending on if media is used at all."
if self._usemedia:
return bool(self.href and self.media.wellformed)
else:
return bool(self.href)
wellformed = property(_getWellformed)

302
libs/cssutils/css/cssmediarule.py

@ -1,302 +0,0 @@
"""CSSMediaRule implements DOM Level 2 CSS CSSMediaRule."""
__all__ = ['CSSMediaRule']
__docformat__ = 'restructuredtext'
__version__ = '$Id$'
import cssrule
import cssutils
import xml.dom
class CSSMediaRule(cssrule.CSSRuleRules):
"""
Objects implementing the CSSMediaRule interface can be identified by the
MEDIA_RULE constant. On these objects the type attribute must return the
value of that constant.
Format::
: MEDIA_SYM S* medium [ COMMA S* medium ]*
STRING? # the name
LBRACE S* ruleset* '}' S*;
``cssRules``
All Rules in this media rule, a :class:`~cssutils.css.CSSRuleList`.
"""
def __init__(self, mediaText='all', name=None,
parentRule=None, parentStyleSheet=None, readonly=False):
"""constructor"""
super(CSSMediaRule, self).__init__(parentRule=parentRule,
parentStyleSheet=parentStyleSheet)
self._atkeyword = u'@media'
# 1. media
if mediaText:
self.media = mediaText
else:
self.media = cssutils.stylesheets.MediaList()
self.name = name
self._readonly = readonly
def __repr__(self):
return u"cssutils.css.%s(mediaText=%r)" % (
self.__class__.__name__,
self.media.mediaText)
def __str__(self):
return u"<cssutils.css.%s object mediaText=%r at 0x%x>" % (
self.__class__.__name__,
self.media.mediaText,
id(self))
def _getCssText(self):
"""Return serialized property cssText."""
return cssutils.ser.do_CSSMediaRule(self)
def _setCssText(self, cssText):
"""
:param cssText:
a parseable string or a tuple of (cssText, dict-of-namespaces)
:Exceptions:
- :exc:`~xml.dom.NamespaceErr`:
Raised if a specified selector uses an unknown namespace
prefix.
- :exc:`~xml.dom.SyntaxErr`:
Raised if the specified CSS string value has a syntax error and
is unparsable.
- :exc:`~xml.dom.InvalidModificationErr`:
Raised if the specified CSS string value represents a different
type of rule than the current one.
- :exc:`~xml.dom.HierarchyRequestErr`:
Raised if the rule cannot be inserted at this point in the
style sheet.
- :exc:`~xml.dom.NoModificationAllowedErr`:
Raised if the rule is readonly.
"""
# media "name"? { cssRules }
super(CSSMediaRule, self)._setCssText(cssText)
# might be (cssText, namespaces)
cssText, namespaces = self._splitNamespacesOff(cssText)
try:
# use parent style sheet ones if available
namespaces = self.parentStyleSheet.namespaces
except AttributeError:
pass
tokenizer = self._tokenize2(cssText)
attoken = self._nexttoken(tokenizer, None)
if self._type(attoken) != self._prods.MEDIA_SYM:
self._log.error(u'CSSMediaRule: No CSSMediaRule found: %s' %
self._valuestr(cssText),
error=xml.dom.InvalidModificationErr)
else:
# save if parse goes wrong
oldMedia = self._media
oldName = self._name
oldCssRules = self._cssRules
ok = True
# media
mediatokens, end = self._tokensupto2(tokenizer,
mediaqueryendonly=True,
separateEnd=True)
if u'{' == self._tokenvalue(end)\
or self._prods.STRING == self._type(end):
self.media = cssutils.stylesheets.MediaList(parentRule=self)
# TODO: remove special case
self.media.mediaText = mediatokens
ok = ok and self.media.wellformed
else:
ok = False
# name (optional)
name = None
nameseq = self._tempSeq()
if self._prods.STRING == self._type(end):
name = self._stringtokenvalue(end)
# TODO: for now comments are lost after name
nametokens, end = self._tokensupto2(tokenizer,
blockstartonly=True,
separateEnd=True)
wellformed, expected = self._parse(None,
nameseq,
nametokens,
{})
if not wellformed:
ok = False
self._log.error(u'CSSMediaRule: Syntax Error: %s' %
self._valuestr(cssText))
# check for {
if u'{' != self._tokenvalue(end):
self._log.error(u'CSSMediaRule: No "{" found: %s' %
self._valuestr(cssText))
return
# cssRules
cssrulestokens, braceOrEOF = self._tokensupto2(tokenizer,
mediaendonly=True,
separateEnd=True)
nonetoken = self._nexttoken(tokenizer, None)
if 'EOF' == self._type(braceOrEOF):
# HACK!!!
# TODO: Not complete, add EOF to rule and } to @media
cssrulestokens.append(braceOrEOF)
braceOrEOF = ('CHAR', '}', 0, 0)
self._log.debug(u'CSSMediaRule: Incomplete, adding "}".',
token=braceOrEOF, neverraise=True)
if u'}' != self._tokenvalue(braceOrEOF):
self._log.error(u'CSSMediaRule: No "}" found.',
token=braceOrEOF)
elif nonetoken:
self._log.error(u'CSSMediaRule: Trailing content found.',
token=nonetoken)
else:
# for closures: must be a mutable
new = {'wellformed': True }
def COMMENT(expected, seq, token, tokenizer=None):
self.insertRule(cssutils.css.CSSComment([token],
parentRule=self,
parentStyleSheet=self.parentStyleSheet))
return expected
def ruleset(expected, seq, token, tokenizer):
rule = cssutils.css.CSSStyleRule(parentRule=self,
parentStyleSheet=self.parentStyleSheet)
rule.cssText = self._tokensupto2(tokenizer, token)
if rule.wellformed:
self.insertRule(rule)
return expected
def atrule(expected, seq, token, tokenizer):
# TODO: get complete rule!
tokens = self._tokensupto2(tokenizer, token)
atval = self._tokenvalue(token)
if atval in ('@charset ', '@font-face', '@import',
'@namespace', '@page', '@media', '@variables'):
self._log.error(u'CSSMediaRule: This rule is not '
u'allowed in CSSMediaRule - ignored: '
u'%s.' % self._valuestr(tokens),
token = token,
error=xml.dom.HierarchyRequestErr)
else:
rule = cssutils.css.CSSUnknownRule(tokens,
parentRule=self,
parentStyleSheet=self.parentStyleSheet)
if rule.wellformed:
self.insertRule(rule)
return expected
# save for possible reset
oldCssRules = self.cssRules
self.cssRules = cssutils.css.CSSRuleList()
seq = [] # not used really
tokenizer = iter(cssrulestokens)
wellformed, expected = self._parse(braceOrEOF,
seq,
tokenizer, {
'COMMENT': COMMENT,
'CHARSET_SYM': atrule,
'FONT_FACE_SYM': atrule,
'IMPORT_SYM': atrule,
'NAMESPACE_SYM': atrule,
'PAGE_SYM': atrule,
'MEDIA_SYM': atrule,
'ATKEYWORD': atrule
},
default=ruleset,
new=new)
ok = ok and wellformed
if ok:
self.name = name
self._setSeq(nameseq)
else:
self._media = oldMedia
self._cssRules = oldCssRules
cssText = property(_getCssText, _setCssText,
doc=u"(DOM) The parsable textual representation of this "
u"rule.")
def _setName(self, name):
if isinstance(name, basestring) or name is None:
# "" or ''
if not name:
name = None
self._name = name
else:
self._log.error(u'CSSImportRule: Not a valid name: %s' % name)
name = property(lambda self: self._name, _setName,
doc=u"An optional name for this media rule.")
def _setMedia(self, media):
"""
:param media:
a :class:`~cssutils.stylesheets.MediaList` or string
"""
self._checkReadonly()
if isinstance(media, basestring):
self._media = cssutils.stylesheets.MediaList(mediaText=media,
parentRule=self)
else:
media._parentRule = self
self._media = media
# NOT IN @media seq at all?!
# # update seq
# for i, item in enumerate(self.seq):
# if item.type == 'media':
# self._seq[i] = (self._media, 'media', None, None)
# break
# else:
# # insert after @media if not in seq at all
# self.seq.insert(0,
# self._media, 'media', None, None)
media = property(lambda self: self._media, _setMedia,
doc=u"(DOM) A list of media types for this rule "
u"of type :class:`~cssutils.stylesheets.MediaList`.")
def insertRule(self, rule, index=None):
"""Implements base ``insertRule``."""
rule, index = self._prepareInsertRule(rule, index)
if rule is False or rule is True:
# done or error
return
# check hierarchy
if isinstance(rule, cssutils.css.CSSCharsetRule) or \
isinstance(rule, cssutils.css.CSSFontFaceRule) or \
isinstance(rule, cssutils.css.CSSImportRule) or \
isinstance(rule, cssutils.css.CSSNamespaceRule) or \
isinstance(rule, cssutils.css.CSSPageRule) or \
isinstance(rule, cssutils.css.MarginRule) or \
isinstance(rule, CSSMediaRule):
self._log.error(u'%s: This type of rule is not allowed here: %s'
% (self.__class__.__name__, rule.cssText),
error=xml.dom.HierarchyRequestErr)
return
return self._finishInsertRule(rule, index)
type = property(lambda self: self.MEDIA_RULE,
doc=u"The type of this rule, as defined by a CSSRule "
u"type constant.")
wellformed = property(lambda self: self.media.wellformed)

295
libs/cssutils/css/cssnamespacerule.py

@ -1,295 +0,0 @@
"""CSSNamespaceRule currently implements http://dev.w3.org/csswg/css3-namespace/
"""
__all__ = ['CSSNamespaceRule']
__docformat__ = 'restructuredtext'
__version__ = '$Id$'
import cssrule
import cssutils
import xml.dom
class CSSNamespaceRule(cssrule.CSSRule):
"""
Represents an @namespace rule within a CSS style sheet.
The @namespace at-rule declares a namespace prefix and associates
it with a given namespace (a string). This namespace prefix can then be
used in namespace-qualified names such as those described in the
Selectors Module [SELECT] or the Values and Units module [CSS3VAL].
Dealing with these rules directly is not needed anymore, easier is
the use of :attr:`cssutils.css.CSSStyleSheet.namespaces`.
Format::
namespace
: NAMESPACE_SYM S* [namespace_prefix S*]? [STRING|URI] S* ';' S*
;
namespace_prefix
: IDENT
;
"""
def __init__(self, namespaceURI=None, prefix=None, cssText=None,
parentRule=None, parentStyleSheet=None, readonly=False):
"""
:Parameters:
namespaceURI
The namespace URI (a simple string!) which is bound to the
given prefix. If no prefix is set
(``CSSNamespaceRule.prefix==''``) the namespace defined by
namespaceURI is set as the default namespace
prefix
The prefix used in the stylesheet for the given
``CSSNamespaceRule.uri``.
cssText
if no namespaceURI is given cssText must be given to set
a namespaceURI as this is readonly later on
parentStyleSheet
sheet where this rule belongs to
Do not use as positional but as keyword parameters only!
If readonly allows setting of properties in constructor only
format namespace::
namespace
: NAMESPACE_SYM S* [namespace_prefix S*]? [STRING|URI] S* ';' S*
;
namespace_prefix
: IDENT
;
"""
super(CSSNamespaceRule, self).__init__(parentRule=parentRule,
parentStyleSheet=parentStyleSheet)
self._atkeyword = u'@namespace'
self._prefix = u''
self._namespaceURI = None
if namespaceURI:
self.namespaceURI = namespaceURI
self.prefix = prefix
tempseq = self._tempSeq()
tempseq.append(self.prefix, 'prefix')
tempseq.append(self.namespaceURI, 'namespaceURI')
self._setSeq(tempseq)
elif cssText is not None:
self.cssText = cssText
if parentStyleSheet:
self._parentStyleSheet = parentStyleSheet
self._readonly = readonly
def __repr__(self):
return u"cssutils.css.%s(namespaceURI=%r, prefix=%r)" % (
self.__class__.__name__,
self.namespaceURI,
self.prefix)
def __str__(self):
return u"<cssutils.css.%s object namespaceURI=%r prefix=%r at 0x%x>" % (
self.__class__.__name__,
self.namespaceURI,
self.prefix,
id(self))
def _getCssText(self):
"""Return serialized property cssText"""
return cssutils.ser.do_CSSNamespaceRule(self)
def _setCssText(self, cssText):
"""
:param cssText: initial value for this rules cssText which is parsed
:exceptions:
- :exc:`~xml.dom.HierarchyRequestErr`:
Raised if the rule cannot be inserted at this point in the
style sheet.
- :exc:`~xml.dom.InvalidModificationErr`:
Raised if the specified CSS string value represents a different
type of rule than the current one.
- :exc:`~xml.dom.NoModificationAllowedErr`:
Raised if the rule is readonly.
- :exc:`~xml.dom.SyntaxErr`:
Raised if the specified CSS string value has a syntax error and
is unparsable.
"""
super(CSSNamespaceRule, self)._setCssText(cssText)
tokenizer = self._tokenize2(cssText)
attoken = self._nexttoken(tokenizer, None)
if self._type(attoken) != self._prods.NAMESPACE_SYM:
self._log.error(u'CSSNamespaceRule: No CSSNamespaceRule found: %s' %
self._valuestr(cssText),
error=xml.dom.InvalidModificationErr)
else:
# for closures: must be a mutable
new = {'keyword': self._tokenvalue(attoken),
'prefix': u'',
'uri': None,
'wellformed': True
}
def _ident(expected, seq, token, tokenizer=None):
# the namespace prefix, optional
if 'prefix or uri' == expected:
new['prefix'] = self._tokenvalue(token)
seq.append(new['prefix'], 'prefix')
return 'uri'
else:
new['wellformed'] = False
self._log.error(
u'CSSNamespaceRule: Unexpected ident.', token)
return expected
def _string(expected, seq, token, tokenizer=None):
# the namespace URI as a STRING
if expected.endswith('uri'):
new['uri'] = self._stringtokenvalue(token)
seq.append(new['uri'], 'namespaceURI')
return ';'
else:
new['wellformed'] = False
self._log.error(
u'CSSNamespaceRule: Unexpected string.', token)
return expected
def _uri(expected, seq, token, tokenizer=None):
# the namespace URI as URI which is DEPRECATED
if expected.endswith('uri'):
uri = self._uritokenvalue(token)
new['uri'] = uri
seq.append(new['uri'], 'namespaceURI')
return ';'
else:
new['wellformed'] = False
self._log.error(
u'CSSNamespaceRule: Unexpected URI.', token)
return expected
def _char(expected, seq, token, tokenizer=None):
# final ;
val = self._tokenvalue(token)
if ';' == expected and u';' == val:
return 'EOF'
else:
new['wellformed'] = False
self._log.error(
u'CSSNamespaceRule: Unexpected char.', token)
return expected
# "NAMESPACE_SYM S* [namespace_prefix S*]? [STRING|URI] S* ';' S*"
newseq = self._tempSeq()
wellformed, expected = self._parse(expected='prefix or uri',
seq=newseq, tokenizer=tokenizer,
productions={'IDENT': _ident,
'STRING': _string,
'URI': _uri,
'CHAR': _char},
new=new)
# wellformed set by parse
wellformed = wellformed and new['wellformed']
# post conditions
if new['uri'] is None:
wellformed = False
self._log.error(u'CSSNamespaceRule: No namespace URI found: %s'
% self._valuestr(cssText))
if expected != 'EOF':
wellformed = False
self._log.error(u'CSSNamespaceRule: No ";" found: %s' %
self._valuestr(cssText))
# set all
if wellformed:
self.atkeyword = new['keyword']
self._prefix = new['prefix']
self.namespaceURI = new['uri']
self._setSeq(newseq)
cssText = property(fget=_getCssText, fset=_setCssText,
doc=u"(DOM) The parsable textual representation of this "
u"rule.")
def _setNamespaceURI(self, namespaceURI):
"""
:param namespaceURI: the initial value for this rules namespaceURI
:exceptions:
- :exc:`~xml.dom.NoModificationAllowedErr`:
(CSSRule) Raised if this rule is readonly or a namespaceURI is
already set in this rule.
"""
self._checkReadonly()
if not self._namespaceURI:
# initial setting
self._namespaceURI = namespaceURI
tempseq = self._tempSeq()
tempseq.append(namespaceURI, 'namespaceURI')
self._setSeq(tempseq) # makes seq readonly!
elif self._namespaceURI != namespaceURI:
self._log.error(u'CSSNamespaceRule: namespaceURI is readonly.',
error=xml.dom.NoModificationAllowedErr)
namespaceURI = property(lambda self: self._namespaceURI, _setNamespaceURI,
doc="URI (handled as simple string) of the defined namespace.")
def _replaceNamespaceURI(self, namespaceURI):
"""Used during parse of new sheet only!
:param namespaceURI: the new value for this rules namespaceURI
"""
self._namespaceURI = namespaceURI
for i, x in enumerate(self._seq):
if 'namespaceURI' == x.type:
self._seq._readonly = False
self._seq.replace(i, namespaceURI, 'namespaceURI')
self._seq._readonly = True
break
def _setPrefix(self, prefix=None):
"""
:param prefix: the new prefix
:exceptions:
- :exc:`~xml.dom.SyntaxErr`:
Raised if the specified CSS string value has a syntax error and
is unparsable.
- :exc:`~xml.dom.NoModificationAllowedErr`:
Raised if this rule is readonly.
"""
self._checkReadonly()
if not prefix:
prefix = u''
else:
tokenizer = self._tokenize2(prefix)
prefixtoken = self._nexttoken(tokenizer, None)
if not prefixtoken or self._type(prefixtoken) != self._prods.IDENT:
self._log.error(u'CSSNamespaceRule: No valid prefix "%s".' %
self._valuestr(prefix),
error=xml.dom.SyntaxErr)
return
else:
prefix = self._tokenvalue(prefixtoken)
# update seq
for i, x in enumerate(self._seq):
if x == self._prefix:
self._seq[i] = (prefix, 'prefix', None, None)
break
else:
# put prefix at the beginning!
self._seq[0] = (prefix, 'prefix', None, None)
# set new prefix
self._prefix = prefix
prefix = property(lambda self: self._prefix, _setPrefix,
doc=u"Prefix used for the defined namespace.")
type = property(lambda self: self.NAMESPACE_RULE,
doc=u"The type of this rule, as defined by a CSSRule "
u"type constant.")
wellformed = property(lambda self: self.namespaceURI is not None)

436
libs/cssutils/css/csspagerule.py

@ -1,436 +0,0 @@
"""CSSPageRule implements DOM Level 2 CSS CSSPageRule."""
__all__ = ['CSSPageRule']
__docformat__ = 'restructuredtext'
__version__ = '$Id$'
from itertools import chain
from cssstyledeclaration import CSSStyleDeclaration
from marginrule import MarginRule
import cssrule
import cssutils
import xml.dom
class CSSPageRule(cssrule.CSSRuleRules):
"""
The CSSPageRule interface represents a @page rule within a CSS style
sheet. The @page rule is used to specify the dimensions, orientation,
margins, etc. of a page box for paged media.
Format::
page :
PAGE_SYM S* IDENT? pseudo_page? S*
'{' S* [ declaration | margin ]? [ ';' S* [ declaration | margin ]? ]* '}' S*
;
pseudo_page :
':' [ "left" | "right" | "first" ]
;
margin :
margin_sym S* '{' declaration [ ';' S* declaration? ]* '}' S*
;
margin_sym :
TOPLEFTCORNER_SYM |
TOPLEFT_SYM |
TOPCENTER_SYM |
TOPRIGHT_SYM |
TOPRIGHTCORNER_SYM |
BOTTOMLEFTCORNER_SYM |
BOTTOMLEFT_SYM |
BOTTOMCENTER_SYM |
BOTTOMRIGHT_SYM |
BOTTOMRIGHTCORNER_SYM |
LEFTTOP_SYM |
LEFTMIDDLE_SYM |
LEFTBOTTOM_SYM |
RIGHTTOP_SYM |
RIGHTMIDDLE_SYM |
RIGHTBOTTOM_SYM
;
`cssRules` contains a list of `MarginRule` objects.
"""
def __init__(self, selectorText=None, style=None, parentRule=None,
parentStyleSheet=None, readonly=False):
"""
If readonly allows setting of properties in constructor only.
:param selectorText:
type string
:param style:
CSSStyleDeclaration for this CSSStyleRule
"""
super(CSSPageRule, self).__init__(parentRule=parentRule,
parentStyleSheet=parentStyleSheet)
self._atkeyword = u'@page'
self._specificity = (0, 0, 0)
tempseq = self._tempSeq()
if selectorText:
self.selectorText = selectorText
tempseq.append(self.selectorText, 'selectorText')
else:
self._selectorText = self._tempSeq()
if style:
self.style = style
else:
self.style = CSSStyleDeclaration()
tempseq.append(self.style, 'style')
self._setSeq(tempseq)
self._readonly = readonly
def __repr__(self):
return u"cssutils.css.%s(selectorText=%r, style=%r)" % (
self.__class__.__name__,
self.selectorText,
self.style.cssText)
def __str__(self):
return (u"<cssutils.css.%s object selectorText=%r specificity=%r "+
u"style=%r cssRules=%r at 0x%x>") % (
self.__class__.__name__,
self.selectorText,
self.specificity,
self.style.cssText,
len(self.cssRules),
id(self))
def __contains__(self, margin):
"""Check if margin is set in the rule."""
return margin in self.keys()
def keys(self):
"Return list of all set margins (MarginRule)."
return list(r.margin for r in self.cssRules)
def __getitem__(self, margin):
"""Retrieve the style (of MarginRule)
for `margin` (which must be normalized).
"""
for r in self.cssRules:
if r.margin == margin:
return r.style
def __setitem__(self, margin, style):
"""Set the style (of MarginRule)
for `margin` (which must be normalized).
"""
for i, r in enumerate(self.cssRules):
if r.margin == margin:
r.style = style
return i
else:
return self.add(MarginRule(margin, style))
def __delitem__(self, margin):
"""Delete the style (the MarginRule)
for `margin` (which must be normalized).
"""
for r in self.cssRules:
if r.margin == margin:
self.deleteRule(r)
def __parseSelectorText(self, selectorText):
"""
Parse `selectorText` which may also be a list of tokens
and returns (selectorText, seq).
see _setSelectorText for details
"""
# for closures: must be a mutable
new = {'wellformed': True, 'last-S': False,
'name': 0, 'first': 0, 'lr': 0}
specificity = (0, 0, 0)
def _char(expected, seq, token, tokenizer=None):
# pseudo_page, :left, :right or :first
val = self._tokenvalue(token)
if not new['last-S'] and expected in ['page', ': or EOF']\
and u':' == val:
try:
identtoken = tokenizer.next()
except StopIteration:
self._log.error(
u'CSSPageRule selectorText: No IDENT found.', token)
else:
ival, ityp = self._tokenvalue(identtoken),\
self._type(identtoken)
if self._prods.IDENT != ityp:
self._log.error(u'CSSPageRule selectorText: Expected '
u'IDENT but found: %r' % ival, token)
else:
if not ival in (u'first', u'left', u'right'):
self._log.warn(u'CSSPageRule: Unknown @page '
u'selector: %r'
% (u':'+ival,), neverraise=True)
if ival == u'first':
new['first'] = 1
else:
new['lr'] = 1
seq.append(val + ival, 'pseudo')
return 'EOF'
return expected
else:
new['wellformed'] = False
self._log.error(u'CSSPageRule selectorText: Unexpected CHAR: %r'
% val, token)
return expected
def S(expected, seq, token, tokenizer=None):
"Does not raise if EOF is found."
if expected == ': or EOF':
# pseudo must directly follow IDENT if given
new['last-S'] = True
return expected
def IDENT(expected, seq, token, tokenizer=None):
""
val = self._tokenvalue(token)
if 'page' == expected:
if self._normalize(val) == u'auto':
self._log.error(u'CSSPageRule selectorText: Invalid pagename.',
token)
else:
new['name'] = 1
seq.append(val, 'IDENT')
return ': or EOF'
else:
new['wellformed'] = False
self._log.error(u'CSSPageRule selectorText: Unexpected IDENT: '
u'%r' % val, token)
return expected
def COMMENT(expected, seq, token, tokenizer=None):
"Does not raise if EOF is found."
seq.append(cssutils.css.CSSComment([token]), 'COMMENT')
return expected
newseq = self._tempSeq()
wellformed, expected = self._parse(expected='page',
seq=newseq, tokenizer=self._tokenize2(selectorText),
productions={'CHAR': _char,
'IDENT': IDENT,
'COMMENT': COMMENT,
'S': S},
new=new)
wellformed = wellformed and new['wellformed']
# post conditions
if expected == 'ident':
self._log.error(
u'CSSPageRule selectorText: No valid selector: %r' %
self._valuestr(selectorText))
return wellformed, newseq, (new['name'], new['first'], new['lr'])
def __parseMarginAndStyle(self, tokens):
"tokens is a list, no generator (yet)"
g = iter(tokens)
styletokens = []
# new rules until parse done
cssRules = []
for token in g:
if token[0] == 'ATKEYWORD' and \
self._normalize(token[1]) in MarginRule.margins:
# MarginRule
m = MarginRule(parentRule=self,
parentStyleSheet=self.parentStyleSheet)
m.cssText = chain([token], g)
# merge if margin set more than once
for r in cssRules:
if r.margin == m.margin:
for p in m.style:
r.style.setProperty(p, replace=False)
break
else:
cssRules.append(m)
continue
# TODO: Properties?
styletokens.append(token)
return cssRules, styletokens
def _getCssText(self):
"""Return serialized property cssText."""
return cssutils.ser.do_CSSPageRule(self)
def _setCssText(self, cssText):
"""
:exceptions:
- :exc:`~xml.dom.SyntaxErr`:
Raised if the specified CSS string value has a syntax error and
is unparsable.
- :exc:`~xml.dom.InvalidModificationErr`:
Raised if the specified CSS string value represents a different
type of rule than the current one.
- :exc:`~xml.dom.HierarchyRequestErr`:
Raised if the rule cannot be inserted at this point in the
style sheet.
- :exc:`~xml.dom.NoModificationAllowedErr`:
Raised if the rule is readonly.
"""
super(CSSPageRule, self)._setCssText(cssText)
tokenizer = self._tokenize2(cssText)
if self._type(self._nexttoken(tokenizer)) != self._prods.PAGE_SYM:
self._log.error(u'CSSPageRule: No CSSPageRule found: %s' %
self._valuestr(cssText),
error=xml.dom.InvalidModificationErr)
else:
newStyle = CSSStyleDeclaration(parentRule=self)
ok = True
selectortokens, startbrace = self._tokensupto2(tokenizer,
blockstartonly=True,
separateEnd=True)
styletokens, braceorEOFtoken = self._tokensupto2(tokenizer,
blockendonly=True,
separateEnd=True)
nonetoken = self._nexttoken(tokenizer)
if self._tokenvalue(startbrace) != u'{':
ok = False
self._log.error(u'CSSPageRule: No start { of style declaration '
u'found: %r' %
self._valuestr(cssText), startbrace)
elif nonetoken:
ok = False
self._log.error(u'CSSPageRule: Trailing content found.',
token=nonetoken)
selok, newselseq, specificity = self.__parseSelectorText(selectortokens)
ok = ok and selok
val, type_ = self._tokenvalue(braceorEOFtoken),\
self._type(braceorEOFtoken)
if val != u'}' and type_ != 'EOF':
ok = False
self._log.error(
u'CSSPageRule: No "}" after style declaration found: %r' %
self._valuestr(cssText))
else:
if 'EOF' == type_:
# add again as style needs it
styletokens.append(braceorEOFtoken)
# filter pagemargin rules out first
cssRules, styletokens = self.__parseMarginAndStyle(styletokens)
# SET, may raise:
newStyle.cssText = styletokens
if ok:
self._selectorText = newselseq
self._specificity = specificity
self.style = newStyle
self.cssRules = cssutils.css.CSSRuleList()
for r in cssRules:
self.cssRules.append(r)
cssText = property(_getCssText, _setCssText,
doc=u"(DOM) The parsable textual representation of this rule.")
def _getSelectorText(self):
"""Wrapper for cssutils Selector object."""
return cssutils.ser.do_CSSPageRuleSelector(self._selectorText)
def _setSelectorText(self, selectorText):
"""Wrapper for cssutils Selector object.
:param selectorText:
DOM String, in CSS 2.1 one of
- :first
- :left
- :right
- empty
:exceptions:
- :exc:`~xml.dom.SyntaxErr`:
Raised if the specified CSS string value has a syntax error
and is unparsable.
- :exc:`~xml.dom.NoModificationAllowedErr`:
Raised if this rule is readonly.
"""
self._checkReadonly()
# may raise SYNTAX_ERR
wellformed, newseq, specificity = self.__parseSelectorText(selectorText)
if wellformed:
self._selectorText = newseq
self._specificity = specificity
selectorText = property(_getSelectorText, _setSelectorText,
doc=u"(DOM) The parsable textual representation of "
u"the page selector for the rule.")
def _setStyle(self, style):
"""
:param style:
a CSSStyleDeclaration or string
"""
self._checkReadonly()
if isinstance(style, basestring):
self._style = CSSStyleDeclaration(cssText=style, parentRule=self)
else:
style._parentRule = self
self._style = style
style = property(lambda self: self._style, _setStyle,
doc=u"(DOM) The declaration-block of this rule set, "
u"a :class:`~cssutils.css.CSSStyleDeclaration`.")
def insertRule(self, rule, index=None):
"""Implements base ``insertRule``."""
rule, index = self._prepareInsertRule(rule, index)
if rule is False or rule is True:
# done or error
return
# check hierarchy
if isinstance(rule, cssutils.css.CSSCharsetRule) or \
isinstance(rule, cssutils.css.CSSFontFaceRule) or \
isinstance(rule, cssutils.css.CSSImportRule) or \
isinstance(rule, cssutils.css.CSSNamespaceRule) or \
isinstance(rule, CSSPageRule) or \
isinstance(rule, cssutils.css.CSSMediaRule):
self._log.error(u'%s: This type of rule is not allowed here: %s'
% (self.__class__.__name__, rule.cssText),
error=xml.dom.HierarchyRequestErr)
return
return self._finishInsertRule(rule, index)
specificity = property(lambda self: self._specificity,
doc=u"""Specificity of this page rule (READONLY).
Tuple of (f, g, h) where:
- if the page selector has a named page, f=1; else f=0
- if the page selector has a ':first' pseudo-class, g=1; else g=0
- if the page selector has a ':left' or ':right' pseudo-class, h=1; else h=0
""")
type = property(lambda self: self.PAGE_RULE,
doc=u"The type of this rule, as defined by a CSSRule "
u"type constant.")
# constant but needed:
wellformed = property(lambda self: True)

122
libs/cssutils/css/cssproperties.py

@ -1,122 +0,0 @@
"""CSS2Properties (partly!) implements DOM Level 2 CSS CSS2Properties used
by CSSStyleDeclaration
TODO: CSS2Properties
If an implementation does implement this interface, it is expected to
understand the specific syntax of the shorthand properties, and apply
their semantics; when the margin property is set, for example, the
marginTop, marginRight, marginBottom and marginLeft properties are
actually being set by the underlying implementation.
When dealing with CSS "shorthand" properties, the shorthand properties
should be decomposed into their component longhand properties as
appropriate, and when querying for their value, the form returned
should be the shortest form exactly equivalent to the declarations made
in the ruleset. However, if there is no shorthand declaration that
could be added to the ruleset without changing in any way the rules
already declared in the ruleset (i.e., by adding longhand rules that
were previously not declared in the ruleset), then the empty string
should be returned for the shorthand property.
For example, querying for the font property should not return
"normal normal normal 14pt/normal Arial, sans-serif", when
"14pt Arial, sans-serif" suffices. (The normals are initial values, and
are implied by use of the longhand property.)
If the values for all the longhand properties that compose a particular
string are the initial values, then a string consisting of all the
initial values should be returned (e.g. a border-width value of
"medium" should be returned as such, not as "").
For some shorthand properties that take missing values from other
sides, such as the margin, padding, and border-[width|style|color]
properties, the minimum number of sides possible should be used; i.e.,
"0px 10px" will be returned instead of "0px 10px 0px 10px".
If the value of a shorthand property can not be decomposed into its
component longhand properties, as is the case for the font property
with a value of "menu", querying for the values of the component
longhand properties should return the empty string.
TODO: CSS2Properties DOMImplementation
The interface found within this section are not mandatory. A DOM
application can use the hasFeature method of the DOMImplementation
interface to determine whether it is supported or not. The feature
string for this extended interface listed in this section is "CSS2"
and the version is "2.0".
"""
__all__ = ['CSS2Properties']
__docformat__ = 'restructuredtext'
__version__ = '$Id$'
import cssutils.profiles
import re
class CSS2Properties(object):
"""The CSS2Properties interface represents a convenience mechanism
for retrieving and setting properties within a CSSStyleDeclaration.
The attributes of this interface correspond to all the properties
specified in CSS2. Getting an attribute of this interface is
equivalent to calling the getPropertyValue method of the
CSSStyleDeclaration interface. Setting an attribute of this
interface is equivalent to calling the setProperty method of the
CSSStyleDeclaration interface.
cssutils actually also allows usage of ``del`` to remove a CSS property
from a CSSStyleDeclaration.
This is an abstract class, the following functions need to be present
in inheriting class:
- ``_getP``
- ``_setP``
- ``_delP``
"""
# actual properties are set after the class definition!
def _getP(self, CSSname): pass
def _setP(self, CSSname, value): pass
def _delP(self, CSSname): pass
_reCSStoDOMname = re.compile('-[a-z]', re.I)
def _toDOMname(CSSname):
"""Returns DOMname for given CSSname e.g. for CSSname 'font-style' returns
'fontStyle'.
"""
def _doCSStoDOMname2(m): return m.group(0)[1].capitalize()
return _reCSStoDOMname.sub(_doCSStoDOMname2, CSSname)
_reDOMtoCSSname = re.compile('([A-Z])[a-z]+')
def _toCSSname(DOMname):
"""Return CSSname for given DOMname e.g. for DOMname 'fontStyle' returns
'font-style'.
"""
def _doDOMtoCSSname2(m): return '-' + m.group(0).lower()
return _reDOMtoCSSname.sub(_doDOMtoCSSname2, DOMname)
# add list of DOMname properties to CSS2Properties
# used for CSSStyleDeclaration to check if allowed properties
# but somehow doubled, any better way?
CSS2Properties._properties = []
for group in cssutils.profiles.properties:
for name in cssutils.profiles.properties[group]:
CSS2Properties._properties.append(_toDOMname(name))
# add CSS2Properties to CSSStyleDeclaration:
def __named_property_def(DOMname):
"""
Closure to keep name known in each properties accessor function
DOMname is converted to CSSname here, so actual calls use CSSname.
"""
CSSname = _toCSSname(DOMname)
def _get(self): return self._getP(CSSname)
def _set(self, value): self._setP(CSSname, value)
def _del(self): self._delP(CSSname)
return _get, _set, _del
# add all CSS2Properties to CSSStyleDeclaration
for DOMname in CSS2Properties._properties:
setattr(CSS2Properties, DOMname,
property(*__named_property_def(DOMname)))

304
libs/cssutils/css/cssrule.py

@ -1,304 +0,0 @@
"""CSSRule implements DOM Level 2 CSS CSSRule."""
__all__ = ['CSSRule']
__docformat__ = 'restructuredtext'
__version__ = '$Id$'
import cssutils
import xml.dom
class CSSRule(cssutils.util.Base2):
"""Abstract base interface for any type of CSS statement. This includes
both rule sets and at-rules. An implementation is expected to preserve
all rules specified in a CSS style sheet, even if the rule is not
recognized by the parser. Unrecognized rules are represented using the
:class:`CSSUnknownRule` interface.
"""
"""
CSSRule type constants.
An integer indicating which type of rule this is.
"""
UNKNOWN_RULE = 0
":class:`cssutils.css.CSSUnknownRule` (not used in CSSOM anymore)"
STYLE_RULE = 1
":class:`cssutils.css.CSSStyleRule`"
CHARSET_RULE = 2
":class:`cssutils.css.CSSCharsetRule` (not used in CSSOM anymore)"
IMPORT_RULE = 3
":class:`cssutils.css.CSSImportRule`"
MEDIA_RULE = 4
":class:`cssutils.css.CSSMediaRule`"
FONT_FACE_RULE = 5
":class:`cssutils.css.CSSFontFaceRule`"
PAGE_RULE = 6
":class:`cssutils.css.CSSPageRule`"
NAMESPACE_RULE = 10
""":class:`cssutils.css.CSSNamespaceRule`,
Value has changed in 0.9.7a3 due to a change in the CSSOM spec."""
COMMENT = 1001 # was -1, cssutils only
""":class:`cssutils.css.CSSComment` - not in the offical spec,
Value has changed in 0.9.7a3"""
VARIABLES_RULE = 1008
""":class:`cssutils.css.CSSVariablesRule` - experimental rule
not in the offical spec"""
MARGIN_RULE = 1006
""":class:`cssutils.css.MarginRule` - experimental rule
not in the offical spec"""
_typestrings = {UNKNOWN_RULE: u'UNKNOWN_RULE',
STYLE_RULE: u'STYLE_RULE',
CHARSET_RULE: u'CHARSET_RULE',
IMPORT_RULE: u'IMPORT_RULE',
MEDIA_RULE: u'MEDIA_RULE',
FONT_FACE_RULE: u'FONT_FACE_RULE',
PAGE_RULE: u'PAGE_RULE',
NAMESPACE_RULE: u'NAMESPACE_RULE',
COMMENT: u'COMMENT',
VARIABLES_RULE: u'VARIABLES_RULE',
MARGIN_RULE: u'MARGIN_RULE'
}
def __init__(self, parentRule=None, parentStyleSheet=None, readonly=False):
"""Set common attributes for all rules."""
super(CSSRule, self).__init__()
self._parent = parentRule
self._parentRule = parentRule
self._parentStyleSheet = parentStyleSheet
self._setSeq(self._tempSeq())
#self._atkeyword = None
# must be set after initialization of #inheriting rule is done
self._readonly = False
def _setAtkeyword(self, keyword):
"""Check if new keyword fits the rule it is used for."""
atkeyword = self._normalize(keyword)
if not self.atkeyword or (self.atkeyword == atkeyword):
self._atkeyword = atkeyword
self._keyword = keyword
else:
self._log.error(u'%s: Invalid atkeyword for this rule: %r' %
(self.atkeyword, keyword),
error=xml.dom.InvalidModificationErr)
atkeyword = property(lambda self: self._atkeyword, _setAtkeyword,
doc=u"Normalized keyword of an @rule (e.g. ``@import``).")
def _setCssText(self, cssText):
"""
:param cssText:
A parsable DOMString.
:exceptions:
- :exc:`~xml.dom.SyntaxErr`:
Raised if the specified CSS string value has a syntax error and
is unparsable.
- :exc:`~xml.dom.InvalidModificationErr`:
Raised if the specified CSS string value represents a different
type of rule than the current one.
- :exc:`~xml.dom.HierarchyRequestErr`:
Raised if the rule cannot be inserted at this point in the
style sheet.
- :exc:`~xml.dom.NoModificationAllowedErr`:
Raised if the rule is readonly.
"""
self._checkReadonly()
cssText = property(lambda self: u'', _setCssText,
doc=u"(DOM) The parsable textual representation of the "
u"rule. This reflects the current state of the rule "
u"and not its initial value.")
parent = property(lambda self: self._parent,
doc=u"The Parent Node of this CSSRule or None.")
parentRule = property(lambda self: self._parentRule,
doc=u"If this rule is contained inside another rule "
u"(e.g. a style rule inside an @media block), this "
u"is the containing rule. If this rule is not nested "
u"inside any other rules, this returns None.")
def _getParentStyleSheet(self):
# rules contained in other rules (@media) use that rules parent
if (self.parentRule):
return self.parentRule._parentStyleSheet
else:
return self._parentStyleSheet
parentStyleSheet = property(_getParentStyleSheet,
doc=u"The style sheet that contains this rule.")
type = property(lambda self: self.UNKNOWN_RULE,
doc=u"The type of this rule, as defined by a CSSRule "
u"type constant.")
typeString = property(lambda self: CSSRule._typestrings[self.type],
doc=u"Descriptive name of this rule's type.")
wellformed = property(lambda self: False,
doc=u"If the rule is wellformed.")
class CSSRuleRules(CSSRule):
"""Abstract base interface for rules that contain other rules
like @media or @page. Methods may be overwritten if a rule
has specific stuff to do like checking the order of insertion like
@media does.
"""
def __init__(self, parentRule=None, parentStyleSheet=None):
super(CSSRuleRules, self).__init__(parentRule=parentRule,
parentStyleSheet=parentStyleSheet)
self.cssRules = cssutils.css.CSSRuleList()
def __iter__(self):
"""Generator iterating over these rule's cssRules."""
for rule in self._cssRules:
yield rule
def _setCssRules(self, cssRules):
"Set new cssRules and update contained rules refs."
cssRules.append = self.insertRule
cssRules.extend = self.insertRule
cssRules.__delitem__ == self.deleteRule
for rule in cssRules:
rule._parentRule = self
rule._parentStyleSheet = None
self._cssRules = cssRules
cssRules = property(lambda self: self._cssRules, _setCssRules,
"All Rules in this style sheet, a "
":class:`~cssutils.css.CSSRuleList`.")
def deleteRule(self, index):
"""
Delete the rule at `index` from rules ``cssRules``.
:param index:
The `index` of the rule to be removed from the rules cssRules
list. For an `index` < 0 **no** :exc:`~xml.dom.IndexSizeErr` is
raised but rules for normal Python lists are used. E.g.
``deleteRule(-1)`` removes the last rule in cssRules.
`index` may also be a CSSRule object which will then be removed.
:Exceptions:
- :exc:`~xml.dom.IndexSizeErr`:
Raised if the specified index does not correspond to a rule in
the media rule list.
- :exc:`~xml.dom.NoModificationAllowedErr`:
Raised if this media rule is readonly.
"""
self._checkReadonly()
if isinstance(index, CSSRule):
for i, r in enumerate(self.cssRules):
if index == r:
index = i
break
else:
raise xml.dom.IndexSizeErr(u"%s: Not a rule in "
u"this rule'a cssRules list: %s"
% (self.__class__.__name__, index))
try:
# detach
self._cssRules[index]._parentRule = None
del self._cssRules[index]
except IndexError:
raise xml.dom.IndexSizeErr(u'%s: %s is not a valid index '
u'in the rulelist of length %i'
% (self.__class__.__name__,
index, self._cssRules.length))
def _prepareInsertRule(self, rule, index=None):
"return checked `index` and optional parsed `rule`"
self._checkReadonly()
# check index
if index is None:
index = len(self._cssRules)
elif index < 0 or index > self._cssRules.length:
raise xml.dom.IndexSizeErr(u'%s: Invalid index %s for '
u'CSSRuleList with a length of %s.'
% (self.__class__.__name__,
index, self._cssRules.length))
# check and optionally parse rule
if isinstance(rule, basestring):
tempsheet = cssutils.css.CSSStyleSheet()
tempsheet.cssText = rule
if len(tempsheet.cssRules) != 1 or (tempsheet.cssRules and
not isinstance(tempsheet.cssRules[0], cssutils.css.CSSRule)):
self._log.error(u'%s: Invalid Rule: %s' % (self.__class__.__name__,
rule))
return False, False
rule = tempsheet.cssRules[0]
elif isinstance(rule, cssutils.css.CSSRuleList):
# insert all rules
for i, r in enumerate(rule):
self.insertRule(r, index + i)
return True, True
elif not isinstance(rule, cssutils.css.CSSRule):
self._log.error(u'%s: Not a CSSRule: %s' % (rule,
self.__class__.__name__))
return False, False
return rule, index
def _finishInsertRule(self, rule, index):
"add `rule` at `index`"
rule._parentRule = self
rule._parentStyleSheet = None
self._cssRules.insert(index, rule)
return index
def add(self, rule):
"""Add `rule` to page rule. Same as ``insertRule(rule)``."""
return self.insertRule(rule)
def insertRule(self, rule, index=None):
"""
Insert `rule` into the rules ``cssRules``.
:param rule:
the parsable text representing the `rule` to be inserted. For rule
sets this contains both the selector and the style declaration.
For at-rules, this specifies both the at-identifier and the rule
content.
cssutils also allows rule to be a valid
:class:`~cssutils.css.CSSRule` object.
:param index:
before the `index` the specified `rule` will be inserted.
If the specified `index` is equal to the length of the rules
rule collection, the rule will be added to the end of the rule.
If index is not given or None rule will be appended to rule
list.
:returns:
the index of the newly inserted rule.
:exceptions:
- :exc:`~xml.dom.HierarchyRequestErr`:
Raised if the `rule` cannot be inserted at the specified `index`,
e.g., if an @import rule is inserted after a standard rule set
or other at-rule.
- :exc:`~xml.dom.IndexSizeErr`:
Raised if the specified `index` is not a valid insertion point.
- :exc:`~xml.dom.NoModificationAllowedErr`:
Raised if this rule is readonly.
- :exc:`~xml.dom.SyntaxErr`:
Raised if the specified `rule` has a syntax error and is
unparsable.
"""
return self._prepareInsertRule(rule, index)

53
libs/cssutils/css/cssrulelist.py

@ -1,53 +0,0 @@
"""CSSRuleList implements DOM Level 2 CSS CSSRuleList.
Partly also http://dev.w3.org/csswg/cssom/#the-cssrulelist."""
__all__ = ['CSSRuleList']
__docformat__ = 'restructuredtext'
__version__ = '$Id$'
class CSSRuleList(list):
"""The CSSRuleList object represents an (ordered) list of statements.
The items in the CSSRuleList are accessible via an integral index,
starting from 0.
Subclasses a standard Python list so theoretically all standard list
methods are available. Setting methods like ``__init__``, ``append``,
``extend`` or ``__setslice__`` are added later on instances of this
class if so desired.
E.g. CSSStyleSheet adds ``append`` which is not available in a simple
instance of this class!
"""
def __init__(self, *ignored):
"Nothing is set as this must also be defined later."
pass
def __notimplemented(self, *ignored):
"Implemented in class using a CSSRuleList only."
raise NotImplementedError(
'Must be implemented by class using an instance of this class.')
append = extend = __setitem__ = __setslice__ = __notimplemented
def item(self, index):
"""(DOM) Retrieve a CSS rule by ordinal `index`. The order in this
collection represents the order of the rules in the CSS style
sheet. If index is greater than or equal to the number of rules in
the list, this returns None.
Returns CSSRule, the style rule at the index position in the
CSSRuleList, or None if that is not a valid index.
"""
try:
return self[index]
except IndexError:
return None
length = property(lambda self: len(self),
doc=u"(DOM) The number of CSSRules in the list.")
def rulesOfType(self, type):
"""Yield the rules which have the given `type` only, one of the
constants defined in :class:`cssutils.css.CSSRule`."""
for r in self:
if r.type == type:
yield r

697
libs/cssutils/css/cssstyledeclaration.py

@ -1,697 +0,0 @@
"""CSSStyleDeclaration implements DOM Level 2 CSS CSSStyleDeclaration and
extends CSS2Properties
see
http://www.w3.org/TR/1998/REC-CSS2-19980512/syndata.html#parsing-errors
Unknown properties
------------------
User agents must ignore a declaration with an unknown property.
For example, if the style sheet is::
H1 { color: red; rotation: 70minutes }
the user agent will treat this as if the style sheet had been::
H1 { color: red }
Cssutils gives a message about any unknown properties but
keeps any property (if syntactically correct).
Illegal values
--------------
User agents must ignore a declaration with an illegal value. For example::
IMG { float: left } /* correct CSS2 */
IMG { float: left here } /* "here" is not a value of 'float' */
IMG { background: "red" } /* keywords cannot be quoted in CSS2 */
IMG { border-width: 3 } /* a unit must be specified for length values */
A CSS2 parser would honor the first rule and ignore the rest, as if the
style sheet had been::
IMG { float: left }
IMG { }
IMG { }
IMG { }
Cssutils again will issue a message (WARNING in this case) about invalid
CSS2 property values.
TODO:
This interface is also used to provide a read-only access to the
computed values of an element. See also the ViewCSS interface.
- return computed values and not literal values
- simplify unit pairs/triples/quadruples
2px 2px 2px 2px -> 2px for border/padding...
- normalize compound properties like:
background: no-repeat left url() #fff
-> background: #fff url() no-repeat left
"""
__all__ = ['CSSStyleDeclaration', 'Property']
__docformat__ = 'restructuredtext'
__version__ = '$Id$'
from cssproperties import CSS2Properties
from property import Property
import cssutils
import xml.dom
class CSSStyleDeclaration(CSS2Properties, cssutils.util.Base2):
"""The CSSStyleDeclaration class represents a single CSS declaration
block. This class may be used to determine the style properties
currently set in a block or to set style properties explicitly
within the block.
While an implementation may not recognize all CSS properties within
a CSS declaration block, it is expected to provide access to all
specified properties in the style sheet through the
CSSStyleDeclaration interface.
Furthermore, implementations that support a specific level of CSS
should correctly handle CSS shorthand properties for that level. For
a further discussion of shorthand properties, see the CSS2Properties
interface.
Additionally the CSS2Properties interface is implemented.
$css2propertyname
All properties defined in the CSS2Properties class are available
as direct properties of CSSStyleDeclaration with their respective
DOM name, so e.g. ``fontStyle`` for property 'font-style'.
These may be used as::
>>> style = CSSStyleDeclaration(cssText='color: red')
>>> style.color = 'green'
>>> print style.color
green
>>> del style.color
>>> print style.color
<BLANKLINE>
Format::
[Property: Value Priority?;]* [Property: Value Priority?]?
"""
def __init__(self, cssText=u'', parentRule=None, readonly=False,
validating=None):
"""
:param cssText:
Shortcut, sets CSSStyleDeclaration.cssText
:param parentRule:
The CSS rule that contains this declaration block or
None if this CSSStyleDeclaration is not attached to a CSSRule.
:param readonly:
defaults to False
:param validating:
a flag defining if this sheet should be validated on change.
Defaults to None, which means defer to the parent stylesheet.
"""
super(CSSStyleDeclaration, self).__init__()
self._parentRule = parentRule
self.validating = validating
self.cssText = cssText
self._readonly = readonly
def __contains__(self, nameOrProperty):
"""Check if a property (or a property with given name) is in style.
:param name:
a string or Property, uses normalized name and not literalname
"""
if isinstance(nameOrProperty, Property):
name = nameOrProperty.name
else:
name = self._normalize(nameOrProperty)
return name in self.__nnames()
def __iter__(self):
"""Iterator of set Property objects with different normalized names."""
def properties():
for name in self.__nnames():
yield self.getProperty(name)
return properties()
def keys(self):
"""Analoguous to standard dict returns property names which are set in
this declaration."""
return list(self.__nnames())
def __getitem__(self, CSSName):
"""Retrieve the value of property ``CSSName`` from this declaration.
``CSSName`` will be always normalized.
"""
return self.getPropertyValue(CSSName)
def __setitem__(self, CSSName, value):
"""Set value of property ``CSSName``. ``value`` may also be a tuple of
(value, priority), e.g. style['color'] = ('red', 'important')
``CSSName`` will be always normalized.
"""
priority = None
if isinstance(value, tuple):
value, priority = value
return self.setProperty(CSSName, value, priority)
def __delitem__(self, CSSName):
"""Delete property ``CSSName`` from this declaration.
If property is not in this declaration return u'' just like
removeProperty.
``CSSName`` will be always normalized.
"""
return self.removeProperty(CSSName)
def __setattr__(self, n, v):
"""Prevent setting of unknown properties on CSSStyleDeclaration
which would not work anyway. For these
``CSSStyleDeclaration.setProperty`` MUST be called explicitly!
TODO:
implementation of known is not really nice, any alternative?
"""
known = ['_tokenizer', '_log', '_ttypes',
'_seq', 'seq', 'parentRule', '_parentRule', 'cssText',
'valid', 'wellformed', 'validating',
'_readonly', '_profiles', '_validating']
known.extend(CSS2Properties._properties)
if n in known:
super(CSSStyleDeclaration, self).__setattr__(n, v)
else:
raise AttributeError(u'Unknown CSS Property, '
u'``CSSStyleDeclaration.setProperty("%s", '
u'...)`` MUST be used.' % n)
def __repr__(self):
return u"cssutils.css.%s(cssText=%r)" % (
self.__class__.__name__,
self.getCssText(separator=u' '))
def __str__(self):
return u"<cssutils.css.%s object length=%r (all: %r) at 0x%x>" % (
self.__class__.__name__,
self.length,
len(self.getProperties(all=True)),
id(self))
def __nnames(self):
"""Return iterator for all different names in order as set
if names are set twice the last one is used (double reverse!)
"""
names = []
for item in reversed(self.seq):
val = item.value
if isinstance(val, Property) and not val.name in names:
names.append(val.name)
return reversed(names)
# overwritten accessor functions for CSS2Properties' properties
def _getP(self, CSSName):
"""(DOM CSS2Properties) Overwritten here and effectively the same as
``self.getPropertyValue(CSSname)``.
Parameter is in CSSname format ('font-style'), see CSS2Properties.
Example::
>>> style = CSSStyleDeclaration(cssText='font-style:italic;')
>>> print style.fontStyle
italic
"""
return self.getPropertyValue(CSSName)
def _setP(self, CSSName, value):
"""(DOM CSS2Properties) Overwritten here and effectively the same as
``self.setProperty(CSSname, value)``.
Only known CSS2Properties may be set this way, otherwise an
AttributeError is raised.
For these unknown properties ``setPropertyValue(CSSname, value)``
has to be called explicitly.
Also setting the priority of properties needs to be done with a
call like ``setPropertyValue(CSSname, value, priority)``.
Example::
>>> style = CSSStyleDeclaration()
>>> style.fontStyle = 'italic'
>>> # or
>>> style.setProperty('font-style', 'italic', '!important')
"""
self.setProperty(CSSName, value)
# TODO: Shorthand ones
def _delP(self, CSSName):
"""(cssutils only) Overwritten here and effectively the same as
``self.removeProperty(CSSname)``.
Example::
>>> style = CSSStyleDeclaration(cssText='font-style:italic;')
>>> del style.fontStyle
>>> print style.fontStyle
<BLANKLINE>
"""
self.removeProperty(CSSName)
def children(self):
"""Generator yielding any known child in this declaration including
*all* properties, comments or CSSUnknownrules.
"""
for item in self._seq:
yield item.value
def _getCssText(self):
"""Return serialized property cssText."""
return cssutils.ser.do_css_CSSStyleDeclaration(self)
def _setCssText(self, cssText):
"""Setting this attribute will result in the parsing of the new value
and resetting of all the properties in the declaration block
including the removal or addition of properties.
:exceptions:
- :exc:`~xml.dom.NoModificationAllowedErr`:
Raised if this declaration is readonly or a property is readonly.
- :exc:`~xml.dom.SyntaxErr`:
Raised if the specified CSS string value has a syntax error and
is unparsable.
"""
self._checkReadonly()
tokenizer = self._tokenize2(cssText)
# for closures: must be a mutable
new = {'wellformed': True}
def ident(expected, seq, token, tokenizer=None):
# a property
tokens = self._tokensupto2(tokenizer, starttoken=token,
semicolon=True)
if self._tokenvalue(tokens[-1]) == u';':
tokens.pop()
property = Property(parent=self)
property.cssText = tokens
if property.wellformed:
seq.append(property, 'Property')
else:
self._log.error(u'CSSStyleDeclaration: Syntax Error in '
u'Property: %s' % self._valuestr(tokens))
# does not matter in this case
return expected
def unexpected(expected, seq, token, tokenizer=None):
# error, find next ; or } to omit upto next property
ignored = self._tokenvalue(token) + self._valuestr(
self._tokensupto2(tokenizer,
propertyvalueendonly=True))
self._log.error(u'CSSStyleDeclaration: Unexpected token, ignoring '
'upto %r.' % ignored,token)
# does not matter in this case
return expected
def char(expected, seq, token, tokenizer=None):
# a standalone ; or error...
if self._tokenvalue(token) == u';':
self._log.info(u'CSSStyleDeclaration: Stripped standalone semicolon'
u': %s' % self._valuestr([token]), neverraise=True)
return expected
else:
return unexpected(expected, seq, token, tokenizer)
# [Property: Value;]* Property: Value?
newseq = self._tempSeq()
wellformed, expected = self._parse(expected=None,
seq=newseq, tokenizer=tokenizer,
productions={'IDENT': ident, 'CHAR': char},
default=unexpected)
# wellformed set by parse
for item in newseq:
item.value._parent = self
# do not check wellformed as invalid things are removed anyway
self._setSeq(newseq)
cssText = property(_getCssText, _setCssText,
doc=u"(DOM) A parsable textual representation of the "
u"declaration block excluding the surrounding curly "
u"braces.")
def getCssText(self, separator=None):
"""
:returns:
serialized property cssText, each property separated by
given `separator` which may e.g. be ``u''`` to be able to use
cssText directly in an HTML style attribute. ``;`` is part of
each property (except the last one) and **cannot** be set with
separator!
"""
return cssutils.ser.do_css_CSSStyleDeclaration(self, separator)
def _setParentRule(self, parentRule):
self._parentRule = parentRule
# for x in self.children():
# x.parent = self
parentRule = property(lambda self: self._parentRule, _setParentRule,
doc="(DOM) The CSS rule that contains this declaration block or "
"None if this CSSStyleDeclaration is not attached to a CSSRule.")
def getProperties(self, name=None, all=False):
"""
:param name:
optional `name` of properties which are requested.
Only properties with this **always normalized** `name` are returned.
If `name` is ``None`` all properties are returned (at least one for
each set name depending on parameter `all`).
:param all:
if ``False`` (DEFAULT) only the effective properties are returned.
If name is given a list with only one property is returned.
if ``True`` all properties including properties set multiple times
with different values or priorities for different UAs are returned.
The order of the properties is fully kept as in the original
stylesheet.
:returns:
a list of :class:`~cssutils.css.Property` objects set in
this declaration.
"""
if name and not all:
# single prop but list
p = self.getProperty(name)
if p:
return [p]
else:
return []
elif not all:
# effective Properties in name order
return [self.getProperty(name) for name in self.__nnames()]
else:
# all properties or all with this name
nname = self._normalize(name)
properties = []
for item in self.seq:
val = item.value
if isinstance(val, Property) and (
(bool(nname) == False) or (val.name == nname)):
properties.append(val)
return properties
def getProperty(self, name, normalize=True):
"""
:param name:
of the CSS property, always lowercase (even if not normalized)
:param normalize:
if ``True`` (DEFAULT) name will be normalized (lowercase, no simple
escapes) so "color", "COLOR" or "C\olor" will all be equivalent
If ``False`` may return **NOT** the effective value but the
effective for the unnormalized name.
:returns:
the effective :class:`~cssutils.css.Property` object.
"""
nname = self._normalize(name)
found = None
for item in reversed(self.seq):
val = item.value
if isinstance(val, Property):
if (normalize and nname == val.name) or name == val.literalname:
if val.priority:
return val
elif not found:
found = val
return found
def getPropertyCSSValue(self, name, normalize=True):
"""
:param name:
of the CSS property, always lowercase (even if not normalized)
:param normalize:
if ``True`` (DEFAULT) name will be normalized (lowercase, no simple
escapes) so "color", "COLOR" or "C\olor" will all be equivalent
If ``False`` may return **NOT** the effective value but the
effective for the unnormalized name.
:returns:
:class:`~cssutils.css.CSSValue`, the value of the effective
property if it has been explicitly set for this declaration block.
(DOM)
Used to retrieve the object representation of the value of a CSS
property if it has been explicitly set within this declaration
block. Returns None if the property has not been set.
(This method returns None if the property is a shorthand
property. Shorthand property values can only be accessed and
modified as strings, using the getPropertyValue and setProperty
methods.)
**cssutils currently always returns a CSSValue if the property is
set.**
for more on shorthand properties see
http://www.dustindiaz.com/css-shorthand/
"""
nname = self._normalize(name)
if nname in self._SHORTHANDPROPERTIES:
self._log.info(u'CSSValue for shorthand property "%s" should be '
u'None, this may be implemented later.' %
nname, neverraise=True)
p = self.getProperty(name, normalize)
if p:
return p.cssValue
else:
return None
def getPropertyValue(self, name, normalize=True):
"""
:param name:
of the CSS property, always lowercase (even if not normalized)
:param normalize:
if ``True`` (DEFAULT) name will be normalized (lowercase, no simple
escapes) so "color", "COLOR" or "C\olor" will all be equivalent
If ``False`` may return **NOT** the effective value but the
effective for the unnormalized name.
:returns:
the value of the effective property if it has been explicitly set
for this declaration block. Returns the empty string if the
property has not been set.
"""
p = self.getProperty(name, normalize)
if p:
return p.value
else:
return u''
def getPropertyPriority(self, name, normalize=True):
"""
:param name:
of the CSS property, always lowercase (even if not normalized)
:param normalize:
if ``True`` (DEFAULT) name will be normalized (lowercase, no simple
escapes) so "color", "COLOR" or "C\olor" will all be equivalent
If ``False`` may return **NOT** the effective value but the
effective for the unnormalized name.
:returns:
the priority of the effective CSS property (e.g. the
"important" qualifier) if the property has been explicitly set in
this declaration block. The empty string if none exists.
"""
p = self.getProperty(name, normalize)
if p:
return p.priority
else:
return u''
def removeProperty(self, name, normalize=True):
"""
(DOM)
Used to remove a CSS property if it has been explicitly set within
this declaration block.
:param name:
of the CSS property
:param normalize:
if ``True`` (DEFAULT) name will be normalized (lowercase, no simple
escapes) so "color", "COLOR" or "C\olor" will all be equivalent.
The effective Property value is returned and *all* Properties
with ``Property.name == name`` are removed.
If ``False`` may return **NOT** the effective value but the
effective for the unnormalized `name` only. Also only the
Properties with the literal name `name` are removed.
:returns:
the value of the property if it has been explicitly set for
this declaration block. Returns the empty string if the property
has not been set or the property name does not correspond to a
known CSS property
:exceptions:
- :exc:`~xml.dom.NoModificationAllowedErr`:
Raised if this declaration is readonly or the property is
readonly.
"""
self._checkReadonly()
r = self.getPropertyValue(name, normalize=normalize)
newseq = self._tempSeq()
if normalize:
# remove all properties with name == nname
nname = self._normalize(name)
for item in self.seq:
if not (isinstance(item.value, Property)
and item.value.name == nname):
newseq.appendItem(item)
else:
# remove all properties with literalname == name
for item in self.seq:
if not (isinstance(item.value, Property)
and item.value.literalname == name):
newseq.appendItem(item)
self._setSeq(newseq)
return r
def setProperty(self, name, value=None, priority=u'',
normalize=True, replace=True):
"""(DOM) Set a property value and priority within this declaration
block.
:param name:
of the CSS property to set (in W3C DOM the parameter is called
"propertyName"), always lowercase (even if not normalized)
If a property with this `name` is present it will be reset.
cssutils also allowed `name` to be a
:class:`~cssutils.css.Property` object, all other
parameter are ignored in this case
:param value:
the new value of the property, ignored if `name` is a Property.
:param priority:
the optional priority of the property (e.g. "important"),
ignored if `name` is a Property.
:param normalize:
if True (DEFAULT) `name` will be normalized (lowercase, no simple
escapes) so "color", "COLOR" or "C\olor" will all be equivalent
:param replace:
if True (DEFAULT) the given property will replace a present
property. If False a new property will be added always.
The difference to `normalize` is that two or more properties with
the same name may be set, useful for e.g. stuff like::
background: red;
background: rgba(255, 0, 0, 0.5);
which defines the same property but only capable UAs use the last
property value, older ones use the first value.
:exceptions:
- :exc:`~xml.dom.SyntaxErr`:
Raised if the specified value has a syntax error and is
unparsable.
- :exc:`~xml.dom.NoModificationAllowedErr`:
Raised if this declaration is readonly or the property is
readonly.
"""
self._checkReadonly()
if isinstance(name, Property):
newp = name
name = newp.literalname
elif not value:
# empty string or None effectively removed property
return self.removeProperty(name)
else:
newp = Property(name, value, priority)
if newp.wellformed:
if replace:
# check if update
nname = self._normalize(name)
properties = self.getProperties(name, all=(not normalize))
for property in reversed(properties):
if normalize and property.name == nname:
property.cssValue = newp.cssValue.cssText
property.priority = newp.priority
return
elif property.literalname == name:
property.cssValue = newp.cssValue.cssText
property.priority = newp.priority
return
# not yet set or forced omit replace
newp.parent = self
self.seq._readonly = False
self.seq.append(newp, 'Property')
self.seq._readonly = True
else:
self._log.warn(u'Invalid Property: %s: %s %s'
% (name, value, priority))
def item(self, index):
"""(DOM) Retrieve the properties that have been explicitly set in
this declaration block. The order of the properties retrieved using
this method does not have to be the order in which they were set.
This method can be used to iterate over all properties in this
declaration block.
:param index:
of the property to retrieve, negative values behave like
negative indexes on Python lists, so -1 is the last element
:returns:
the name of the property at this ordinal position. The
empty string if no property exists at this position.
**ATTENTION:**
Only properties with different names are counted. If two
properties with the same name are present in this declaration
only the effective one is included.
:meth:`item` and :attr:`length` work on the same set here.
"""
names = list(self.__nnames())
try:
return names[index]
except IndexError:
return u''
length = property(lambda self: len(list(self.__nnames())),
doc=u"(DOM) The number of distinct properties that have "
u"been explicitly in this declaration block. The "
u"range of valid indices is 0 to length-1 inclusive. "
u"These are properties with a different ``name`` "
u"only. :meth:`item` and :attr:`length` work on the "
u"same set here.")
def _getValidating(self):
try:
# CSSParser.parseX() sets validating of stylesheet
return self.parentRule.parentStyleSheet.validating
except AttributeError:
# CSSParser.parseStyle() sets validating of declaration
if self._validating is not None:
return self._validating
# default
return True
def _setValidating(self, validating):
self._validating = validating
validating = property(_getValidating, _setValidating,
doc=u"If ``True`` this declaration validates "
u"contained properties. The parent StyleSheet "
u"validation setting does *always* win though so "
u"even if validating is True it may not validate "
u"if the StyleSheet defines else!")

234
libs/cssutils/css/cssstylerule.py

@ -1,234 +0,0 @@
"""CSSStyleRule implements DOM Level 2 CSS CSSStyleRule."""
__all__ = ['CSSStyleRule']
__docformat__ = 'restructuredtext'
__version__ = '$Id$'
from cssstyledeclaration import CSSStyleDeclaration
from selectorlist import SelectorList
import cssrule
import cssutils
import xml.dom
class CSSStyleRule(cssrule.CSSRule):
"""The CSSStyleRule object represents a ruleset specified (if any) in a CSS
style sheet. It provides access to a declaration block as well as to the
associated group of selectors.
Format::
: selector [ COMMA S* selector ]*
LBRACE S* declaration [ ';' S* declaration ]* '}' S*
;
"""
def __init__(self, selectorText=None, style=None, parentRule=None,
parentStyleSheet=None, readonly=False):
"""
:Parameters:
selectorText
string parsed into selectorList
style
string parsed into CSSStyleDeclaration for this CSSStyleRule
readonly
if True allows setting of properties in constructor only
"""
super(CSSStyleRule, self).__init__(parentRule=parentRule,
parentStyleSheet=parentStyleSheet)
self.selectorList = SelectorList()
if selectorText:
self.selectorText = selectorText
if style:
self.style = style
else:
self.style = CSSStyleDeclaration()
self._readonly = readonly
def __repr__(self):
if self._namespaces:
st = (self.selectorText, self._namespaces)
else:
st = self.selectorText
return u"cssutils.css.%s(selectorText=%r, style=%r)" % (
self.__class__.__name__, st, self.style.cssText)
def __str__(self):
return u"<cssutils.css.%s object selectorText=%r style=%r _namespaces=%r "\
u"at 0x%x>" % (self.__class__.__name__,
self.selectorText,
self.style.cssText,
self._namespaces,
id(self))
def _getCssText(self):
"""Return serialized property cssText."""
return cssutils.ser.do_CSSStyleRule(self)
def _setCssText(self, cssText):
"""
:param cssText:
a parseable string or a tuple of (cssText, dict-of-namespaces)
:exceptions:
- :exc:`~xml.dom.NamespaceErr`:
Raised if the specified selector uses an unknown namespace
prefix.
- :exc:`~xml.dom.SyntaxErr`:
Raised if the specified CSS string value has a syntax error and
is unparsable.
- :exc:`~xml.dom.InvalidModificationErr`:
Raised if the specified CSS string value represents a different
type of rule than the current one.
- :exc:`~xml.dom.HierarchyRequestErr`:
Raised if the rule cannot be inserted at this point in the
style sheet.
- :exc:`~xml.dom.NoModificationAllowedErr`:
Raised if the rule is readonly.
"""
super(CSSStyleRule, self)._setCssText(cssText)
# might be (cssText, namespaces)
cssText, namespaces = self._splitNamespacesOff(cssText)
try:
# use parent style sheet ones if available
namespaces = self.parentStyleSheet.namespaces
except AttributeError:
pass
tokenizer = self._tokenize2(cssText)
selectortokens = self._tokensupto2(tokenizer, blockstartonly=True)
styletokens = self._tokensupto2(tokenizer, blockendonly=True)
trail = self._nexttoken(tokenizer)
if trail:
self._log.error(u'CSSStyleRule: Trailing content: %s' %
self._valuestr(cssText), token=trail)
elif not selectortokens:
self._log.error(u'CSSStyleRule: No selector found: %r' %
self._valuestr(cssText))
elif self._tokenvalue(selectortokens[0]).startswith(u'@'):
self._log.error(u'CSSStyleRule: No style rule: %r' %
self._valuestr(cssText),
error=xml.dom.InvalidModificationErr)
else:
newSelectorList = SelectorList(parentRule=self)
newStyle = CSSStyleDeclaration(parentRule=self)
ok = True
bracetoken = selectortokens.pop()
if self._tokenvalue(bracetoken) != u'{':
ok = False
self._log.error(
u'CSSStyleRule: No start { of style declaration found: %r' %
self._valuestr(cssText), bracetoken)
elif not selectortokens:
ok = False
self._log.error(u'CSSStyleRule: No selector found: %r.' %
self._valuestr(cssText), bracetoken)
# SET
newSelectorList.selectorText = (selectortokens,
namespaces)
if not styletokens:
ok = False
self._log.error(
u'CSSStyleRule: No style declaration or "}" found: %r' %
self._valuestr(cssText))
else:
braceorEOFtoken = styletokens.pop()
val, typ = self._tokenvalue(braceorEOFtoken),\
self._type(braceorEOFtoken)
if val != u'}' and typ != 'EOF':
ok = False
self._log.error(u'CSSStyleRule: No "}" after style '
u'declaration found: %r'
% self._valuestr(cssText))
else:
if 'EOF' == typ:
# add again as style needs it
styletokens.append(braceorEOFtoken)
# SET, may raise:
newStyle.cssText = styletokens
if ok:
self.selectorList = newSelectorList
self.style = newStyle
cssText = property(_getCssText, _setCssText,
doc=u"(DOM) The parsable textual representation of this "
u"rule.")
def __getNamespaces(self):
"""Uses children namespaces if not attached to a sheet, else the sheet's
ones."""
try:
return self.parentStyleSheet.namespaces
except AttributeError:
return self.selectorList._namespaces
_namespaces = property(__getNamespaces,
doc=u"If this Rule is attached to a CSSStyleSheet "
u"the namespaces of that sheet are mirrored "
u"here. While the Rule is not attached the "
u"namespaces of selectorList are used.""")
def _setSelectorList(self, selectorList):
"""
:param selectorList: A SelectorList which replaces the current
selectorList object
"""
self._checkReadonly()
selectorList._parentRule = self
self._selectorList = selectorList
_selectorList = None
selectorList = property(lambda self: self._selectorList, _setSelectorList,
doc=u"The SelectorList of this rule.")
def _setSelectorText(self, selectorText):
"""
wrapper for cssutils SelectorList object
:param selectorText:
of type string, might also be a comma separated list
of selectors
:exceptions:
- :exc:`~xml.dom.NamespaceErr`:
Raised if the specified selector uses an unknown namespace
prefix.
- :exc:`~xml.dom.SyntaxErr`:
Raised if the specified CSS string value has a syntax error
and is unparsable.
- :exc:`~xml.dom.NoModificationAllowedErr`:
Raised if this rule is readonly.
"""
self._checkReadonly()
sl = SelectorList(selectorText=selectorText, parentRule=self)
if sl.wellformed:
self._selectorList = sl
selectorText = property(lambda self: self._selectorList.selectorText,
_setSelectorText,
doc=u"(DOM) The textual representation of the "
u"selector for the rule set.")
def _setStyle(self, style):
"""
:param style: A string or CSSStyleDeclaration which replaces the
current style object.
"""
self._checkReadonly()
if isinstance(style, basestring):
self._style = CSSStyleDeclaration(cssText=style, parentRule=self)
else:
style._parentRule = self
self._style = style
style = property(lambda self: self._style, _setStyle,
doc=u"(DOM) The declaration-block of this rule set.")
type = property(lambda self: self.STYLE_RULE,
doc=u"The type of this rule, as defined by a CSSRule "
"type constant.")
wellformed = property(lambda self: self.selectorList.wellformed)

804
libs/cssutils/css/cssstylesheet.py

@ -1,804 +0,0 @@
"""CSSStyleSheet implements DOM Level 2 CSS CSSStyleSheet.
Partly also:
- http://dev.w3.org/csswg/cssom/#the-cssstylesheet
- http://www.w3.org/TR/2006/WD-css3-namespace-20060828/
TODO:
- ownerRule and ownerNode
"""
__all__ = ['CSSStyleSheet']
__docformat__ = 'restructuredtext'
__version__ = '$Id$'
from cssutils.helper import Deprecated
from cssutils.util import _Namespaces, _SimpleNamespaces, _readUrl
from cssrule import CSSRule
from cssvariablesdeclaration import CSSVariablesDeclaration
import cssutils.stylesheets
import xml.dom
class CSSStyleSheet(cssutils.stylesheets.StyleSheet):
"""CSSStyleSheet represents a CSS style sheet.
Format::
stylesheet
: [ CHARSET_SYM S* STRING S* ';' ]?
[S|CDO|CDC]* [ import [S|CDO|CDC]* ]*
[ namespace [S|CDO|CDC]* ]* # according to @namespace WD
[ [ ruleset | media | page ] [S|CDO|CDC]* ]*
``cssRules``
All Rules in this style sheet, a :class:`~cssutils.css.CSSRuleList`.
"""
def __init__(self, href=None, media=None, title=u'', disabled=None,
ownerNode=None, parentStyleSheet=None, readonly=False,
ownerRule=None,
validating=True):
"""
For parameters see :class:`~cssutils.stylesheets.StyleSheet`
"""
super(CSSStyleSheet, self).__init__(
'text/css', href, media, title, disabled,
ownerNode, parentStyleSheet,
validating=validating)
self._ownerRule = ownerRule
self.cssRules = cssutils.css.CSSRuleList()
self._namespaces = _Namespaces(parentStyleSheet=self, log=self._log)
self._variables = CSSVariablesDeclaration()
self._readonly = readonly
# used only during setting cssText by parse*()
self.__encodingOverride = None
self._fetcher = None
def __iter__(self):
"Generator which iterates over cssRules."
for rule in self._cssRules:
yield rule
def __repr__(self):
if self.media:
mediaText = self.media.mediaText
else:
mediaText = None
return "cssutils.css.%s(href=%r, media=%r, title=%r)" % (
self.__class__.__name__,
self.href, mediaText, self.title)
def __str__(self):
if self.media:
mediaText = self.media.mediaText
else:
mediaText = None
return "<cssutils.css.%s object encoding=%r href=%r "\
"media=%r title=%r namespaces=%r at 0x%x>" % (
self.__class__.__name__, self.encoding, self.href,
mediaText, self.title, self.namespaces.namespaces,
id(self))
def _cleanNamespaces(self):
"Remove all namespace rules with same namespaceURI but last."
rules = self.cssRules
namespaceitems = self.namespaces.items()
i = 0
while i < len(rules):
rule = rules[i]
if rule.type == rule.NAMESPACE_RULE and \
(rule.prefix, rule.namespaceURI) not in namespaceitems:
self.deleteRule(i)
else:
i += 1
def _getUsedURIs(self):
"Return set of URIs used in the sheet."
useduris = set()
for r1 in self:
if r1.STYLE_RULE == r1.type:
useduris.update(r1.selectorList._getUsedUris())
elif r1.MEDIA_RULE == r1.type:
for r2 in r1:
if r2.type == r2.STYLE_RULE:
useduris.update(r2.selectorList._getUsedUris())
return useduris
def _setCssRules(self, cssRules):
"Set new cssRules and update contained rules refs."
cssRules.append = self.insertRule
cssRules.extend = self.insertRule
cssRules.__delitem__ = self.deleteRule
for rule in cssRules:
rule._parentStyleSheet = self
self._cssRules = cssRules
cssRules = property(lambda self: self._cssRules, _setCssRules,
u"All Rules in this style sheet, a "
u":class:`~cssutils.css.CSSRuleList`.")
def _getCssText(self):
"Textual representation of the stylesheet (a byte string)."
return cssutils.ser.do_CSSStyleSheet(self)
def _setCssText(self, cssText):
"""Parse `cssText` and overwrites the whole stylesheet.
:param cssText:
a parseable string or a tuple of (cssText, dict-of-namespaces)
:exceptions:
- :exc:`~xml.dom.NamespaceErr`:
If a namespace prefix is found which is not declared.
- :exc:`~xml.dom.NoModificationAllowedErr`:
Raised if the rule is readonly.
- :exc:`~xml.dom.SyntaxErr`:
Raised if the specified CSS string value has a syntax error and
is unparsable.
"""
self._checkReadonly()
cssText, namespaces = self._splitNamespacesOff(cssText)
tokenizer = self._tokenize2(cssText)
def S(expected, seq, token, tokenizer=None):
# @charset must be at absolute beginning of style sheet
# or 0 for py3
return max(1, expected or 0)
def COMMENT(expected, seq, token, tokenizer=None):
"special: sets parent*"
self.insertRule(cssutils.css.CSSComment([token],
parentStyleSheet=self))
# or 0 for py3
return max(1, expected or 0)
def charsetrule(expected, seq, token, tokenizer):
# parse and consume tokens in any case
rule = cssutils.css.CSSCharsetRule(parentStyleSheet=self)
rule.cssText = self._tokensupto2(tokenizer, token)
if expected > 0:
self._log.error(u'CSSStylesheet: CSSCharsetRule only allowed '
u'at beginning of stylesheet.',
token, xml.dom.HierarchyRequestErr)
return expected
elif rule.wellformed:
self.insertRule(rule)
return 1
def importrule(expected, seq, token, tokenizer):
# parse and consume tokens in any case
rule = cssutils.css.CSSImportRule(parentStyleSheet=self)
rule.cssText = self._tokensupto2(tokenizer, token)
if expected > 1:
self._log.error(u'CSSStylesheet: CSSImportRule not allowed '
u'here.', token, xml.dom.HierarchyRequestErr)
return expected
elif rule.wellformed:
self.insertRule(rule)
return 1
def namespacerule(expected, seq, token, tokenizer):
# parse and consume tokens in any case
rule = cssutils.css.CSSNamespaceRule(cssText=self._tokensupto2(tokenizer,
token),
parentStyleSheet=self)
if expected > 2:
self._log.error(u'CSSStylesheet: CSSNamespaceRule not allowed '
u'here.', token, xml.dom.HierarchyRequestErr)
return expected
elif rule.wellformed:
if rule.prefix not in self.namespaces:
# add new if not same prefix
self.insertRule(rule, _clean=False)
else:
# same prefix => replace namespaceURI
for r in self.cssRules.rulesOfType(rule.NAMESPACE_RULE):
if r.prefix == rule.prefix:
r._replaceNamespaceURI(rule.namespaceURI)
self._namespaces[rule.prefix] = rule.namespaceURI
return 2
def variablesrule(expected, seq, token, tokenizer):
# parse and consume tokens in any case
rule = cssutils.css.CSSVariablesRule(parentStyleSheet=self)
rule.cssText = self._tokensupto2(tokenizer, token)
if expected > 2:
self._log.error(u'CSSStylesheet: CSSVariablesRule not allowed '
u'here.', token, xml.dom.HierarchyRequestErr)
return expected
elif rule.wellformed:
self.insertRule(rule)
self._updateVariables()
return 2
def fontfacerule(expected, seq, token, tokenizer):
# parse and consume tokens in any case
rule = cssutils.css.CSSFontFaceRule(parentStyleSheet=self)
rule.cssText = self._tokensupto2(tokenizer, token)
if rule.wellformed:
self.insertRule(rule)
return 3
def mediarule(expected, seq, token, tokenizer):
# parse and consume tokens in any case
rule = cssutils.css.CSSMediaRule(parentStyleSheet=self)
rule.cssText = self._tokensupto2(tokenizer, token)
if rule.wellformed:
self.insertRule(rule)
return 3
def pagerule(expected, seq, token, tokenizer):
# parse and consume tokens in any case
rule = cssutils.css.CSSPageRule(parentStyleSheet=self)
rule.cssText = self._tokensupto2(tokenizer, token)
if rule.wellformed:
self.insertRule(rule)
return 3
def unknownrule(expected, seq, token, tokenizer):
# parse and consume tokens in any case
if token[1] in cssutils.css.MarginRule.margins:
self._log.error(u'CSSStylesheet: MarginRule out CSSPageRule.',
token, neverraise=True)
rule = cssutils.css.MarginRule(parentStyleSheet=self)
rule.cssText = self._tokensupto2(tokenizer, token)
else:
self._log.warn(u'CSSStylesheet: Unknown @rule found.',
token, neverraise=True)
rule = cssutils.css.CSSUnknownRule(parentStyleSheet=self)
rule.cssText = self._tokensupto2(tokenizer, token)
if rule.wellformed:
self.insertRule(rule)
# or 0 for py3
return max(1, expected or 0)
def ruleset(expected, seq, token, tokenizer):
# parse and consume tokens in any case
rule = cssutils.css.CSSStyleRule(parentStyleSheet=self)
rule.cssText = self._tokensupto2(tokenizer, token)
if rule.wellformed:
self.insertRule(rule)
return 3
# save for possible reset
oldCssRules = self.cssRules
oldNamespaces = self._namespaces
self.cssRules = cssutils.css.CSSRuleList()
# simple during parse
self._namespaces = namespaces
self._variables = CSSVariablesDeclaration()
# not used?!
newseq = []
# ['CHARSET', 'IMPORT', ('VAR', NAMESPACE'), ('PAGE', 'MEDIA', ruleset)]
wellformed, expected = self._parse(0, newseq, tokenizer,
{'S': S,
'COMMENT': COMMENT,
'CDO': lambda *ignored: None,
'CDC': lambda *ignored: None,
'CHARSET_SYM': charsetrule,
'FONT_FACE_SYM': fontfacerule,
'IMPORT_SYM': importrule,
'NAMESPACE_SYM': namespacerule,
'PAGE_SYM': pagerule,
'MEDIA_SYM': mediarule,
'VARIABLES_SYM': variablesrule,
'ATKEYWORD': unknownrule
},
default=ruleset)
if wellformed:
# use proper namespace object
self._namespaces = _Namespaces(parentStyleSheet=self, log=self._log)
self._cleanNamespaces()
else:
# reset
self._cssRules = oldCssRules
self._namespaces = oldNamespaces
self._updateVariables()
self._cleanNamespaces()
cssText = property(_getCssText, _setCssText,
"Textual representation of the stylesheet (a byte string)")
def _resolveImport(self, url):
"""Read (encoding, enctype, decodedContent) from `url` for @import
sheets."""
try:
# only available during parsing of a complete sheet
parentEncoding = self.__newEncoding
except AttributeError:
try:
# explicit @charset
parentEncoding = self._cssRules[0].encoding
except (IndexError, AttributeError):
# default not UTF-8 but None!
parentEncoding = None
return _readUrl(url, fetcher=self._fetcher,
overrideEncoding=self.__encodingOverride,
parentEncoding=parentEncoding)
def _setCssTextWithEncodingOverride(self, cssText, encodingOverride=None,
encoding=None):
"""Set `cssText` but use `encodingOverride` to overwrite detected
encoding. This is used by parse and @import during setting of cssText.
If `encoding` is given use this but do not save as `encodingOverride`.
"""
if encodingOverride:
# encoding during resolving of @import
self.__encodingOverride = encodingOverride
if encoding:
# save for nested @import
self.__newEncoding = encoding
self.cssText = cssText
if encodingOverride:
# set encodingOverride explicit again!
self.encoding = self.__encodingOverride
# del?
self.__encodingOverride = None
elif encoding:
# may e.g. be httpEncoding
self.encoding = encoding
try:
del self.__newEncoding
except AttributeError, e:
pass
def _setFetcher(self, fetcher=None):
"""Set @import URL loader, if None the default is used."""
self._fetcher = fetcher
def _getEncoding(self):
"""Encoding set in :class:`~cssutils.css.CSSCharsetRule` or if ``None``
resulting in default ``utf-8`` encoding being used."""
try:
return self._cssRules[0].encoding
except (IndexError, AttributeError):
return 'utf-8'
def _setEncoding(self, encoding):
"""Set `encoding` of charset rule if present in sheet or insert a new
:class:`~cssutils.css.CSSCharsetRule` with given `encoding`.
If `encoding` is None removes charsetrule if present resulting in
default encoding of utf-8.
"""
try:
rule = self._cssRules[0]
except IndexError:
rule = None
if rule and rule.CHARSET_RULE == rule.type:
if encoding:
rule.encoding = encoding
else:
self.deleteRule(0)
elif encoding:
self.insertRule(cssutils.css.CSSCharsetRule(encoding=encoding), 0)
encoding = property(_getEncoding, _setEncoding,
"(cssutils) Reflect encoding of an @charset rule or 'utf-8' "
"(default) if set to ``None``")
namespaces = property(lambda self: self._namespaces,
doc="All Namespaces used in this CSSStyleSheet.")
def _updateVariables(self):
"""Updates self._variables, called when @import or @variables rules
is added to sheet.
"""
for r in self.cssRules.rulesOfType(CSSRule.IMPORT_RULE):
s = r.styleSheet
if s:
for var in s.variables:
self._variables.setVariable(var, s.variables[var])
# for r in self.cssRules.rulesOfType(CSSRule.IMPORT_RULE):
# for vr in r.styleSheet.cssRules.rulesOfType(CSSRule.VARIABLES_RULE):
# for var in vr.variables:
# self._variables.setVariable(var, vr.variables[var])
for vr in self.cssRules.rulesOfType(CSSRule.VARIABLES_RULE):
for var in vr.variables:
self._variables.setVariable(var, vr.variables[var])
variables = property(lambda self: self._variables,
doc=u"A :class:`cssutils.css.CSSVariablesDeclaration` "
u"containing all available variables in this "
u"CSSStyleSheet including the ones defined in "
u"imported sheets.")
def add(self, rule):
"""Add `rule` to style sheet at appropriate position.
Same as ``insertRule(rule, inOrder=True)``.
"""
return self.insertRule(rule, index=None, inOrder=True)
def deleteRule(self, index):
"""Delete rule at `index` from the style sheet.
:param index:
The `index` of the rule to be removed from the StyleSheet's rule
list. For an `index` < 0 **no** :exc:`~xml.dom.IndexSizeErr` is
raised but rules for normal Python lists are used. E.g.
``deleteRule(-1)`` removes the last rule in cssRules.
`index` may also be a CSSRule object which will then be removed
from the StyleSheet.
:exceptions:
- :exc:`~xml.dom.IndexSizeErr`:
Raised if the specified index does not correspond to a rule in
the style sheet's rule list.
- :exc:`~xml.dom.NamespaceErr`:
Raised if removing this rule would result in an invalid StyleSheet
- :exc:`~xml.dom.NoModificationAllowedErr`:
Raised if this style sheet is readonly.
"""
self._checkReadonly()
if isinstance(index, CSSRule):
for i, r in enumerate(self.cssRules):
if index == r:
index = i
break
else:
raise xml.dom.IndexSizeErr(u"CSSStyleSheet: Not a rule in"
" this sheets'a cssRules list: %s"
% index)
try:
rule = self._cssRules[index]
except IndexError:
raise xml.dom.IndexSizeErr(
u'CSSStyleSheet: %s is not a valid index in the rulelist of '
u'length %i' % (index, self._cssRules.length))
else:
if rule.type == rule.NAMESPACE_RULE:
# check all namespacerules if used
uris = [r.namespaceURI for r in self
if r.type == r.NAMESPACE_RULE]
useduris = self._getUsedURIs()
if rule.namespaceURI in useduris and\
uris.count(rule.namespaceURI) == 1:
raise xml.dom.NoModificationAllowedErr(
u'CSSStyleSheet: NamespaceURI defined in this rule is '
u'used, cannot remove.')
return
rule._parentStyleSheet = None # detach
del self._cssRules[index] # delete from StyleSheet
def insertRule(self, rule, index=None, inOrder=False, _clean=True):
"""
Used to insert a new rule into the style sheet. The new rule now
becomes part of the cascade.
:param rule:
a parsable DOMString, in cssutils also a
:class:`~cssutils.css.CSSRule` or :class:`~cssutils.css.CSSRuleList`
:param index:
of the rule before the new rule will be inserted.
If the specified `index` is equal to the length of the
StyleSheet's rule collection, the rule will be added to the end
of the style sheet.
If `index` is not given or ``None`` rule will be appended to rule
list.
:param inOrder:
if ``True`` the rule will be put to a proper location while
ignoring `index` and without raising
:exc:`~xml.dom.HierarchyRequestErr`.
The resulting index is returned nevertheless.
:returns: The index within the style sheet's rule collection
:Exceptions:
- :exc:`~xml.dom.HierarchyRequestErr`:
Raised if the rule cannot be inserted at the specified `index`
e.g. if an @import rule is inserted after a standard rule set
or other at-rule.
- :exc:`~xml.dom.IndexSizeErr`:
Raised if the specified `index` is not a valid insertion point.
- :exc:`~xml.dom.NoModificationAllowedErr`:
Raised if this style sheet is readonly.
- :exc:`~xml.dom.SyntaxErr`:
Raised if the specified rule has a syntax error and is
unparsable.
"""
self._checkReadonly()
# check position
if index is None:
index = len(self._cssRules)
elif index < 0 or index > self._cssRules.length:
raise xml.dom.IndexSizeErr(
u'CSSStyleSheet: Invalid index %s for CSSRuleList with a '
u'length of %s.' % (index, self._cssRules.length))
return
if isinstance(rule, basestring):
# init a temp sheet which has the same properties as self
tempsheet = CSSStyleSheet(href=self.href,
media=self.media,
title=self.title,
parentStyleSheet=self.parentStyleSheet,
ownerRule=self.ownerRule)
tempsheet._ownerNode = self.ownerNode
tempsheet._fetcher = self._fetcher
# prepend encoding if in this sheet to be able to use it in
# @import rules encoding resolution
# do not add if new rule startswith "@charset" (which is exact!)
if not rule.startswith(u'@charset') and (self._cssRules and
self._cssRules[0].type == self._cssRules[0].CHARSET_RULE):
# rule 0 is @charset!
newrulescount, newruleindex = 2, 1
rule = self._cssRules[0].cssText + rule
else:
newrulescount, newruleindex = 1, 0
# parse the new rule(s)
tempsheet.cssText = (rule, self._namespaces)
if len(tempsheet.cssRules) != newrulescount or (not isinstance(
tempsheet.cssRules[newruleindex], cssutils.css.CSSRule)):
self._log.error(u'CSSStyleSheet: Not a CSSRule: %s' % rule)
return
rule = tempsheet.cssRules[newruleindex]
rule._parentStyleSheet = None # done later?
# TODO:
#tempsheet._namespaces = self._namespaces
#variables?
elif isinstance(rule, cssutils.css.CSSRuleList):
# insert all rules
for i, r in enumerate(rule):
self.insertRule(r, index + i)
return index
if not rule.wellformed:
self._log.error(u'CSSStyleSheet: Invalid rules cannot be added.')
return
# CHECK HIERARCHY
# @charset
if rule.type == rule.CHARSET_RULE:
if inOrder:
index = 0
# always first and only
if (self._cssRules
and self._cssRules[0].type == rule.CHARSET_RULE):
self._cssRules[0].encoding = rule.encoding
else:
self._cssRules.insert(0, rule)
elif index != 0 or (self._cssRules and
self._cssRules[0].type == rule.CHARSET_RULE):
self._log.error(
u'CSSStylesheet: @charset only allowed once at the'
' beginning of a stylesheet.',
error=xml.dom.HierarchyRequestErr)
return
else:
self._cssRules.insert(index, rule)
# @unknown or comment
elif rule.type in (rule.UNKNOWN_RULE, rule.COMMENT) and not inOrder:
if index == 0 and self._cssRules and\
self._cssRules[0].type == rule.CHARSET_RULE:
self._log.error(
u'CSSStylesheet: @charset must be the first rule.',
error=xml.dom.HierarchyRequestErr)
return
else:
self._cssRules.insert(index, rule)
# @import
elif rule.type == rule.IMPORT_RULE:
if inOrder:
# automatic order
if rule.type in (r.type for r in self):
# find last of this type
for i, r in enumerate(reversed(self._cssRules)):
if r.type == rule.type:
index = len(self._cssRules) - i
break
else:
# find first point to insert
if self._cssRules and\
self._cssRules[0].type in (rule.CHARSET_RULE,
rule.COMMENT):
index = 1
else:
index = 0
else:
# after @charset
if index == 0 and self._cssRules and\
self._cssRules[0].type == rule.CHARSET_RULE:
self._log.error(
u'CSSStylesheet: Found @charset at index 0.',
error=xml.dom.HierarchyRequestErr)
return
# before @namespace @variables @page @font-face @media stylerule
for r in self._cssRules[:index]:
if r.type in (r.NAMESPACE_RULE,
r.VARIABLES_RULE,
r.MEDIA_RULE,
r.PAGE_RULE,
r.STYLE_RULE,
r.FONT_FACE_RULE):
self._log.error(
u'CSSStylesheet: Cannot insert @import here,'
' found @namespace, @variables, @media, @page or'
' CSSStyleRule before index %s.' %
index,
error=xml.dom.HierarchyRequestErr)
return
self._cssRules.insert(index, rule)
self._updateVariables()
# @namespace
elif rule.type == rule.NAMESPACE_RULE:
if inOrder:
if rule.type in (r.type for r in self):
# find last of this type
for i, r in enumerate(reversed(self._cssRules)):
if r.type == rule.type:
index = len(self._cssRules) - i
break
else:
# find first point to insert
for i, r in enumerate(self._cssRules):
if r.type in (r.VARIABLES_RULE, r.MEDIA_RULE,
r.PAGE_RULE, r.STYLE_RULE,
r.FONT_FACE_RULE, r.UNKNOWN_RULE,
r.COMMENT):
index = i # before these
break
else:
# after @charset and @import
for r in self._cssRules[index:]:
if r.type in (r.CHARSET_RULE, r.IMPORT_RULE):
self._log.error(
u'CSSStylesheet: Cannot insert @namespace here,'
' found @charset or @import after index %s.' %
index,
error=xml.dom.HierarchyRequestErr)
return
# before @variables @media @page @font-face and stylerule
for r in self._cssRules[:index]:
if r.type in (r.VARIABLES_RULE,
r.MEDIA_RULE,
r.PAGE_RULE,
r.STYLE_RULE,
r.FONT_FACE_RULE):
self._log.error(
u'CSSStylesheet: Cannot insert @namespace here,'
' found @variables, @media, @page or CSSStyleRule'
' before index %s.' %
index,
error=xml.dom.HierarchyRequestErr)
return
if not (rule.prefix in self.namespaces and
self.namespaces[rule.prefix] == rule.namespaceURI):
# no doublettes
self._cssRules.insert(index, rule)
if _clean:
self._cleanNamespaces()
# @variables
elif rule.type == rule.VARIABLES_RULE:
if inOrder:
if rule.type in (r.type for r in self):
# find last of this type
for i, r in enumerate(reversed(self._cssRules)):
if r.type == rule.type:
index = len(self._cssRules) - i
break
else:
# find first point to insert
for i, r in enumerate(self._cssRules):
if r.type in (r.MEDIA_RULE,
r.PAGE_RULE,
r.STYLE_RULE,
r.FONT_FACE_RULE,
r.UNKNOWN_RULE,
r.COMMENT):
index = i # before these
break
else:
# after @charset @import @namespace
for r in self._cssRules[index:]:
if r.type in (r.CHARSET_RULE,
r.IMPORT_RULE,
r.NAMESPACE_RULE):
self._log.error(
u'CSSStylesheet: Cannot insert @variables here,'
' found @charset, @import or @namespace after'
' index %s.' %
index,
error=xml.dom.HierarchyRequestErr)
return
# before @media @page @font-face and stylerule
for r in self._cssRules[:index]:
if r.type in (r.MEDIA_RULE,
r.PAGE_RULE,
r.STYLE_RULE,
r.FONT_FACE_RULE):
self._log.error(
u'CSSStylesheet: Cannot insert @variables here,'
' found @media, @page or CSSStyleRule'
' before index %s.' %
index,
error=xml.dom.HierarchyRequestErr)
return
self._cssRules.insert(index, rule)
self._updateVariables()
# all other where order is not important
else:
if inOrder:
# simply add to end as no specific order
self._cssRules.append(rule)
index = len(self._cssRules) - 1
else:
for r in self._cssRules[index:]:
if r.type in (r.CHARSET_RULE,
r.IMPORT_RULE,
r.NAMESPACE_RULE):
self._log.error(
u'CSSStylesheet: Cannot insert rule here, found '
u'@charset, @import or @namespace before index %s.'
% index, error=xml.dom.HierarchyRequestErr)
return
self._cssRules.insert(index, rule)
# post settings
rule._parentStyleSheet = self
if rule.IMPORT_RULE == rule.type and not rule.hrefFound:
# try loading the imported sheet which has new relative href now
rule.href = rule.href
return index
ownerRule = property(lambda self: self._ownerRule,
doc=u'A ref to an @import rule if it is imported, '
u'else ``None``.')
@Deprecated(u'Use ``cssutils.setSerializer(serializer)`` instead.')
def setSerializer(self, cssserializer):
"""Set the cssutils global Serializer used for all output."""
if isinstance(cssserializer, cssutils.CSSSerializer):
cssutils.ser = cssserializer
else:
raise ValueError(u'Serializer must be an instance of '
u'cssutils.CSSSerializer.')
@Deprecated(u'Set pref in ``cssutils.ser.prefs`` instead.')
def setSerializerPref(self, pref, value):
"""Set a Preference of CSSSerializer used for output.
See :class:`cssutils.serialize.Preferences` for possible
preferences to be set.
"""
cssutils.ser.prefs.__setattr__(pref, value)

209
libs/cssutils/css/cssunknownrule.py

@ -1,209 +0,0 @@
"""CSSUnknownRule implements DOM Level 2 CSS CSSUnknownRule."""
__all__ = ['CSSUnknownRule']
__docformat__ = 'restructuredtext'
__version__ = '$Id$'
import cssrule
import cssutils
import xml.dom
class CSSUnknownRule(cssrule.CSSRule):
"""
Represents an at-rule not supported by this user agent, so in
effect all other at-rules not defined in cssutils.
Format::
@xxx until ';' or block {...}
"""
def __init__(self, cssText=u'', parentRule=None,
parentStyleSheet=None, readonly=False):
"""
:param cssText:
of type string
"""
super(CSSUnknownRule, self).__init__(parentRule=parentRule,
parentStyleSheet=parentStyleSheet)
self._atkeyword = None
if cssText:
self.cssText = cssText
self._readonly = readonly
def __repr__(self):
return u"cssutils.css.%s(cssText=%r)" % (
self.__class__.__name__,
self.cssText)
def __str__(self):
return u"<cssutils.css.%s object cssText=%r at 0x%x>" % (
self.__class__.__name__,
self.cssText,
id(self))
def _getCssText(self):
"""Return serialized property cssText."""
return cssutils.ser.do_CSSUnknownRule(self)
def _setCssText(self, cssText):
"""
:exceptions:
- :exc:`~xml.dom.SyntaxErr`:
Raised if the specified CSS string value has a syntax error and
is unparsable.
- :exc:`~xml.dom.InvalidModificationErr`:
Raised if the specified CSS string value represents a different
type of rule than the current one.
- :exc:`~xml.dom.HierarchyRequestErr`:
Raised if the rule cannot be inserted at this point in the
style sheet.
- :exc:`~xml.dom.NoModificationAllowedErr`:
Raised if the rule is readonly.
"""
super(CSSUnknownRule, self)._setCssText(cssText)
tokenizer = self._tokenize2(cssText)
attoken = self._nexttoken(tokenizer, None)
if not attoken or self._type(attoken) != self._prods.ATKEYWORD:
self._log.error(u'CSSUnknownRule: No CSSUnknownRule found: %s' %
self._valuestr(cssText),
error=xml.dom.InvalidModificationErr)
else:
# for closures: must be a mutable
new = {'nesting': [], # {} [] or ()
'wellformed': True
}
def CHAR(expected, seq, token, tokenizer=None):
type_, val, line, col = token
if expected != 'EOF':
if val in u'{[(':
new['nesting'].append(val)
elif val in u'}])':
opening = {u'}': u'{', u']': u'[', u')': u'('}[val]
try:
if new['nesting'][-1] == opening:
new['nesting'].pop()
else:
raise IndexError()
except IndexError:
new['wellformed'] = False
self._log.error(u'CSSUnknownRule: Wrong nesting of '
u'{, [ or (.', token=token)
if val in u'};' and not new['nesting']:
expected = 'EOF'
seq.append(val, type_, line=line, col=col)
return expected
else:
new['wellformed'] = False
self._log.error(u'CSSUnknownRule: Expected end of rule.',
token=token)
return expected
def FUNCTION(expected, seq, token, tokenizer=None):
# handled as opening (
type_, val, line, col = token
val = self._tokenvalue(token)
if expected != 'EOF':
new['nesting'].append(u'(')
seq.append(val, type_, line=line, col=col)
return expected
else:
new['wellformed'] = False
self._log.error(u'CSSUnknownRule: Expected end of rule.',
token=token)
return expected
def EOF(expected, seq, token, tokenizer=None):
"close all blocks and return 'EOF'"
for x in reversed(new['nesting']):
closing = {u'{': u'}', u'[': u']', u'(': u')'}[x]
seq.append(closing, closing)
new['nesting'] = []
return 'EOF'
def INVALID(expected, seq, token, tokenizer=None):
# makes rule invalid
self._log.error(u'CSSUnknownRule: Bad syntax.',
token=token, error=xml.dom.SyntaxErr)
new['wellformed'] = False
return expected
def STRING(expected, seq, token, tokenizer=None):
type_, val, line, col = token
val = self._stringtokenvalue(token)
if expected != 'EOF':
seq.append(val, type_, line=line, col=col)
return expected
else:
new['wellformed'] = False
self._log.error(u'CSSUnknownRule: Expected end of rule.',
token=token)
return expected
def URI(expected, seq, token, tokenizer=None):
type_, val, line, col = token
val = self._uritokenvalue(token)
if expected != 'EOF':
seq.append(val, type_, line=line, col=col)
return expected
else:
new['wellformed'] = False
self._log.error(u'CSSUnknownRule: Expected end of rule.',
token=token)
return expected
def default(expected, seq, token, tokenizer=None):
type_, val, line, col = token
if expected != 'EOF':
seq.append(val, type_, line=line, col=col)
return expected
else:
new['wellformed'] = False
self._log.error(u'CSSUnknownRule: Expected end of rule.',
token=token)
return expected
# unknown : ATKEYWORD S* ... ; | }
newseq = self._tempSeq()
wellformed, expected = self._parse(expected=None,
seq=newseq, tokenizer=tokenizer,
productions={'CHAR': CHAR,
'EOF': EOF,
'FUNCTION': FUNCTION,
'INVALID': INVALID,
'STRING': STRING,
'URI': URI,
'S': default # overwrite default default!
},
default=default,
new=new)
# wellformed set by parse
wellformed = wellformed and new['wellformed']
# post conditions
if expected != 'EOF':
wellformed = False
self._log.error(u'CSSUnknownRule: No ending ";" or "}" found: '
u'%r' % self._valuestr(cssText))
elif new['nesting']:
wellformed = False
self._log.error(u'CSSUnknownRule: Unclosed "{", "[" or "(": %r'
% self._valuestr(cssText))
# set all
if wellformed:
self.atkeyword = self._tokenvalue(attoken)
self._setSeq(newseq)
cssText = property(fget=_getCssText, fset=_setCssText,
doc=u"(DOM) The parsable textual representation.")
type = property(lambda self: self.UNKNOWN_RULE,
doc=u"The type of this rule, as defined by a CSSRule "
u"type constant.")
wellformed = property(lambda self: bool(self.atkeyword))

1251
libs/cssutils/css/cssvalue.py

File diff suppressed because it is too large

330
libs/cssutils/css/cssvariablesdeclaration.py

@ -1,330 +0,0 @@
"""CSSVariablesDeclaration
http://disruptive-innovations.com/zoo/cssvariables/#mozTocId496530
"""
__all__ = ['CSSVariablesDeclaration']
__docformat__ = 'restructuredtext'
__version__ = '$Id: cssstyledeclaration.py 1819 2009-08-01 20:52:43Z cthedot $'
from cssutils.prodparser import *
from cssutils.helper import normalize
from value import PropertyValue
import cssutils
import itertools
import xml.dom
class CSSVariablesDeclaration(cssutils.util._NewBase):
"""The CSSVariablesDeclaration interface represents a single block of
variable declarations.
"""
def __init__(self, cssText=u'', parentRule=None, readonly=False):
"""
:param cssText:
Shortcut, sets CSSVariablesDeclaration.cssText
:param parentRule:
The CSS rule that contains this declaration block or
None if this CSSVariablesDeclaration is not attached to a CSSRule.
:param readonly:
defaults to False
Format::
variableset
: vardeclaration [ ';' S* vardeclaration ]* S*
;
vardeclaration
: varname ':' S* term
;
varname
: IDENT S*
;
"""
super(CSSVariablesDeclaration, self).__init__()
self._parentRule = parentRule
self._vars = {}
if cssText:
self.cssText = cssText
self._readonly = readonly
def __repr__(self):
return u"cssutils.css.%s(cssText=%r)" % (self.__class__.__name__,
self.cssText)
def __str__(self):
return u"<cssutils.css.%s object length=%r at 0x%x>" % (
self.__class__.__name__,
self.length,
id(self))
def __contains__(self, variableName):
"""Check if a variable is in variable declaration block.
:param variableName:
a string
"""
return normalize(variableName) in self.keys()
def __getitem__(self, variableName):
"""Retrieve the value of variable ``variableName`` from this
declaration.
"""
return self.getVariableValue(variableName)
def __setitem__(self, variableName, value):
self.setVariable(variableName, value)
def __delitem__(self, variableName):
return self.removeVariable(variableName)
def __iter__(self):
"""Iterator of names of set variables."""
for name in self.keys():
yield name
def keys(self):
"""Analoguous to standard dict returns variable names which are set in
this declaration."""
return self._vars.keys()
def _getCssText(self):
"""Return serialized property cssText."""
return cssutils.ser.do_css_CSSVariablesDeclaration(self)
def _setCssText(self, cssText):
"""Setting this attribute will result in the parsing of the new value
and resetting of all the properties in the declaration block
including the removal or addition of properties.
:exceptions:
- :exc:`~xml.dom.NoModificationAllowedErr`:
Raised if this declaration is readonly or a property is readonly.
- :exc:`~xml.dom.SyntaxErr`:
Raised if the specified CSS string value has a syntax error and
is unparsable.
Format::
variableset
: vardeclaration [ ';' S* vardeclaration ]*
;
vardeclaration
: varname ':' S* term
;
varname
: IDENT S*
;
expr
: [ VARCALL | term ] [ operator [ VARCALL | term ] ]*
;
"""
self._checkReadonly()
vardeclaration = Sequence(
PreDef.ident(),
PreDef.char(u':', u':', toSeq=False),
#PreDef.S(toSeq=False, optional=True),
Prod(name=u'term', match=lambda t, v: True,
toSeq=lambda t, tokens: (u'value',
PropertyValue(itertools.chain([t],
tokens),
parent=self)
)
)
)
prods = Sequence(vardeclaration,
Sequence(PreDef.S(optional=True),
PreDef.char(u';', u';', toSeq=False),
PreDef.S(optional=True),
vardeclaration,
minmax=lambda: (0, None)),
PreDef.S(optional=True),
PreDef.char(u';', u';', toSeq=False, optional=True)
)
# parse
wellformed, seq, store, notused = \
ProdParser().parse(cssText,
u'CSSVariableDeclaration',
prods)
if wellformed:
newseq = self._tempSeq()
newvars = {}
# seq contains only name: value pairs plus comments etc
nameitem = None
for item in seq:
if u'IDENT' == item.type:
nameitem = item
elif u'value' == item.type:
nname = normalize(nameitem.value)
if nname in newvars:
# replace var with same name
for i, it in enumerate(newseq):
if normalize(it.value[0]) == nname:
newseq.replace(i,
(nameitem.value, item.value),
'var',
nameitem.line, nameitem.col)
else:
# saved non normalized name for reserialization
newseq.append((nameitem.value, item.value),
'var',
nameitem.line, nameitem.col)
# newseq.append((nameitem.value, item.value),
# 'var',
# nameitem.line, nameitem.col)
newvars[nname] = item.value
else:
newseq.appendItem(item)
self._setSeq(newseq)
self._vars = newvars
self.wellformed = True
cssText = property(_getCssText, _setCssText,
doc=u"(DOM) A parsable textual representation of the declaration "
u"block excluding the surrounding curly braces.")
def _setParentRule(self, parentRule):
self._parentRule = parentRule
parentRule = property(lambda self: self._parentRule, _setParentRule,
doc=u"(DOM) The CSS rule that contains this"
u" declaration block or None if this block"
u" is not attached to a CSSRule.")
def getVariableValue(self, variableName):
"""Used to retrieve the value of a variable if it has been explicitly
set within this variable declaration block.
:param variableName:
The name of the variable.
:returns:
the value of the variable if it has been explicitly set in this
variable declaration block. Returns the empty string if the
variable has not been set.
"""
try:
return self._vars[normalize(variableName)].cssText
except KeyError, e:
return u''
def removeVariable(self, variableName):
"""Used to remove a variable if it has been explicitly set within this
variable declaration block.
:param variableName:
The name of the variable.
:returns:
the value of the variable if it has been explicitly set for this
variable declaration block. Returns the empty string if the
variable has not been set.
:exceptions:
- :exc:`~xml.dom.NoModificationAllowedErr`:
Raised if this declaration is readonly is readonly.
"""
normalname = variableName
try:
r = self._vars[normalname]
except KeyError, e:
return u''
else:
self.seq._readonly = False
if normalname in self._vars:
for i, x in enumerate(self.seq):
if x.value[0] == variableName:
del self.seq[i]
self.seq._readonly = True
del self._vars[normalname]
return r.cssText
def setVariable(self, variableName, value):
"""Used to set a variable value within this variable declaration block.
:param variableName:
The name of the CSS variable.
:param value:
The new value of the variable, may also be a PropertyValue object.
:exceptions:
- :exc:`~xml.dom.SyntaxErr`:
Raised if the specified value has a syntax error and is
unparsable.
- :exc:`~xml.dom.NoModificationAllowedErr`:
Raised if this declaration is readonly or the property is
readonly.
"""
self._checkReadonly()
# check name
wellformed, seq, store, unused = \
ProdParser().parse(normalize(variableName),
u'variableName',
Sequence(PreDef.ident()))
if not wellformed:
self._log.error(u'Invalid variableName: %r: %r'
% (variableName, value))
else:
# check value
if isinstance(value, PropertyValue):
v = value
else:
v = PropertyValue(cssText=value, parent=self)
if not v.wellformed:
self._log.error(u'Invalid variable value: %r: %r'
% (variableName, value))
else:
# update seq
self.seq._readonly = False
variableName = normalize(variableName)
if variableName in self._vars:
for i, x in enumerate(self.seq):
if x.value[0] == variableName:
self.seq.replace(i,
[variableName, v],
x.type,
x.line,
x.col)
break
else:
self.seq.append([variableName, v], 'var')
self.seq._readonly = True
self._vars[variableName] = v
def item(self, index):
"""Used to retrieve the variables that have been explicitly set in
this variable declaration block. The order of the variables
retrieved using this method does not have to be the order in which
they were set. This method can be used to iterate over all variables
in this variable declaration block.
:param index:
of the variable name to retrieve, negative values behave like
negative indexes on Python lists, so -1 is the last element
:returns:
The name of the variable at this ordinal position. The empty
string if no variable exists at this position.
"""
try:
return self.keys()[index]
except IndexError:
return u''
length = property(lambda self: len(self._vars),
doc=u"The number of variables that have been explicitly set in this"
u" variable declaration block. The range of valid indices is 0"
u" to length-1 inclusive.")

198
libs/cssutils/css/cssvariablesrule.py

@ -1,198 +0,0 @@
"""CSSVariables implements (and only partly) experimental
`CSS Variables <http://disruptive-innovations.com/zoo/cssvariables/>`_
"""
__all__ = ['CSSVariablesRule']
__docformat__ = 'restructuredtext'
__version__ = '$Id: cssfontfacerule.py 1818 2009-07-30 21:39:00Z cthedot $'
from cssvariablesdeclaration import CSSVariablesDeclaration
import cssrule
import cssutils
import xml.dom
class CSSVariablesRule(cssrule.CSSRule):
"""
The CSSVariablesRule interface represents a @variables rule within a CSS
style sheet. The @variables rule is used to specify variables.
cssutils uses a :class:`~cssutils.css.CSSVariablesDeclaration` to
represent the variables.
Format::
variables
VARIABLES_SYM S* medium [ COMMA S* medium ]* LBRACE S*
variableset* '}' S*
;
for variableset see :class:`cssutils.css.CSSVariablesDeclaration`
**Media are not implemented. Reason is that cssutils is using CSS
variables in a kind of preprocessing and therefor no media information
is available at this stage. For now do not use media!**
Example::
@variables {
CorporateLogoBGColor: #fe8d12;
}
div.logoContainer {
background-color: var(CorporateLogoBGColor);
}
"""
def __init__(self, mediaText=None, variables=None, parentRule=None,
parentStyleSheet=None, readonly=False):
"""
If readonly allows setting of properties in constructor only.
"""
super(CSSVariablesRule, self).__init__(parentRule=parentRule,
parentStyleSheet=parentStyleSheet)
self._atkeyword = u'@variables'
# dummy
self._media = cssutils.stylesheets.MediaList(mediaText,
readonly=readonly)
if variables:
self.variables = variables
else:
self.variables = CSSVariablesDeclaration(parentRule=self)
self._readonly = readonly
def __repr__(self):
return u"cssutils.css.%s(mediaText=%r, variables=%r)" % (
self.__class__.__name__,
self._media.mediaText,
self.variables.cssText)
def __str__(self):
return u"<cssutils.css.%s object mediaText=%r variables=%r valid=%r " \
u"at 0x%x>" % (self.__class__.__name__,
self._media.mediaText,
self.variables.cssText,
self.valid,
id(self))
def _getCssText(self):
"""Return serialized property cssText."""
return cssutils.ser.do_CSSVariablesRule(self)
def _setCssText(self, cssText):
"""
:exceptions:
- :exc:`~xml.dom.SyntaxErr`:
Raised if the specified CSS string value has a syntax error and
is unparsable.
- :exc:`~xml.dom.InvalidModificationErr`:
Raised if the specified CSS string value represents a different
type of rule than the current one.
- :exc:`~xml.dom.HierarchyRequestErr`:
Raised if the rule cannot be inserted at this point in the
style sheet.
- :exc:`~xml.dom.NoModificationAllowedErr`:
Raised if the rule is readonly.
Format::
variables
: VARIABLES_SYM S* medium [ COMMA S* medium ]* LBRACE S*
variableset* '}' S*
;
variableset
: LBRACE S* vardeclaration [ ';' S* vardeclaration ]* '}' S*
;
"""
super(CSSVariablesRule, self)._setCssText(cssText)
tokenizer = self._tokenize2(cssText)
attoken = self._nexttoken(tokenizer, None)
if self._type(attoken) != self._prods.VARIABLES_SYM:
self._log.error(u'CSSVariablesRule: No CSSVariablesRule found: %s' %
self._valuestr(cssText),
error=xml.dom.InvalidModificationErr)
else:
newVariables = CSSVariablesDeclaration(parentRule=self)
ok = True
beforetokens, brace = self._tokensupto2(tokenizer,
blockstartonly=True,
separateEnd=True)
if self._tokenvalue(brace) != u'{':
ok = False
self._log.error(u'CSSVariablesRule: No start { of variable '
u'declaration found: %r'
% self._valuestr(cssText), brace)
# parse stuff before { which should be comments and S only
new = {'wellformed': True}
newseq = self._tempSeq()#[]
beforewellformed, expected = self._parse(expected=':',
seq=newseq, tokenizer=self._tokenize2(beforetokens),
productions={})
ok = ok and beforewellformed and new['wellformed']
variablestokens, braceorEOFtoken = self._tokensupto2(tokenizer,
blockendonly=True,
separateEnd=True)
val, type_ = self._tokenvalue(braceorEOFtoken), \
self._type(braceorEOFtoken)
if val != u'}' and type_ != 'EOF':
ok = False
self._log.error(u'CSSVariablesRule: No "}" after variables '
u'declaration found: %r'
% self._valuestr(cssText))
nonetoken = self._nexttoken(tokenizer)
if nonetoken:
ok = False
self._log.error(u'CSSVariablesRule: Trailing content found.',
token=nonetoken)
if 'EOF' == type_:
# add again as variables needs it
variablestokens.append(braceorEOFtoken)
# SET but may raise:
newVariables.cssText = variablestokens
if ok:
# contains probably comments only upto {
self._setSeq(newseq)
self.variables = newVariables
cssText = property(_getCssText, _setCssText,
doc=u"(DOM) The parsable textual representation of this "
u"rule.")
media = property(doc=u"NOT IMPLEMENTED! As cssutils resolves variables "\
u"during serializing media information is lost.")
def _setVariables(self, variables):
"""
:param variables:
a CSSVariablesDeclaration or string
"""
self._checkReadonly()
if isinstance(variables, basestring):
self._variables = CSSVariablesDeclaration(cssText=variables,
parentRule=self)
else:
variables._parentRule = self
self._variables = variables
variables = property(lambda self: self._variables, _setVariables,
doc=u"(DOM) The variables of this rule set, a "
u":class:`cssutils.css.CSSVariablesDeclaration`.")
type = property(lambda self: self.VARIABLES_RULE,
doc=u"The type of this rule, as defined by a CSSRule "
u"type constant.")
valid = property(lambda self: True, doc='NOT IMPLEMTED REALLY (TODO)')
# constant but needed:
wellformed = property(lambda self: True)

215
libs/cssutils/css/marginrule.py

@ -1,215 +0,0 @@
"""MarginRule implements DOM Level 2 CSS MarginRule."""
__all__ = ['MarginRule']
__docformat__ = 'restructuredtext'
__version__ = '$Id$'
from cssutils.prodparser import *
from cssstyledeclaration import CSSStyleDeclaration
import cssrule
import cssutils
import xml.dom
class MarginRule(cssrule.CSSRule):
"""
A margin at-rule consists of an ATKEYWORD that identifies the margin box
(e.g. '@top-left') and a block of declarations (said to be in the margin
context).
Format::
margin :
margin_sym S* '{' declaration [ ';' S* declaration? ]* '}' S*
;
margin_sym :
TOPLEFTCORNER_SYM |
TOPLEFT_SYM |
TOPCENTER_SYM |
TOPRIGHT_SYM |
TOPRIGHTCORNER_SYM |
BOTTOMLEFTCORNER_SYM |
BOTTOMLEFT_SYM |
BOTTOMCENTER_SYM |
BOTTOMRIGHT_SYM |
BOTTOMRIGHTCORNER_SYM |
LEFTTOP_SYM |
LEFTMIDDLE_SYM |
LEFTBOTTOM_SYM |
RIGHTTOP_SYM |
RIGHTMIDDLE_SYM |
RIGHTBOTTOM_SYM
;
e.g.::
@top-left {
content: "123";
}
"""
margins = ['@top-left-corner',
'@top-left',
'@top-center',
'@top-right',
'@top-right-corner',
'@bottom-left-corner',
'@bottom-left',
'@bottom-center',
'@bottom-right',
'@bottom-right-corner',
'@left-top',
'@left-middle',
'@left-bottom',
'@right-top',
'@right-middle',
'@right-bottom'
]
def __init__(self, margin=None, style=None, parentRule=None,
parentStyleSheet=None, readonly=False):
"""
:param atkeyword:
The margin area, e.g. '@top-left' for this rule
:param style:
CSSStyleDeclaration for this MarginRule
"""
super(MarginRule, self).__init__(parentRule=parentRule,
parentStyleSheet=parentStyleSheet)
self._atkeyword = self._keyword = None
if margin:
self.margin = margin
if style:
self.style = style
else:
self.style = CSSStyleDeclaration(parentRule=self)
self._readonly = readonly
def _setMargin(self, margin):
"""Check if new keyword fits the rule it is used for."""
n = self._normalize(margin)
if n not in MarginRule.margins:
self._log.error(u'Invalid margin @keyword for this %s rule: %r' %
(self.margin, margin),
error=xml.dom.InvalidModificationErr)
else:
self._atkeyword = n
self._keyword = margin
margin = property(lambda self: self._atkeyword, _setMargin,
doc=u"Margin area of parent CSSPageRule. "
u"`margin` and `atkeyword` are both normalized "
u"@keyword of the @rule.")
atkeyword = margin
def __repr__(self):
return u"cssutils.css.%s(margin=%r, style=%r)" % (self.__class__.__name__,
self.margin,
self.style.cssText)
def __str__(self):
return u"<cssutils.css.%s object margin=%r style=%r "\
u"at 0x%x>" % (self.__class__.__name__,
self.margin,
self.style.cssText,
id(self))
def _getCssText(self):
"""Return serialized property cssText."""
return cssutils.ser.do_MarginRule(self)
def _setCssText(self, cssText):
"""
:exceptions:
- :exc:`~xml.dom.SyntaxErr`:
Raised if the specified CSS string value has a syntax error and
is unparsable.
- :exc:`~xml.dom.InvalidModificationErr`:
Raised if the specified CSS string value represents a different
type of rule than the current one.
- :exc:`~xml.dom.HierarchyRequestErr`:
Raised if the rule cannot be inserted at this point in the
style sheet.
- :exc:`~xml.dom.NoModificationAllowedErr`:
Raised if the rule is readonly.
"""
super(MarginRule, self)._setCssText(cssText)
# TEMP: all style tokens are saved in store to fill styledeclaration
# TODO: resolve when all generators
styletokens = Prod(name='styletokens',
match=lambda t, v: v != u'}',
#toSeq=False,
toStore='styletokens',
storeToken=True
)
prods = Sequence(Prod(name='@ margin',
match=lambda t, v:
t == 'ATKEYWORD' and
self._normalize(v) in MarginRule.margins,
toStore='margin'
# TODO?
#, exception=xml.dom.InvalidModificationErr
),
PreDef.char('OPEN', u'{'),
Sequence(Choice(PreDef.unknownrule(toStore='@'),
styletokens),
minmax=lambda: (0, None)
),
PreDef.char('CLOSE', u'}', stopAndKeep=True)
)
# parse
ok, seq, store, unused = ProdParser().parse(cssText,
u'MarginRule',
prods)
if ok:
# TODO: use seq for serializing instead of fixed stuff?
self._setSeq(seq)
if 'margin' in store:
# may raise:
self.margin = store['margin'].value
else:
self._log.error(u'No margin @keyword for this %s rule' %
self.margin,
error=xml.dom.InvalidModificationErr)
# new empty style
self.style = CSSStyleDeclaration(parentRule=self)
if 'styletokens' in store:
# may raise:
self.style.cssText = store['styletokens']
cssText = property(fget=_getCssText, fset=_setCssText,
doc=u"(DOM) The parsable textual representation.")
def _setStyle(self, style):
"""
:param style: A string or CSSStyleDeclaration which replaces the
current style object.
"""
self._checkReadonly()
if isinstance(style, basestring):
self._style = CSSStyleDeclaration(cssText=style, parentRule=self)
else:
style._parentRule = self
self._style = style
style = property(lambda self: self._style, _setStyle,
doc=u"(DOM) The declaration-block of this rule set.")
type = property(lambda self: self.MARGIN_RULE,
doc=u"The type of this rule, as defined by a CSSRule "
u"type constant.")
wellformed = property(lambda self: bool(self.atkeyword))

510
libs/cssutils/css/property.py

@ -1,510 +0,0 @@
"""Property is a single CSS property in a CSSStyleDeclaration."""
__all__ = ['Property']
__docformat__ = 'restructuredtext'
__version__ = '$Id$'
from cssutils.helper import Deprecated
from value import PropertyValue
import cssutils
import xml.dom
class Property(cssutils.util.Base):
"""A CSS property in a StyleDeclaration of a CSSStyleRule (cssutils).
Format::
property = name
: IDENT S*
;
expr = value
: term [ operator term ]*
;
term
: unary_operator?
[ NUMBER S* | PERCENTAGE S* | LENGTH S* | EMS S* | EXS S* |
ANGLE S* | TIME S* | FREQ S* | function ]
| STRING S* | IDENT S* | URI S* | hexcolor
;
function
: FUNCTION S* expr ')' S*
;
/*
* There is a constraint on the color that it must
* have either 3 or 6 hex-digits (i.e., [0-9a-fA-F])
* after the "#"; e.g., "#000" is OK, but "#abcd" is not.
*/
hexcolor
: HASH S*
;
prio
: IMPORTANT_SYM S*
;
"""
def __init__(self, name=None, value=None, priority=u'',
_mediaQuery=False, parent=None):
"""
:param name:
a property name string (will be normalized)
:param value:
a property value string
:param priority:
an optional priority string which currently must be u'',
u'!important' or u'important'
:param _mediaQuery:
if ``True`` value is optional (used by MediaQuery)
:param parent:
the parent object, normally a
:class:`cssutils.css.CSSStyleDeclaration`
"""
super(Property, self).__init__()
self.seqs = [[], None, []]
self.wellformed = False
self._mediaQuery = _mediaQuery
self.parent = parent
self.__nametoken = None
self._name = u''
self._literalname = u''
self.seqs[1] = PropertyValue(parent=self)
if name:
self.name = name
self.propertyValue = value
self._priority = u''
self._literalpriority = u''
if priority:
self.priority = priority
def __repr__(self):
return u"cssutils.css.%s(name=%r, value=%r, priority=%r)" % (
self.__class__.__name__,
self.literalname,
self.propertyValue.cssText,
self.priority)
def __str__(self):
return u"<%s.%s object name=%r value=%r priority=%r valid=%r at 0x%x>" \
% (self.__class__.__module__,
self.__class__.__name__,
self.name,
self.propertyValue.cssText,
self.priority,
self.valid,
id(self))
def _isValidating(self):
"""Return True if validation is enabled."""
try:
return self.parent.validating
except AttributeError:
# default (no parent)
return True
def _getCssText(self):
"""Return serialized property cssText."""
return cssutils.ser.do_Property(self)
def _setCssText(self, cssText):
"""
:exceptions:
- :exc:`~xml.dom.SyntaxErr`:
Raised if the specified CSS string value has a syntax error and
is unparsable.
- :exc:`~xml.dom.NoModificationAllowedErr`:
Raised if the rule is readonly.
"""
# check and prepare tokenlists for setting
tokenizer = self._tokenize2(cssText)
nametokens = self._tokensupto2(tokenizer, propertynameendonly=True)
if nametokens:
wellformed = True
valuetokens = self._tokensupto2(tokenizer,
propertyvalueendonly=True)
prioritytokens = self._tokensupto2(tokenizer,
propertypriorityendonly=True)
if self._mediaQuery and not valuetokens:
# MediaQuery may consist of name only
self.name = nametokens
self.propertyValue = None
self.priority = None
return
# remove colon from nametokens
colontoken = nametokens.pop()
if self._tokenvalue(colontoken) != u':':
wellformed = False
self._log.error(u'Property: No ":" after name found: %s' %
self._valuestr(cssText), colontoken)
elif not nametokens:
wellformed = False
self._log.error(u'Property: No property name found: %s' %
self._valuestr(cssText), colontoken)
if valuetokens:
if self._tokenvalue(valuetokens[-1]) == u'!':
# priority given, move "!" to prioritytokens
prioritytokens.insert(0, valuetokens.pop(-1))
else:
wellformed = False
self._log.error(u'Property: No property value found: %s' %
self._valuestr(cssText), colontoken)
if wellformed:
self.wellformed = True
self.name = nametokens
self.propertyValue = valuetokens
self.priority = prioritytokens
# also invalid values are set!
if self._isValidating():
self.validate()
else:
self._log.error(u'Property: No property name found: %s' %
self._valuestr(cssText))
cssText = property(fget=_getCssText, fset=_setCssText,
doc="A parsable textual representation.")
def _setName(self, name):
"""
:exceptions:
- :exc:`~xml.dom.SyntaxErr`:
Raised if the specified name has a syntax error and is
unparsable.
"""
# for closures: must be a mutable
new = {'literalname': None,
'wellformed': True}
def _ident(expected, seq, token, tokenizer=None):
# name
if 'name' == expected:
new['literalname'] = self._tokenvalue(token).lower()
seq.append(new['literalname'])
return 'EOF'
else:
new['wellformed'] = False
self._log.error(u'Property: Unexpected ident.', token)
return expected
newseq = []
wellformed, expected = self._parse(expected='name',
seq=newseq,
tokenizer=self._tokenize2(name),
productions={'IDENT': _ident})
wellformed = wellformed and new['wellformed']
# post conditions
# define a token for error logging
if isinstance(name, list):
token = name[0]
self.__nametoken = token
else:
token = None
if not new['literalname']:
wellformed = False
self._log.error(u'Property: No name found: %s' %
self._valuestr(name), token=token)
if wellformed:
self.wellformed = True
self._literalname = new['literalname']
self._name = self._normalize(self._literalname)
self.seqs[0] = newseq
# validate
if self._isValidating() and self._name not in cssutils.profile.knownNames:
# self.valid = False
self._log.warn(u'Property: Unknown Property name.',
token=token, neverraise=True)
else:
pass
# self.valid = True
# if self.propertyValue:
# self.propertyValue._propertyName = self._name
# #self.valid = self.propertyValue.valid
else:
self.wellformed = False
name = property(lambda self: self._name, _setName,
doc="Name of this property.")
literalname = property(lambda self: self._literalname,
doc="Readonly literal (not normalized) name "
"of this property")
def _setPropertyValue(self, cssText):
"""
See css.PropertyValue
:exceptions:
- :exc:`~xml.dom.SyntaxErr`:
Raised if the specified CSS string value has a syntax error
(according to the attached property) or is unparsable.
- :exc:`~xml.dom.InvalidModificationErr`:
TODO: Raised if the specified CSS string value represents a different
type of values than the values allowed by the CSS property.
"""
if self._mediaQuery and not cssText:
self.seqs[1] = PropertyValue(parent=self)
else:
self.seqs[1].cssText = cssText
self.wellformed = self.wellformed and self.seqs[1].wellformed
propertyValue = property(lambda self: self.seqs[1],
_setPropertyValue,
doc=u"(cssutils) PropertyValue object of property")
def _getValue(self):
if self.propertyValue:
# value without comments
return self.propertyValue.value
else:
return u''
def _setValue(self, value):
self._setPropertyValue(value)
value = property(_getValue, _setValue,
doc="The textual value of this Properties propertyValue.")
def _setPriority(self, priority):
"""
priority
a string, currently either u'', u'!important' or u'important'
Format::
prio
: IMPORTANT_SYM S*
;
"!"{w}"important" {return IMPORTANT_SYM;}
:exceptions:
- :exc:`~xml.dom.SyntaxErr`:
Raised if the specified priority has a syntax error and is
unparsable.
In this case a priority not equal to None, "" or "!{w}important".
As CSSOM defines CSSStyleDeclaration.getPropertyPriority resulting
in u'important' this value is also allowed to set a Properties
priority
"""
if self._mediaQuery:
self._priority = u''
self._literalpriority = u''
if priority:
self._log.error(u'Property: No priority in a MediaQuery - '
u'ignored.')
return
if isinstance(priority, basestring) and\
u'important' == self._normalize(priority):
priority = u'!%s' % priority
# for closures: must be a mutable
new = {'literalpriority': u'',
'wellformed': True}
def _char(expected, seq, token, tokenizer=None):
# "!"
val = self._tokenvalue(token)
if u'!' == expected == val:
seq.append(val)
return 'important'
else:
new['wellformed'] = False
self._log.error(u'Property: Unexpected char.', token)
return expected
def _ident(expected, seq, token, tokenizer=None):
# "important"
val = self._tokenvalue(token)
if 'important' == expected:
new['literalpriority'] = val
seq.append(val)
return 'EOF'
else:
new['wellformed'] = False
self._log.error(u'Property: Unexpected ident.', token)
return expected
newseq = []
wellformed, expected = self._parse(expected='!',
seq=newseq,
tokenizer=self._tokenize2(priority),
productions={'CHAR': _char,
'IDENT': _ident})
wellformed = wellformed and new['wellformed']
# post conditions
if priority and not new['literalpriority']:
wellformed = False
self._log.info(u'Property: Invalid priority: %s' %
self._valuestr(priority))
if wellformed:
self.wellformed = self.wellformed and wellformed
self._literalpriority = new['literalpriority']
self._priority = self._normalize(self.literalpriority)
self.seqs[2] = newseq
# validate priority
if self._priority not in (u'', u'important'):
self._log.error(u'Property: No CSS priority value: %s' %
self._priority)
priority = property(lambda self: self._priority, _setPriority,
doc="Priority of this property.")
literalpriority = property(lambda self: self._literalpriority,
doc="Readonly literal (not normalized) priority of this property")
def _setParent(self, parent):
self._parent = parent
parent = property(lambda self: self._parent, _setParent,
doc="The Parent Node (normally a CSSStyledeclaration) of this "
"Property")
def validate(self):
"""Validate value against `profiles` which are checked dynamically.
properties in e.g. @font-face rules are checked against
``cssutils.profile.CSS3_FONT_FACE`` only.
For each of the following cases a message is reported:
- INVALID (so the property is known but not valid)
``ERROR Property: Invalid value for "{PROFILE-1[/PROFILE-2...]"
property: ...``
- VALID but not in given profiles or defaultProfiles
``WARNING Property: Not valid for profile "{PROFILE-X}" but valid
"{PROFILE-Y}" property: ...``
- VALID in current profile
``DEBUG Found valid "{PROFILE-1[/PROFILE-2...]" property...``
- UNKNOWN property
``WARNING Unknown Property name...`` is issued
so for example::
cssutils.log.setLevel(logging.DEBUG)
parser = cssutils.CSSParser()
s = parser.parseString('''body {
unknown-property: x;
color: 4;
color: rgba(1,2,3,4);
color: red
}''')
# Log output:
WARNING Property: Unknown Property name. [2:9: unknown-property]
ERROR Property: Invalid value for "CSS Color Module Level 3/CSS Level 2.1" property: 4 [3:9: color]
DEBUG Property: Found valid "CSS Color Module Level 3" value: rgba(1, 2, 3, 4) [4:9: color]
DEBUG Property: Found valid "CSS Level 2.1" value: red [5:9: color]
and when setting an explicit default profile::
cssutils.profile.defaultProfiles = cssutils.profile.CSS_LEVEL_2
s = parser.parseString('''body {
unknown-property: x;
color: 4;
color: rgba(1,2,3,4);
color: red
}''')
# Log output:
WARNING Property: Unknown Property name. [2:9: unknown-property]
ERROR Property: Invalid value for "CSS Color Module Level 3/CSS Level 2.1" property: 4 [3:9: color]
WARNING Property: Not valid for profile "CSS Level 2.1" but valid "CSS Color Module Level 3" value: rgba(1, 2, 3, 4) [4:9: color]
DEBUG Property: Found valid "CSS Level 2.1" value: red [5:9: color]
"""
valid = False
profiles = None
try:
# if @font-face use that profile
rule = self.parent.parentRule
except AttributeError:
pass
else:
if rule is not None:
if rule.type == rule.FONT_FACE_RULE:
profiles = [cssutils.profile.CSS3_FONT_FACE]
#TODO: same for @page
if self.name and self.value:
cv = self.propertyValue
# TODO
# if cv.cssValueType == cv.CSS_VARIABLE and not cv.value:
# # TODO: false alarms too!
# cssutils.log.warn(u'No value for variable "%s" found, keeping '
# u'variable.' % cv.name, neverraise=True)
if self.name in cssutils.profile.knownNames:
# add valid, matching, validprofiles...
valid, matching, validprofiles = \
cssutils.profile.validateWithProfile(self.name,
self.value,
profiles)
if not valid:
self._log.error(u'Property: Invalid value for '
u'"%s" property: %s'
% (u'/'.join(validprofiles), self.value),
token=self.__nametoken,
neverraise=True)
# TODO: remove logic to profiles!
elif valid and not matching:#(profiles and profiles not in validprofiles):
if not profiles:
notvalidprofiles = u'/'.join(cssutils.profile.defaultProfiles)
else:
notvalidprofiles = profiles
self._log.warn(u'Property: Not valid for profile "%s" '
u'but valid "%s" value: %s '
% (notvalidprofiles, u'/'.join(validprofiles),
self.value),
token = self.__nametoken,
neverraise=True)
valid = False
elif valid:
self._log.debug(u'Property: Found valid "%s" value: %s'
% (u'/'.join(validprofiles), self.value),
token = self.__nametoken,
neverraise=True)
if self._priority not in (u'', u'important'):
valid = False
return valid
valid = property(validate, doc=u"Check if value of this property is valid "
u"in the properties context.")
@Deprecated(u'Use ``property.propertyValue`` instead.')
def _getCSSValue(self):
return self.propertyValue
@Deprecated(u'Use ``property.propertyValue`` instead.')
def _setCSSValue(self, cssText):
self._setPropertyValue(cssText)
cssValue = property(_getCSSValue, _setCSSValue,
doc="(DEPRECATED) Use ``property.propertyValue`` instead.")

813
libs/cssutils/css/selector.py

@ -1,813 +0,0 @@
"""Selector is a single Selector of a CSSStyleRule SelectorList.
Partly implements http://www.w3.org/TR/css3-selectors/.
TODO
- .contains(selector)
- .isSubselector(selector)
"""
__all__ = ['Selector']
__docformat__ = 'restructuredtext'
__version__ = '$Id$'
from cssutils.helper import Deprecated
from cssutils.util import _SimpleNamespaces
import cssutils
import xml.dom
class Selector(cssutils.util.Base2):
"""
(cssutils) a single selector in a :class:`~cssutils.css.SelectorList`
of a :class:`~cssutils.css.CSSStyleRule`.
Format::
# implemented in SelectorList
selectors_group
: selector [ COMMA S* selector ]*
;
selector
: simple_selector_sequence [ combinator simple_selector_sequence ]*
;
combinator
/* combinators can be surrounded by white space */
: PLUS S* | GREATER S* | TILDE S* | S+
;
simple_selector_sequence
: [ type_selector | universal ]
[ HASH | class | attrib | pseudo | negation ]*
| [ HASH | class | attrib | pseudo | negation ]+
;
type_selector
: [ namespace_prefix ]? element_name
;
namespace_prefix
: [ IDENT | '*' ]? '|'
;
element_name
: IDENT
;
universal
: [ namespace_prefix ]? '*'
;
class
: '.' IDENT
;
attrib
: '[' S* [ namespace_prefix ]? IDENT S*
[ [ PREFIXMATCH |
SUFFIXMATCH |
SUBSTRINGMATCH |
'=' |
INCLUDES |
DASHMATCH ] S* [ IDENT | STRING ] S*
]? ']'
;
pseudo
/* '::' starts a pseudo-element, ':' a pseudo-class */
/* Exceptions: :first-line, :first-letter, :before and :after. */
/* Note that pseudo-elements are restricted to one per selector and */
/* occur only in the last simple_selector_sequence. */
: ':' ':'? [ IDENT | functional_pseudo ]
;
functional_pseudo
: FUNCTION S* expression ')'
;
expression
/* In CSS3, the expressions are identifiers, strings, */
/* or of the form "an+b" */
: [ [ PLUS | '-' | DIMENSION | NUMBER | STRING | IDENT ] S* ]+
;
negation
: NOT S* negation_arg S* ')'
;
negation_arg
: type_selector | universal | HASH | class | attrib | pseudo
;
"""
def __init__(self, selectorText=None, parent=None,
readonly=False):
"""
:Parameters:
selectorText
initial value of this selector
parent
a SelectorList
readonly
default to False
"""
super(Selector, self).__init__()
self.__namespaces = _SimpleNamespaces(log=self._log)
self._element = None
self._parent = parent
self._specificity = (0, 0, 0, 0)
if selectorText:
self.selectorText = selectorText
self._readonly = readonly
def __repr__(self):
if self.__getNamespaces():
st = (self.selectorText, self._getUsedNamespaces())
else:
st = self.selectorText
return u"cssutils.css.%s(selectorText=%r)" % (self.__class__.__name__,
st)
def __str__(self):
return u"<cssutils.css.%s object selectorText=%r specificity=%r" \
u" _namespaces=%r at 0x%x>" % (self.__class__.__name__,
self.selectorText,
self.specificity,
self._getUsedNamespaces(),
id(self))
def _getUsedUris(self):
"Return list of actually used URIs in this Selector."
uris = set()
for item in self.seq:
type_, val = item.type, item.value
if type_.endswith(u'-selector') or type_ == u'universal' and \
isinstance(val, tuple) and val[0] not in (None, u'*'):
uris.add(val[0])
return uris
def _getUsedNamespaces(self):
"Return actually used namespaces only."
useduris = self._getUsedUris()
namespaces = _SimpleNamespaces(log=self._log)
for p, uri in self._namespaces.items():
if uri in useduris:
namespaces[p] = uri
return namespaces
def __getNamespaces(self):
"Use own namespaces if not attached to a sheet, else the sheet's ones."
try:
return self._parent.parentRule.parentStyleSheet.namespaces
except AttributeError:
return self.__namespaces
_namespaces = property(__getNamespaces,
doc=u"If this Selector is attached to a "
u"CSSStyleSheet the namespaces of that sheet "
u"are mirrored here. While the Selector (or "
u"parent SelectorList or parentRule(s) of that "
u"are not attached a own dict of {prefix: "
u"namespaceURI} is used.")
element = property(lambda self: self._element,
doc=u"Effective element target of this selector.")
parent = property(lambda self: self._parent,
doc=u"(DOM) The SelectorList that contains this Selector "
u"or None if this Selector is not attached to a "
u"SelectorList.")
def _getSelectorText(self):
"""Return serialized format."""
return cssutils.ser.do_css_Selector(self)
def _setSelectorText(self, selectorText):
"""
:param selectorText:
parsable string or a tuple of (selectorText, dict-of-namespaces).
Given namespaces are ignored if this object is attached to a
CSSStyleSheet!
:exceptions:
- :exc:`~xml.dom.NamespaceErr`:
Raised if the specified selector uses an unknown namespace
prefix.
- :exc:`~xml.dom.SyntaxErr`:
Raised if the specified CSS string value has a syntax error
and is unparsable.
- :exc:`~xml.dom.NoModificationAllowedErr`:
Raised if this rule is readonly.
"""
self._checkReadonly()
# might be (selectorText, namespaces)
selectorText, namespaces = self._splitNamespacesOff(selectorText)
try:
# uses parent stylesheets namespaces if available,
# otherwise given ones
namespaces = self.parent.parentRule.parentStyleSheet.namespaces
except AttributeError:
pass
tokenizer = self._tokenize2(selectorText)
if not tokenizer:
self._log.error(u'Selector: No selectorText given.')
else:
# prepare tokenlist:
# "*" -> type "universal"
# "*"|IDENT + "|" -> combined to "namespace_prefix"
# "|" -> type "namespace_prefix"
# "." + IDENT -> combined to "class"
# ":" + IDENT, ":" + FUNCTION -> pseudo-class
# FUNCTION "not(" -> negation
# "::" + IDENT, "::" + FUNCTION -> pseudo-element
tokens = []
for t in tokenizer:
typ, val, lin, col = t
if val == u':' and tokens and\
self._tokenvalue(tokens[-1]) == ':':
# combine ":" and ":"
tokens[-1] = (typ, u'::', lin, col)
elif typ == 'IDENT' and tokens\
and self._tokenvalue(tokens[-1]) == u'.':
# class: combine to .IDENT
tokens[-1] = ('class', u'.'+val, lin, col)
elif typ == 'IDENT' and tokens and \
self._tokenvalue(tokens[-1]).startswith(u':') and\
not self._tokenvalue(tokens[-1]).endswith(u'('):
# pseudo-X: combine to :IDENT or ::IDENT but not ":a(" + "b"
if self._tokenvalue(tokens[-1]).startswith(u'::'):
t = 'pseudo-element'
else:
t = 'pseudo-class'
tokens[-1] = (t, self._tokenvalue(tokens[-1])+val, lin, col)
elif typ == 'FUNCTION' and val == u'not(' and tokens and \
u':' == self._tokenvalue(tokens[-1]):
tokens[-1] = ('negation', u':' + val, lin, tokens[-1][3])
elif typ == 'FUNCTION' and tokens\
and self._tokenvalue(tokens[-1]).startswith(u':'):
# pseudo-X: combine to :FUNCTION( or ::FUNCTION(
if self._tokenvalue(tokens[-1]).startswith(u'::'):
t = 'pseudo-element'
else:
t = 'pseudo-class'
tokens[-1] = (t, self._tokenvalue(tokens[-1])+val, lin, col)
elif val == u'*' and tokens and\
self._type(tokens[-1]) == 'namespace_prefix' and\
self._tokenvalue(tokens[-1]).endswith(u'|'):
# combine prefix|*
tokens[-1] = ('universal', self._tokenvalue(tokens[-1])+val,
lin, col)
elif val == u'*':
# universal: "*"
tokens.append(('universal', val, lin, col))
elif val == u'|' and tokens and\
self._type(tokens[-1]) in (self._prods.IDENT, 'universal')\
and self._tokenvalue(tokens[-1]).find(u'|') == -1:
# namespace_prefix: "IDENT|" or "*|"
tokens[-1] = ('namespace_prefix',
self._tokenvalue(tokens[-1])+u'|', lin, col)
elif val == u'|':
# namespace_prefix: "|"
tokens.append(('namespace_prefix', val, lin, col))
else:
tokens.append(t)
tokenizer = iter(tokens)
# for closures: must be a mutable
new = {'context': [''], # stack of: 'attrib', 'negation', 'pseudo'
'element': None,
'_PREFIX': None,
'specificity': [0, 0, 0, 0], # mutable, finally a tuple!
'wellformed': True
}
# used for equality checks and setting of a space combinator
S = u' '
def append(seq, val, typ=None, token=None):
"""
appends to seq
namespace_prefix, IDENT will be combined to a tuple
(prefix, name) where prefix might be None, the empty string
or a prefix.
Saved are also:
- specificity definition: style, id, class/att, type
- element: the element this Selector is for
"""
context = new['context'][-1]
if token:
line, col = token[2], token[3]
else:
line, col = None, None
if typ == '_PREFIX':
# SPECIAL TYPE: save prefix for combination with next
new['_PREFIX'] = val[:-1]
# handle next time
return
if new['_PREFIX'] is not None:
# as saved from before and reset to None
prefix, new['_PREFIX'] = new['_PREFIX'], None
elif typ == 'universal' and '|' in val:
# val == *|* or prefix|*
prefix, val = val.split('|')
else:
prefix = None
# namespace
if (typ.endswith('-selector') or typ == 'universal') and not (
'attribute-selector' == typ and not prefix):
# att **IS NOT** in default ns
if prefix == u'*':
# *|name: in ANY_NS
namespaceURI = cssutils._ANYNS
elif prefix is None:
# e or *: default namespace with prefix u''
# or local-name()
namespaceURI = namespaces.get(u'', None)
elif prefix == u'':
# |name or |*: in no (or the empty) namespace
namespaceURI = u''
else:
# explicit namespace prefix
# does not raise KeyError, see _SimpleNamespaces
namespaceURI = namespaces[prefix]
if namespaceURI is None:
new['wellformed'] = False
self._log.error(u'Selector: No namespaceURI found '
u'for prefix %r' % prefix,
token=token,
error=xml.dom.NamespaceErr)
return
# val is now (namespaceprefix, name) tuple
val = (namespaceURI, val)
# specificity
if not context or context == 'negation':
if 'id' == typ:
new['specificity'][1] += 1
elif 'class' == typ or '[' == val:
new['specificity'][2] += 1
elif typ in ('type-selector', 'negation-type-selector',
'pseudo-element'):
new['specificity'][3] += 1
if not context and typ in ('type-selector', 'universal'):
# define element
new['element'] = val
seq.append(val, typ, line=line, col=col)
# expected constants
simple_selector_sequence = 'type_selector universal HASH class ' \
'attrib pseudo negation '
simple_selector_sequence2 = 'HASH class attrib pseudo negation '
element_name = 'element_name'
negation_arg = 'type_selector universal HASH class attrib pseudo'
negationend = ')'
attname = 'prefix attribute'
attname2 = 'attribute'
attcombinator = 'combinator ]' # optional
attvalue = 'value' # optional
attend = ']'
expressionstart = 'PLUS - DIMENSION NUMBER STRING IDENT'
expression = expressionstart + ' )'
combinator = ' combinator'
def _COMMENT(expected, seq, token, tokenizer=None):
"special implementation for comment token"
append(seq, cssutils.css.CSSComment([token]), 'COMMENT',
token=token)
return expected
def _S(expected, seq, token, tokenizer=None):
# S
context = new['context'][-1]
if context.startswith('pseudo-'):
if seq and seq[-1].value not in u'+-':
# e.g. x:func(a + b)
append(seq, S, 'S', token=token)
return expected
elif context != 'attrib' and 'combinator' in expected:
append(seq, S, 'descendant', token=token)
return simple_selector_sequence + combinator
else:
return expected
def _universal(expected, seq, token, tokenizer=None):
# *|* or prefix|*
context = new['context'][-1]
val = self._tokenvalue(token)
if 'universal' in expected:
append(seq, val, 'universal', token=token)
if 'negation' == context:
return negationend
else:
return simple_selector_sequence2 + combinator
else:
new['wellformed'] = False
self._log.error(
u'Selector: Unexpected universal.', token=token)
return expected
def _namespace_prefix(expected, seq, token, tokenizer=None):
# prefix| => element_name
# or prefix| => attribute_name if attrib
context = new['context'][-1]
val = self._tokenvalue(token)
if 'attrib' == context and 'prefix' in expected:
# [PREFIX|att]
append(seq, val, '_PREFIX', token=token)
return attname2
elif 'type_selector' in expected:
# PREFIX|*
append(seq, val, '_PREFIX', token=token)
return element_name
else:
new['wellformed'] = False
self._log.error(
u'Selector: Unexpected namespace prefix.', token=token)
return expected
def _pseudo(expected, seq, token, tokenizer=None):
# pseudo-class or pseudo-element :a ::a :a( ::a(
"""
/* '::' starts a pseudo-element, ':' a pseudo-class */
/* Exceptions: :first-line, :first-letter, :before and
:after. */
/* Note that pseudo-elements are restricted to one per selector
and */
/* occur only in the last simple_selector_sequence. */
"""
context = new['context'][-1]
val, typ = self._tokenvalue(token, normalize=True),\
self._type(token)
if 'pseudo' in expected:
if val in (':first-line',
':first-letter',
':before',
':after'):
# always pseudo-element ???
typ = 'pseudo-element'
append(seq, val, typ, token=token)
if val.endswith(u'('):
# function
# "pseudo-" "class" or "element"
new['context'].append(typ)
return expressionstart
elif 'negation' == context:
return negationend
elif 'pseudo-element' == typ:
# only one per element, check at ) also!
return combinator
else:
return simple_selector_sequence2 + combinator
else:
new['wellformed'] = False
self._log.error(
u'Selector: Unexpected start of pseudo.', token=token)
return expected
def _expression(expected, seq, token, tokenizer=None):
# [ [ PLUS | '-' | DIMENSION | NUMBER | STRING | IDENT ] S* ]+
context = new['context'][-1]
val, typ = self._tokenvalue(token), self._type(token)
if context.startswith('pseudo-'):
append(seq, val, typ, token=token)
return expression
else:
new['wellformed'] = False
self._log.error(
u'Selector: Unexpected %s.' % typ, token=token)
return expected
def _attcombinator(expected, seq, token, tokenizer=None):
# context: attrib
# PREFIXMATCH | SUFFIXMATCH | SUBSTRINGMATCH | INCLUDES |
# DASHMATCH
context = new['context'][-1]
val, typ = self._tokenvalue(token), self._type(token)
if 'attrib' == context and 'combinator' in expected:
# combinator in attrib
append(seq, val, typ.lower(), token=token)
return attvalue
else:
new['wellformed'] = False
self._log.error(
u'Selector: Unexpected %s.' % typ, token=token)
return expected
def _string(expected, seq, token, tokenizer=None):
# identifier
context = new['context'][-1]
typ, val = self._type(token), self._stringtokenvalue(token)
# context: attrib
if 'attrib' == context and 'value' in expected:
# attrib: [...=VALUE]
append(seq, val, typ, token=token)
return attend
# context: pseudo
elif context.startswith('pseudo-'):
# :func(...)
append(seq, val, typ, token=token)
return expression
else:
new['wellformed'] = False
self._log.error(
u'Selector: Unexpected STRING.', token=token)
return expected
def _ident(expected, seq, token, tokenizer=None):
# identifier
context = new['context'][-1]
val, typ = self._tokenvalue(token), self._type(token)
# context: attrib
if 'attrib' == context and 'attribute' in expected:
# attrib: [...|ATT...]
append(seq, val, 'attribute-selector', token=token)
return attcombinator
elif 'attrib' == context and 'value' in expected:
# attrib: [...=VALUE]
append(seq, val, 'attribute-value', token=token)
return attend
# context: negation
elif 'negation' == context:
# negation: (prefix|IDENT)
append(seq, val, 'negation-type-selector', token=token)
return negationend
# context: pseudo
elif context.startswith('pseudo-'):
# :func(...)
append(seq, val, typ, token=token)
return expression
elif 'type_selector' in expected or element_name == expected:
# element name after ns or complete type_selector
append(seq, val, 'type-selector', token=token)
return simple_selector_sequence2 + combinator
else:
new['wellformed'] = False
self._log.error(u'Selector: Unexpected IDENT.', token=token)
return expected
def _class(expected, seq, token, tokenizer=None):
# .IDENT
context = new['context'][-1]
val = self._tokenvalue(token)
if 'class' in expected:
append(seq, val, 'class', token=token)
if 'negation' == context:
return negationend
else:
return simple_selector_sequence2 + combinator
else:
new['wellformed'] = False
self._log.error(u'Selector: Unexpected class.', token=token)
return expected
def _hash(expected, seq, token, tokenizer=None):
# #IDENT
context = new['context'][-1]
val = self._tokenvalue(token)
if 'HASH' in expected:
append(seq, val, 'id', token=token)
if 'negation' == context:
return negationend
else:
return simple_selector_sequence2 + combinator
else:
new['wellformed'] = False
self._log.error(u'Selector: Unexpected HASH.', token=token)
return expected
def _char(expected, seq, token, tokenizer=None):
# + > ~ ) [ ] + -
context = new['context'][-1]
val = self._tokenvalue(token)
# context: attrib
if u']' == val and 'attrib' == context and ']' in expected:
# end of attrib
append(seq, val, 'attribute-end', token=token)
context = new['context'].pop() # attrib is done
context = new['context'][-1]
if 'negation' == context:
return negationend
else:
return simple_selector_sequence2 + combinator
elif u'=' == val and 'attrib' == context\
and 'combinator' in expected:
# combinator in attrib
append(seq, val, 'equals', token=token)
return attvalue
# context: negation
elif u')' == val and 'negation' == context and u')' in expected:
# not(negation_arg)"
append(seq, val, 'negation-end', token=token)
new['context'].pop() # negation is done
context = new['context'][-1]
return simple_selector_sequence + combinator
# context: pseudo (at least one expression)
elif val in u'+-' and context.startswith('pseudo-'):
# :func(+ -)"
_names = {'+': 'plus', '-': 'minus'}
if val == u'+' and seq and seq[-1].value == S:
seq.replace(-1, val, _names[val])
else:
append(seq, val, _names[val],
token=token)
return expression
elif u')' == val and context.startswith('pseudo-') and\
expression == expected:
# :func(expression)"
append(seq, val, 'function-end', token=token)
new['context'].pop() # pseudo is done
if 'pseudo-element' == context:
return combinator
else:
return simple_selector_sequence + combinator
# context: ROOT
elif u'[' == val and 'attrib' in expected:
# start of [attrib]
append(seq, val, 'attribute-start', token=token)
new['context'].append('attrib')
return attname
elif val in u'+>~' and 'combinator' in expected:
# no other combinator except S may be following
_names = {
'>': 'child',
'+': 'adjacent-sibling',
'~': 'following-sibling'}
if seq and seq[-1].value == S:
seq.replace(-1, val, _names[val])
else:
append(seq, val, _names[val], token=token)
return simple_selector_sequence
elif u',' == val:
# not a selectorlist
new['wellformed'] = False
self._log.error(
u'Selector: Single selector only.',
error=xml.dom.InvalidModificationErr,
token=token)
return expected
else:
new['wellformed'] = False
self._log.error(
u'Selector: Unexpected CHAR.', token=token)
return expected
def _negation(expected, seq, token, tokenizer=None):
# not(
context = new['context'][-1]
val = self._tokenvalue(token, normalize=True)
if 'negation' in expected:
new['context'].append('negation')
append(seq, val, 'negation-start', token=token)
return negation_arg
else:
new['wellformed'] = False
self._log.error(
u'Selector: Unexpected negation.', token=token)
return expected
def _atkeyword(expected, seq, token, tokenizer=None):
"invalidates selector"
new['wellformed'] = False
self._log.error(
u'Selector: Unexpected ATKEYWORD.', token=token)
return expected
# expected: only|not or mediatype, mediatype, feature, and
newseq = self._tempSeq()
wellformed, expected = self._parse(
expected=simple_selector_sequence,
seq=newseq, tokenizer=tokenizer,
productions={'CHAR': _char,
'class': _class,
'HASH': _hash,
'STRING': _string,
'IDENT': _ident,
'namespace_prefix': _namespace_prefix,
'negation': _negation,
'pseudo-class': _pseudo,
'pseudo-element': _pseudo,
'universal': _universal,
# pseudo
'NUMBER': _expression,
'DIMENSION': _expression,
# attribute
'PREFIXMATCH': _attcombinator,
'SUFFIXMATCH': _attcombinator,
'SUBSTRINGMATCH': _attcombinator,
'DASHMATCH': _attcombinator,
'INCLUDES': _attcombinator,
'S': _S,
'COMMENT': _COMMENT,
'ATKEYWORD': _atkeyword})
wellformed = wellformed and new['wellformed']
# post condition
if len(new['context']) > 1 or not newseq:
wellformed = False
self._log.error(u'Selector: Invalid or incomplete selector: %s'
% self._valuestr(selectorText))
if expected == 'element_name':
wellformed = False
self._log.error(u'Selector: No element name found: %s'
% self._valuestr(selectorText))
if expected == simple_selector_sequence and newseq:
wellformed = False
self._log.error(u'Selector: Cannot end with combinator: %s'
% self._valuestr(selectorText))
if newseq and hasattr(newseq[-1].value, 'strip') \
and newseq[-1].value.strip() == u'':
del newseq[-1]
# set
if wellformed:
self.__namespaces = namespaces
self._element = new['element']
self._specificity = tuple(new['specificity'])
self._setSeq(newseq)
# filter that only used ones are kept
self.__namespaces = self._getUsedNamespaces()
selectorText = property(_getSelectorText, _setSelectorText,
doc=u"(DOM) The parsable textual representation of "
u"the selector.")
specificity = property(lambda self: self._specificity,
doc="""Specificity of this selector (READONLY).
Tuple of (a, b, c, d) where:
a
presence of style in document, always 0 if not used on a
document
b
number of ID selectors
c
number of .class selectors
d
number of Element (type) selectors""")
wellformed = property(lambda self: bool(len(self.seq)))
@Deprecated('Use property parent instead')
def _getParentList(self):
return self.parent
parentList = property(_getParentList,
doc="DEPRECATED, see property parent instead")

234
libs/cssutils/css/selectorlist.py

@ -1,234 +0,0 @@
"""SelectorList is a list of CSS Selector objects.
TODO
- remove duplicate Selectors. -> CSSOM canonicalize
- ??? CSS2 gives a special meaning to the comma (,) in selectors.
However, since it is not known if the comma may acquire other
meanings in future versions of CSS, the whole statement should be
ignored if there is an error anywhere in the selector, even though
the rest of the selector may look reasonable in CSS2.
Illegal example(s):
For example, since the "&" is not a valid token in a CSS2 selector,
a CSS2 user agent must ignore the whole second line, and not set
the color of H3 to red:
"""
__all__ = ['SelectorList']
__docformat__ = 'restructuredtext'
__version__ = '$Id$'
from selector import Selector
import cssutils
import xml.dom
class SelectorList(cssutils.util.Base, cssutils.util.ListSeq):
"""A list of :class:`~cssutils.css.Selector` objects
of a :class:`~cssutils.css.CSSStyleRule`."""
def __init__(self, selectorText=None, parentRule=None,
readonly=False):
"""
:Parameters:
selectorText
parsable list of Selectors
parentRule
the parent CSSRule if available
"""
super(SelectorList, self).__init__()
self._parentRule = parentRule
if selectorText:
self.selectorText = selectorText
self._readonly = readonly
def __repr__(self):
if self._namespaces:
st = (self.selectorText, self._namespaces)
else:
st = self.selectorText
return u"cssutils.css.%s(selectorText=%r)" % (self.__class__.__name__,
st)
def __str__(self):
return u"<cssutils.css.%s object selectorText=%r _namespaces=%r at " \
u"0x%x>" % (self.__class__.__name__,
self.selectorText,
self._namespaces,
id(self))
def __setitem__(self, index, newSelector):
"""Overwrite ListSeq.__setitem__
Any duplicate Selectors are **not** removed.
"""
newSelector = self.__prepareset(newSelector)
if newSelector:
self.seq[index] = newSelector
def __prepareset(self, newSelector, namespaces=None):
"Used by appendSelector and __setitem__"
if not namespaces:
namespaces = {}
self._checkReadonly()
if not isinstance(newSelector, Selector):
newSelector = Selector((newSelector, namespaces),
parent=self)
if newSelector.wellformed:
newSelector._parent = self # maybe set twice but must be!
return newSelector
def __getNamespaces(self):
"""Use children namespaces if not attached to a sheet, else the sheet's
ones.
"""
try:
return self.parentRule.parentStyleSheet.namespaces
except AttributeError:
namespaces = {}
for selector in self.seq:
namespaces.update(selector._namespaces)
return namespaces
def _getUsedUris(self):
"Used by CSSStyleSheet to check if @namespace rules are needed"
uris = set()
for s in self:
uris.update(s._getUsedUris())
return uris
_namespaces = property(__getNamespaces, doc="""If this SelectorList is
attached to a CSSStyleSheet the namespaces of that sheet are mirrored
here. While the SelectorList (or parentRule(s) are
not attached the namespaces of all children Selectors are used.""")
def append(self, newSelector):
"Same as :meth:`appendSelector`."
self.appendSelector(newSelector)
def appendSelector(self, newSelector):
"""
Append `newSelector` to this list (a string will be converted to a
:class:`~cssutils.css.Selector`).
:param newSelector:
comma-separated list of selectors (as a single string) or a tuple of
`(newSelector, dict-of-namespaces)`
:returns: New :class:`~cssutils.css.Selector` or ``None`` if
`newSelector` is not wellformed.
:exceptions:
- :exc:`~xml.dom.NamespaceErr`:
Raised if the specified selector uses an unknown namespace
prefix.
- :exc:`~xml.dom.SyntaxErr`:
Raised if the specified CSS string value has a syntax error
and is unparsable.
- :exc:`~xml.dom.NoModificationAllowedErr`:
Raised if this rule is readonly.
"""
self._checkReadonly()
# might be (selectorText, namespaces)
newSelector, namespaces = self._splitNamespacesOff(newSelector)
try:
# use parent's only if available
namespaces = self.parentRule.parentStyleSheet.namespaces
except AttributeError:
# use already present namespaces plus new given ones
_namespaces = self._namespaces
_namespaces.update(namespaces)
namespaces = _namespaces
newSelector = self.__prepareset(newSelector, namespaces)
if newSelector:
seq = self.seq[:]
del self.seq[:]
for s in seq:
if s.selectorText != newSelector.selectorText:
self.seq.append(s)
self.seq.append(newSelector)
return newSelector
def _getSelectorText(self):
"Return serialized format."
return cssutils.ser.do_css_SelectorList(self)
def _setSelectorText(self, selectorText):
"""
:param selectorText:
comma-separated list of selectors or a tuple of
(selectorText, dict-of-namespaces)
:exceptions:
- :exc:`~xml.dom.NamespaceErr`:
Raised if the specified selector uses an unknown namespace
prefix.
- :exc:`~xml.dom.SyntaxErr`:
Raised if the specified CSS string value has a syntax error
and is unparsable.
- :exc:`~xml.dom.NoModificationAllowedErr`:
Raised if this rule is readonly.
"""
self._checkReadonly()
# might be (selectorText, namespaces)
selectorText, namespaces = self._splitNamespacesOff(selectorText)
try:
# use parent's only if available
namespaces = self.parentRule.parentStyleSheet.namespaces
except AttributeError:
pass
wellformed = True
tokenizer = self._tokenize2(selectorText)
newseq = []
expected = True
while True:
# find all upto and including next ",", EOF or nothing
selectortokens = self._tokensupto2(tokenizer, listseponly=True)
if selectortokens:
if self._tokenvalue(selectortokens[-1]) == ',':
expected = selectortokens.pop()
else:
expected = None
selector = Selector((selectortokens, namespaces),
parent=self)
if selector.wellformed:
newseq.append(selector)
else:
wellformed = False
self._log.error(u'SelectorList: Invalid Selector: %s' %
self._valuestr(selectortokens))
else:
break
# post condition
if u',' == expected:
wellformed = False
self._log.error(u'SelectorList: Cannot end with ",": %r' %
self._valuestr(selectorText))
elif expected:
wellformed = False
self._log.error(u'SelectorList: Unknown Syntax: %r' %
self._valuestr(selectorText))
if wellformed:
self.seq = newseq
selectorText = property(_getSelectorText, _setSelectorText,
doc=u"(cssutils) The textual representation of the "
u"selector for a rule set.")
length = property(lambda self: len(self),
doc=u"The number of :class:`~cssutils.css.Selector` "
u"objects in the list.")
parentRule = property(lambda self: self._parentRule,
doc=u"(DOM) The CSS rule that contains this "
u"SelectorList or ``None`` if this SelectorList "
u"is not attached to a CSSRule.")
wellformed = property(lambda self: bool(len(self.seq)))

871
libs/cssutils/css/value.py

@ -1,871 +0,0 @@
"""Value related classes.
DOM Level 2 CSS CSSValue, CSSPrimitiveValue and CSSValueList are **no longer**
supported and are replaced by these new classes.
"""
__all__ = ['PropertyValue',
'Value',
'ColorValue',
'DimensionValue',
'URIValue',
'CSSFunction',
'CSSVariable',
'MSValue'
]
__docformat__ = 'restructuredtext'
__version__ = '$Id$'
from cssutils.prodparser import *
import cssutils
from cssutils.helper import normalize, pushtoken
import colorsys
import math
import re
import xml.dom
import urlparse
class PropertyValue(cssutils.util._NewBase):
"""
An unstructured list like holder for all values defined for a
:class:`~cssutils.css.Property`. Contains :class:`~cssutils.css.Value`
or subclass objects. Currently there is no access to the combinators of
the defined values which might simply be space or comma or slash.
You may:
- iterate over all contained Value objects (not the separators like ``,``,
``/`` or `` `` though!)
- get a Value item by index or use ``PropertyValue[index]``
- find out the number of values defined (unstructured)
"""
def __init__(self, cssText=None, parent=None, readonly=False):
"""
:param cssText:
the parsable cssText of the value
:param readonly:
defaults to False
"""
super(PropertyValue, self).__init__()
self.parent = parent
self.wellformed = False
if cssText is not None: # may be 0
if isinstance(cssText, (int, float)):
cssText = unicode(cssText) # if it is a number
self.cssText = cssText
self._readonly = readonly
def __len__(self):
return len(list(self.__items()))
def __getitem__(self, index):
try:
return list(self.__items())[index]
except IndexError:
return None
def __iter__(self):
"Generator which iterates over values."
for item in self.__items():
yield item
def __repr__(self):
return u"cssutils.css.%s(%r)" % (self.__class__.__name__,
self.cssText)
def __str__(self):
return u"<cssutils.css.%s object length=%r cssText=%r at "\
u"0x%x>" % (self.__class__.__name__,
self.length, self.cssText, id(self))
def __items(self, seq=None):
"a generator of Value obects only, no , / or ' '"
if seq is None:
seq = self.seq
return (x.value for x in seq if isinstance(x.value, Value))
def _setCssText(self, cssText):
if isinstance(cssText, (int, float)):
cssText = unicode(cssText) # if it is a number
"""
Format::
unary_operator
: '-' | '+'
;
operator
: '/' S* | ',' S* | /* empty */
;
expr
: term [ operator term ]*
;
term
: unary_operator?
[ NUMBER S* | PERCENTAGE S* | LENGTH S* | EMS S* | EXS S* |
ANGLE S* | TIME S* | FREQ S* ]
| STRING S* | IDENT S* | URI S* | hexcolor | function
| UNICODE-RANGE S*
;
function
: FUNCTION S* expr ')' S*
;
/*
* There is a constraint on the color that it must
* have either 3 or 6 hex-digits (i.e., [0-9a-fA-F])
* after the "#"; e.g., "#000" is OK, but "#abcd" is not.
*/
hexcolor
: HASH S*
;
:exceptions:
- :exc:`~xml.dom.SyntaxErr`:
Raised if the specified CSS string value has a syntax error
(according to the attached property) or is unparsable.
- :exc:`~xml.dom.InvalidModificationErr`:
TODO: Raised if the specified CSS string value represents a
different type of values than the values allowed by the CSS
property.
- :exc:`~xml.dom.NoModificationAllowedErr`:
Raised if this value is readonly.
"""
self._checkReadonly()
# used as operator is , / or S
nextSor = u',/'
term = Choice(_ColorProd(self, nextSor),
_DimensionProd(self, nextSor),
_URIProd(self, nextSor),
_ValueProd(self, nextSor),
# _CalcValueProd(self, nextSor),
# _Rect(self, nextSor),
# all other functions
_CSSVariableProd(self, nextSor),
_MSValueProd(self, nextSor),
_CSSFunctionProd(self, nextSor)
)
operator = Choice(PreDef.S(toSeq=False),
PreDef.char('comma', ',',
toSeq=lambda t, tokens: ('operator', t[1])),
PreDef.char('slash', '/',
toSeq=lambda t, tokens: ('operator', t[1])),
optional=True)
prods = Sequence(term,
Sequence(# mayEnd this Sequence if whitespace
operator,
# TODO: only when setting via other class
# used by variabledeclaration currently
PreDef.char('END', ';',
stopAndKeep=True,
optional=True),
# TODO: } and !important ends too!
term,
minmax=lambda: (0, None)))
# parse
ok, seq, store, unused = ProdParser().parse(cssText,
u'PropertyValue',
prods)
# must be at least one value!
ok = ok and len(list(self.__items(seq))) > 0
if ok:
self._setSeq(seq)
self.wellformed = True
else:
self._log.error(u'PropertyValue: Unknown syntax or no value: %s' %
self._valuestr(cssText))
cssText = property(lambda self: cssutils.ser.do_css_PropertyValue(self),
_setCssText,
doc="A string representation of the current value.")
def item(self, index):
"""
The value at position `index`. Alternatively simple use
``PropertyValue[index]``.
:param index:
the parsable cssText of the value
:exceptions:
- :exc:`~IndexError`:
Raised if index if out of bounds
"""
return self[index]
length = property(lambda self: len(self),
doc=u"Number of values set.")
value = property(lambda self: cssutils.ser.do_css_PropertyValue(self,
valuesOnly=True),
doc=u"A string representation of the current value "
u"without any comments used for validation.")
class Value(cssutils.util._NewBase):
"""
Represents a single CSS value. For now simple values of
IDENT, STRING, or UNICODE-RANGE values are represented directly
as Value objects. Other values like e.g. FUNCTIONs are represented by
subclasses with an extended API.
"""
IDENT = u'IDENT'
STRING = u'STRING'
UNICODE_RANGE = u'UNICODE-RANGE'
URI = u'URI'
DIMENSION = u'DIMENSION'
NUMBER = u'NUMBER'
PERCENTAGE = u'PERCENTAGE'
COLOR_VALUE = u'COLOR_VALUE'
HASH = u'HASH'
FUNCTION = u'FUNCTION'
VARIABLE = u'VARIABLE'
_type = None
_value = u''
def __init__(self, cssText=None, parent=None, readonly=False):
super(Value, self).__init__()
self.parent = parent
if cssText:
self.cssText = cssText
def __repr__(self):
return u"cssutils.css.%s(%r)" % (self.__class__.__name__,
self.cssText)
def __str__(self):
return u"<cssutils.css.%s object type=%s value=%r cssText=%r at 0x%x>"\
% (self.__class__.__name__,
self.type, self.value, self.cssText,
id(self))
def _setCssText(self, cssText):
self._checkReadonly()
prods = Choice(PreDef.hexcolor(stop=True),
PreDef.ident(stop=True),
PreDef.string(stop=True),
PreDef.unicode_range(stop=True),
)
ok, seq, store, unused = ProdParser().parse(cssText, u'Value', prods)
if ok:
# only 1 value anyway!
self._type = seq[0].type
self._value = seq[0].value
self._setSeq(seq)
self.wellformed = ok
cssText = property(lambda self: cssutils.ser.do_css_Value(self),
_setCssText,
doc=u'String value of this value.')
type = property(lambda self: self._type, #_setType,
doc=u"Type of this value, for now the production type "
u"like e.g. `DIMENSION` or `STRING`. All types are "
u"defined as constants in :class:`~cssutils.css.Value`.")
def _setValue(self, value):
# TODO: check!
self._value = value
value = property(lambda self: self._value, _setValue,
doc=u"Actual value if possible: An int or float or else "
u" a string")
class ColorValue(Value):
"""
A color value like rgb(), rgba(), hsl(), hsla() or #rgb, #rrggbb
TODO: Color Keywords
"""
from colors import COLORS
type = Value.COLOR_VALUE
# hexcolor, FUNCTION?
_colorType = None
_red = 0
_green = 0
_blue = 0
_alpha = 0
def __str__(self):
return u"<cssutils.css.%s object type=%s value=%r colorType=%r "\
u"red=%s blue=%s green=%s alpha=%s at 0x%x>"\
% (self.__class__.__name__,
self.type, self.value,
self.colorType, self.red, self.green, self.blue, self.alpha,
id(self))
def _setCssText(self, cssText):
self._checkReadonly()
types = self._prods # rename!
component = Choice(PreDef.unary(toSeq=lambda t, tokens: (t[0],
DimensionValue(pushtoken(t, tokens),
parent=self)
)),
PreDef.number(toSeq=lambda t, tokens: (t[0],
DimensionValue(pushtoken(t, tokens),
parent=self)
)),
PreDef.percentage(toSeq=lambda t, tokens: (t[0],
DimensionValue(pushtoken(t, tokens),
parent=self)
))
)
noalp = Sequence(Prod(name='FUNCTION',
match=lambda t, v: t == types.FUNCTION and
v in (u'rgb(', u'hsl('),
toSeq=lambda t, tokens: (t[0], normalize(t[1]))),
component,
Sequence(PreDef.comma(),
component,
minmax=lambda: (2, 2)
),
PreDef.funcEnd(stop=True)
)
witha = Sequence(Prod(name='FUNCTION',
match=lambda t, v: t == types.FUNCTION and
v in (u'rgba(', u'hsla('),
toSeq=lambda t, tokens: (t[0],
normalize(t[1]))
),
component,
Sequence(PreDef.comma(),
component,
minmax=lambda: (3, 3)
),
PreDef.funcEnd(stop=True)
)
namedcolor = Prod(name='Named Color',
match=lambda t, v: t == 'IDENT' and (
normalize(v) in self.COLORS.keys()
),
stop=True)
prods = Choice(PreDef.hexcolor(stop=True),
namedcolor,
noalp,
witha)
ok, seq, store, unused = ProdParser().parse(cssText,
self.type,
prods)
if ok:
t, v = seq[0].type, seq[0].value
if u'IDENT' == t:
rgba = self.COLORS[normalize(v)]
if u'HASH' == t:
if len(v) == 4:
# HASH #rgb
rgba = (int(2*v[1], 16),
int(2*v[2], 16),
int(2*v[3], 16),
1.0)
else:
# HASH #rrggbb
rgba = (int(v[1:3], 16),
int(v[3:5], 16),
int(v[5:7], 16),
1.0)
elif u'FUNCTION' == t:
functiontype, raw, check = None, [], u''
HSL = False
for item in seq:
try:
type_ = item.value.type
except AttributeError, e:
# type of function, e.g. rgb(
if item.type == 'FUNCTION':
functiontype = item.value
HSL = functiontype in (u'hsl(', u'hsla(')
continue
# save components
if type_ == Value.NUMBER:
raw.append(item.value.value)
check += u'N'
elif type_ == Value.PERCENTAGE:
if HSL:
# save as percentage fraction
raw.append(item.value.value / 100.0)
else:
# save as real value of percentage of 255
raw.append(int(255 * item.value.value / 100))
check += u'P'
if HSL:
# convert to rgb
# h is 360 based (circle)
h, s, l = raw[0] / 360.0, raw[1], raw[2]
# ORDER h l s !!!
r, g, b = colorsys.hls_to_rgb(h, l, s)
# back to 255 based
rgba = [int(round(r*255)),
int(round(g*255)),
int(round(b*255))]
if len(raw) > 3:
rgba.append(raw[3])
else:
# rgb, rgba
rgba = raw
if len(rgba) < 4:
rgba.append(1.0)
# validate
checks = {u'rgb(': ('NNN', 'PPP'),
u'rgba(': ('NNNN', 'PPPN'),
u'hsl(': ('NPP',),
u'hsla(': ('NPPN',)
}
if check not in checks[functiontype]:
self._log.error(u'ColorValue has invalid %s) parameters: '
u'%s (N=Number, P=Percentage)' %
(functiontype, check))
self._colorType = t
self._red, self._green, self._blue, self._alpha = tuple(rgba)
self._setSeq(seq)
self.wellformed = ok
cssText = property(lambda self: cssutils.ser.do_css_ColorValue(self),
_setCssText,
doc=u"String value of this value.")
value = property(lambda self: cssutils.ser.do_css_CSSFunction(self, True),
doc=u'Same as cssText but without comments.')
type = property(lambda self: Value.COLOR_VALUE,
doc=u"Type is fixed to Value.COLOR_VALUE.")
def _getName(self):
for n, v in self.COLORS.items():
if v == (self.red, self.green, self.blue, self.alpha):
return n
colorType = property(lambda self: self._colorType,
doc=u"IDENT (red), HASH (#f00) or FUNCTION (rgb(255, 0, 0).")
name = property(_getName,
doc=u'Name of the color if known (in ColorValue.COLORS) '
u'else None')
red = property(lambda self: self._red,
doc=u'red part as integer between 0 and 255')
green = property(lambda self: self._green,
doc=u'green part as integer between 0 and 255')
blue = property(lambda self: self._blue,
doc=u'blue part as integer between 0 and 255')
alpha = property(lambda self: self._alpha,
doc=u'alpha part as float between 0.0 and 1.0')
class DimensionValue(Value):
"""
A numerical value with an optional dimenstion like e.g. "px" or "%".
Covers DIMENSION, PERCENTAGE or NUMBER values.
"""
__reNumDim = re.compile(ur'^(\d*\.\d+|\d+)(.*)$', re.I | re.U | re.X)
_dimension = None
_sign = None
def __str__(self):
return u"<cssutils.css.%s object type=%s value=%r dimension=%r cssText=%r at 0x%x>"\
% (self.__class__.__name__,
self.type, self.value, self.dimension, self.cssText,
id(self))
def _setCssText(self, cssText):
self._checkReadonly()
prods = Sequence(PreDef.unary(),
Choice(PreDef.dimension(stop=True),
PreDef.number(stop=True),
PreDef.percentage(stop=True)
)
)
ok, seq, store, unused = ProdParser().parse(cssText,
u'DimensionValue',
prods)
if ok:
sign = val = u''
dim = type_ = None
# find
for item in seq:
if item.value in u'+-':
sign = item.value
else:
type_ = item.type
# number + optional dim
v, d = self.__reNumDim.findall(
normalize(item.value))[0]
if u'.' in v:
val = float(sign + v)
else:
val = int(sign + v)
if d:
dim = d
self._sign = sign
self._value = val
self._dimension = dim
self._type = type_
self._setSeq(seq)
self.wellformed = ok
cssText = property(lambda self: cssutils.ser.do_css_Value(self),
_setCssText,
doc=u"String value of this value including dimension.")
dimension = property(lambda self: self._dimension, #_setValue,
doc=u"Dimension if a DIMENSION or PERCENTAGE value, "
u"else None")
class URIValue(Value):
"""
An URI value like ``url(example.png)``.
"""
_type = Value.URI
_uri = Value._value
def __str__(self):
return u"<cssutils.css.%s object type=%s value=%r uri=%r cssText=%r at 0x%x>"\
% (self.__class__.__name__,
self.type, self.value, self.uri, self.cssText,
id(self))
def _setCssText(self, cssText):
self._checkReadonly()
prods = Sequence(PreDef.uri(stop=True))
ok, seq, store, unused = ProdParser().parse(cssText, u'URIValue', prods)
if ok:
# only 1 value only anyway
self._type = seq[0].type
self._value = seq[0].value
self._setSeq(seq)
self.wellformed = ok
cssText = property(lambda self: cssutils.ser.do_css_Value(self),
_setCssText,
doc=u'String value of this value.')
def _setUri(self, uri):
# TODO: check?
self._value = uri
uri = property(lambda self: self._value, _setUri,
doc=u"Actual URL without delimiters or the empty string")
def absoluteUri(self):
"""Actual URL, made absolute if possible, else same as `uri`."""
# Ancestry: PropertyValue, Property, CSSStyleDeclaration, CSSStyleRule,
# CSSStyleSheet
try:
# TODO: better way?
styleSheet = self.parent.parent.parent.parentRule.parentStyleSheet
except AttributeError, e:
return self.uri
else:
return urlparse.urljoin(styleSheet.href, self.uri)
absoluteUri = property(absoluteUri, doc=absoluteUri.__doc__)
class CSSFunction(Value):
"""
A function value.
"""
_functionName = 'Function'
def _productions(self):
"""Return definition used for parsing."""
types = self._prods # rename!
itemProd = Choice(_ColorProd(self),
_DimensionProd(self),
_URIProd(self),
_ValueProd(self),
#_CalcValueProd(self),
_CSSVariableProd(self),
_CSSFunctionProd(self)
)
funcProds = Sequence(Prod(name='FUNCTION',
match=lambda t, v: t == types.FUNCTION,
toSeq=lambda t, tokens: (t[0],
normalize(t[1]))),
Choice(Sequence(itemProd,
Sequence(PreDef.comma(),
itemProd,
minmax=lambda: (0, None)),
PreDef.funcEnd(stop=True)),
PreDef.funcEnd(stop=True))
)
return funcProds
def _setCssText(self, cssText):
self._checkReadonly()
ok, seq, store, unused = ProdParser().parse(cssText,
self.type,
self._productions())
if ok:
self._setSeq(seq)
self.wellformed = ok
cssText = property(lambda self: cssutils.ser.do_css_CSSFunction(self),
_setCssText,
doc=u"String value of this value.")
value = property(lambda self: cssutils.ser.do_css_CSSFunction(self, True),
doc=u'Same as cssText but without comments.')
type = property(lambda self: Value.FUNCTION,
doc=u"Type is fixed to Value.FUNCTION.")
class MSValue(CSSFunction):
"""An IE specific Microsoft only function value which is much looser
in what is syntactically allowed."""
_functionName = 'MSValue'
def _productions(self):
"""Return definition used for parsing."""
types = self._prods # rename!
func = Prod(name='MSValue-Sub',
match=lambda t, v: t == self._prods.FUNCTION,
toSeq=lambda t, tokens: (MSValue._functionName,
MSValue(pushtoken(t,
tokens
),
parent=self
)
)
)
funcProds = Sequence(Prod(name='FUNCTION',
match=lambda t, v: t == types.FUNCTION,
toSeq=lambda t, tokens: (t[0], t[1])
),
Sequence(Choice(_ColorProd(self),
_DimensionProd(self),
_URIProd(self),
_ValueProd(self),
_MSValueProd(self),
#_CalcValueProd(self),
_CSSVariableProd(self),
func,
#_CSSFunctionProd(self),
Prod(name='MSValuePart',
match=lambda t, v: v != u')',
toSeq=lambda t, tokens: (t[0], t[1])
)
),
minmax=lambda: (0, None)
),
PreDef.funcEnd(stop=True)
)
return funcProds
def _setCssText(self, cssText):
super(MSValue, self)._setCssText(cssText)
cssText = property(lambda self: cssutils.ser.do_css_MSValue(self),
_setCssText,
doc=u"String value of this value.")
class CSSVariable(CSSFunction):
"""The CSSVariable represents a CSS variables like ``var(varname)``.
A variable has a (nonnormalized!) `name` and a `value` which is
tried to be resolved from any available CSSVariablesRule definition.
"""
_functionName = 'CSSVariable'
_name = None
def __str__(self):
return u"<cssutils.css.%s object name=%r value=%r at 0x%x>" % (
self.__class__.__name__, self.name, self.value, id(self))
def _setCssText(self, cssText):
self._checkReadonly()
types = self._prods # rename!
prods = Sequence(Prod(name='var',
match=lambda t, v: t == types.FUNCTION and
normalize(v) == u'var('
),
PreDef.ident(toStore='ident'),
PreDef.funcEnd(stop=True))
# store: name of variable
store = {'ident': None}
ok, seq, store, unused = ProdParser().parse(cssText,
u'CSSVariable',
prods)
if ok:
self._name = store['ident'].value
self._setSeq(seq)
self.wellformed = ok
cssText = property(lambda self: cssutils.ser.do_css_CSSVariable(self),
_setCssText, doc=u"String representation of variable.")
# TODO: writable? check if var (value) available?
name = property(lambda self: self._name,
doc=u"The name identifier of this variable referring to "
u"a value in a "
u":class:`cssutils.css.CSSVariablesDeclaration`.")
type = property(lambda self: Value.VARIABLE,
doc=u"Type is fixed to Value.VARIABLE.")
def _getValue(self):
"Find contained sheet and @variables there"
rel = self
while True:
# find node which has parentRule to get to StyleSheet
if hasattr(rel, 'parent'):
rel = rel.parent
else:
break
try:
variables = rel.parentRule.parentStyleSheet.variables
except AttributeError:
return None
else:
try:
return variables[self.name]
except KeyError:
return None
value = property(_getValue,
doc=u'The resolved actual value or None.')
# helper for productions
def _ValueProd(parent, nextSor=False):
return Prod(name='Value',
match=lambda t, v: t in ('IDENT', 'STRING', 'UNICODE-RANGE'),
nextSor = nextSor,
toSeq=lambda t, tokens: ('Value', Value(
pushtoken(t,
tokens),
parent=parent)
)
)
def _DimensionProd(parent, nextSor=False):
return Prod(name='Dimension',
match=lambda t, v: t in (u'DIMENSION',
u'NUMBER',
u'PERCENTAGE') or v in u'+-',
nextSor = nextSor,
toSeq=lambda t, tokens: (t[0], DimensionValue(
pushtoken(t,
tokens),
parent=parent)
)
)
def _URIProd(parent, nextSor=False):
return Prod(name='URIValue',
match=lambda t, v: t == 'URI',
nextSor = nextSor,
toSeq=lambda t, tokens: ('URIValue', URIValue(
pushtoken(t,
tokens),
parent=parent)
)
)
reHexcolor = re.compile(r'^\#(?:[0-9abcdefABCDEF]{3}|[0-9abcdefABCDEF]{6})$')
def _ColorProd(parent, nextSor=False):
return Prod(name='ColorValue',
match=lambda t, v:
(t == 'HASH' and
reHexcolor.match(v)
) or
(t == 'FUNCTION' and
normalize(v) in (u'rgb(',
u'rgba(',
u'hsl(',
u'hsla(')
) or
(t == 'IDENT' and
normalize(v) in ColorValue.COLORS.keys()
),
nextSor = nextSor,
toSeq=lambda t, tokens: ('ColorValue', ColorValue(
pushtoken(t,
tokens),
parent=parent)
)
)
def _CSSFunctionProd(parent, nextSor=False):
return PreDef.function(nextSor=nextSor,
toSeq=lambda t, tokens: (CSSFunction._functionName,
CSSFunction(
pushtoken(t, tokens),
parent=parent)
)
)
def _CSSVariableProd(parent, nextSor=False):
return PreDef.variable(nextSor=nextSor,
toSeq=lambda t, tokens: (CSSVariable._functionName,
CSSVariable(
pushtoken(t, tokens),
parent=parent)
)
)
def _MSValueProd(parent, nextSor=False):
return Prod(name=MSValue._functionName,
match=lambda t, v: (#t == self._prods.FUNCTION and (
normalize(v) in (u'expression(',
u'alpha(',
u'blur(',
u'chroma(',
u'dropshadow(',
u'fliph(',
u'flipv(',
u'glow(',
u'gray(',
u'invert(',
u'mask(',
u'shadow(',
u'wave(',
u'xray(') or
v.startswith(u'progid:DXImageTransform.Microsoft.')
),
nextSor=nextSor,
toSeq=lambda t, tokens: (MSValue._functionName,
MSValue(pushtoken(t,
tokens
),
parent=parent
)
)
)

131
libs/cssutils/css2productions.py

@ -1,131 +0,0 @@
"""productions for CSS 2.1
CSS2_1_MACROS and CSS2_1_PRODUCTIONS are from both
http://www.w3.org/TR/CSS21/grammar.html and
http://www.w3.org/TR/css3-syntax/#grammar0
"""
__all__ = ['CSSProductions', 'MACROS', 'PRODUCTIONS']
__docformat__ = 'restructuredtext'
__version__ = '$Id$'
# option case-insensitive
MACROS = {
'h': r'[0-9a-f]',
#'nonascii': r'[\200-\377]',
'nonascii': r'[^\0-\177]', # CSS3
'unicode': r'\\{h}{1,6}(\r\n|[ \t\r\n\f])?',
'escape': r'{unicode}|\\[^\r\n\f0-9a-f]',
'nmstart': r'[_a-zA-Z]|{nonascii}|{escape}',
'nmchar': r'[_a-zA-Z0-9-]|{nonascii}|{escape}',
'string1': r'\"([^\n\r\f\\"]|\\{nl}|{escape})*\"',
'string2': r"\'([^\n\r\f\\']|\\{nl}|{escape})*\'",
'invalid1': r'\"([^\n\r\f\\"]|\\{nl}|{escape})*',
'invalid2': r"\'([^\n\r\f\\']|\\{nl}|{escape})*",
'comment': r'\/\*[^*]*\*+([^/*][^*]*\*+)*\/',
# CSS list 080725 19:43
# \/\*([^*\\]|{escape})*\*+(([^/*\\]|{escape})[^*]*\*+)*\/
'ident': r'[-]?{nmstart}{nmchar}*',
'name': r'{nmchar}+',
# CHANGED TO SPEC: added "-?"
'num': r'-?[0-9]*\.[0-9]+|[0-9]+',
'string': r'{string1}|{string2}',
'invalid': r'{invalid1}|{invalid2}',
'url': r'([!#$%&*-~]|{nonascii}|{escape})*',
's': r'[ \t\r\n\f]+',
'w': r'{s}?',
'nl': r'\n|\r\n|\r|\f',
'range': r'\?{1,6}|{h}(\?{0,5}|{h}(\?{0,4}|{h}(\?{0,3}|{h}(\?{0,2}|{h}(\??|{h})))))',
'A': r'a|\\0{0,4}(41|61)(\r\n|[ \t\r\n\f])?',
'C': r'c|\\0{0,4}(43|63)(\r\n|[ \t\r\n\f])?',
'D': r'd|\\0{0,4}(44|64)(\r\n|[ \t\r\n\f])?',
'E': r'e|\\0{0,4}(45|65)(\r\n|[ \t\r\n\f])?',
'F': r'f|\\0{0,4}(46|66)(\r\n|[ \t\r\n\f])?',
'G': r'g|\\0{0,4}(47|67)(\r\n|[ \t\r\n\f])?|\\g',
'H': r'h|\\0{0,4}(48|68)(\r\n|[ \t\r\n\f])?|\\h',
'I': r'i|\\0{0,4}(49|69)(\r\n|[ \t\r\n\f])?|\\i',
'K': r'k|\\0{0,4}(4b|6b)(\r\n|[ \t\r\n\f])?|\\k',
'M': r'm|\\0{0,4}(4d|6d)(\r\n|[ \t\r\n\f])?|\\m',
'N': r'n|\\0{0,4}(4e|6e)(\r\n|[ \t\r\n\f])?|\\n',
'O': r'o|\\0{0,4}(51|71)(\r\n|[ \t\r\n\f])?|\\o',
'P': r'p|\\0{0,4}(50|70)(\r\n|[ \t\r\n\f])?|\\p',
'R': r'r|\\0{0,4}(52|72)(\r\n|[ \t\r\n\f])?|\\r',
'S': r's|\\0{0,4}(53|73)(\r\n|[ \t\r\n\f])?|\\s',
'T': r't|\\0{0,4}(54|74)(\r\n|[ \t\r\n\f])?|\\t',
'X': r'x|\\0{0,4}(58|78)(\r\n|[ \t\r\n\f])?|\\x',
'Z': r'z|\\0{0,4}(5a|7a)(\r\n|[ \t\r\n\f])?|\\z',
}
PRODUCTIONS = [
('URI', r'url\({w}{string}{w}\)'), #"url("{w}{string}{w}")" {return URI;}
('URI', r'url\({w}{url}{w}\)'), #"url("{w}{url}{w}")" {return URI;}
('FUNCTION', r'{ident}\('), #{ident}"(" {return FUNCTION;}
('IMPORT_SYM', r'@{I}{M}{P}{O}{R}{T}'), #"@import" {return IMPORT_SYM;}
('PAGE_SYM', r'@{P}{A}{G}{E}'), #"@page" {return PAGE_SYM;}
('MEDIA_SYM', r'@{M}{E}{D}{I}{A}'), #"@media" {return MEDIA_SYM;}
('FONT_FACE_SYM', r'@{F}{O}{N}{T}\-{F}{A}{C}{E}'), #"@font-face" {return FONT_FACE_SYM;}
# CHANGED TO SPEC: only @charset
('CHARSET_SYM', r'@charset '), #"@charset " {return CHARSET_SYM;}
('NAMESPACE_SYM', r'@{N}{A}{M}{E}{S}{P}{A}{C}{E}'), #"@namespace" {return NAMESPACE_SYM;}
# CHANGED TO SPEC: ATKEYWORD
('ATKEYWORD', r'\@{ident}'),
('IDENT', r'{ident}'), #{ident} {return IDENT;}
('STRING', r'{string}'), #{string} {return STRING;}
('INVALID', r'{invalid}'), # {return INVALID; /* unclosed string */}
('HASH', r'\#{name}'), #"#"{name} {return HASH;}
('PERCENTAGE', r'{num}%'), #{num}% {return PERCENTAGE;}
('LENGTH', r'{num}{E}{M}'), #{num}em {return EMS;}
('LENGTH', r'{num}{E}{X}'), #{num}ex {return EXS;}
('LENGTH', r'{num}{P}{X}'), #{num}px {return LENGTH;}
('LENGTH', r'{num}{C}{M}'), #{num}cm {return LENGTH;}
('LENGTH', r'{num}{M}{M}'), #{num}mm {return LENGTH;}
('LENGTH', r'{num}{I}{N}'), #{num}in {return LENGTH;}
('LENGTH', r'{num}{P}{T}'), #{num}pt {return LENGTH;}
('LENGTH', r'{num}{P}{C}'), #{num}pc {return LENGTH;}
('ANGLE', r'{num}{D}{E}{G}'), #{num}deg {return ANGLE;}
('ANGLE', r'{num}{R}{A}{D}'), #{num}rad {return ANGLE;}
('ANGLE', r'{num}{G}{R}{A}{D}'), #{num}grad {return ANGLE;}
('TIME', r'{num}{M}{S}'), #{num}ms {return TIME;}
('TIME', r'{num}{S}'), #{num}s {return TIME;}
('FREQ', r'{num}{H}{Z}'), #{num}Hz {return FREQ;}
('FREQ', r'{num}{K}{H}{Z}'), #{num}kHz {return FREQ;}
('DIMEN', r'{num}{ident}'), #{num}{ident} {return DIMEN;}
('NUMBER', r'{num}'), #{num} {return NUMBER;}
#('UNICODERANGE', r'U\+{range}'), #U\+{range} {return UNICODERANGE;}
#('UNICODERANGE', r'U\+{h}{1,6}-{h}{1,6}'), #U\+{h}{1,6}-{h}{1,6} {return UNICODERANGE;}
# --- CSS3 ---
('UNICODE-RANGE', r'[0-9A-F?]{1,6}(\-[0-9A-F]{1,6})?'),
('CDO', r'\<\!\-\-'), #"<!--" {return CDO;}
('CDC', r'\-\-\>'), #"-->" {return CDC;}
('S', r'{s}'),# {return S;}
# \/\*[^*]*\*+([^/*][^*]*\*+)*\/ /* ignore comments */
# {s}+\/\*[^*]*\*+([^/*][^*]*\*+)*\/ {unput(' '); /*replace by space*/}
('INCLUDES', r'\~\='), #"~=" {return INCLUDES;}
('DASHMATCH', r'\|\='), #"|=" {return DASHMATCH;}
('LBRACE', r'\{'), #{w}"{" {return LBRACE;}
('PLUS', r'\+'), #{w}"+" {return PLUS;}
('GREATER', r'\>'), #{w}">" {return GREATER;}
('COMMA', r'\,'), #{w}"," {return COMMA;}
('IMPORTANT_SYM', r'\!({w}|{comment})*{I}{M}{P}{O}{R}{T}{A}{N}{T}'), #"!{w}important" {return IMPORTANT_SYM;}
('COMMENT', '\/\*[^*]*\*+([^/][^*]*\*+)*\/'), # /* ignore comments */
('CLASS', r'\.'), #. {return *yytext;}
# --- CSS3! ---
('CHAR', r'[^"\']'),
]
class CSSProductions(object):
pass
for i, t in enumerate(PRODUCTIONS):
setattr(CSSProductions, t[0].replace('-', '_'), t[0])

124
libs/cssutils/cssproductions.py

@ -1,124 +0,0 @@
"""productions for cssutils based on a mix of CSS 2.1 and CSS 3 Syntax
productions
- http://www.w3.org/TR/css3-syntax
- http://www.w3.org/TR/css3-syntax/#grammar0
open issues
- numbers contain "-" if present
- HASH: #aaa is, #000 is not anymore,
CSS2.1: 'nmchar': r'[_a-z0-9-]|{nonascii}|{escape}',
CSS3: 'nmchar': r'[_a-z-]|{nonascii}|{escape}',
"""
__all__ = ['CSSProductions', 'MACROS', 'PRODUCTIONS']
__docformat__ = 'restructuredtext'
__version__ = '$Id$'
# a complete list of css3 macros
MACROS = {
'nonascii': r'[^\0-\177]',
'unicode': r'\\[0-9A-Fa-f]{1,6}(?:{nl}|{s})?',
#'escape': r'{unicode}|\\[ -~\200-\777]',
'escape': r'{unicode}|\\[^\n\r\f0-9a-f]',
'nmstart': r'[_a-zA-Z]|{nonascii}|{escape}',
'nmchar': r'[-_a-zA-Z0-9]|{nonascii}|{escape}',
'string1': r'"([^\n\r\f\\"]|\\{nl}|{escape})*"',
'string2': r"'([^\n\r\f\\']|\\{nl}|{escape})*'",
'invalid1': r'\"([^\n\r\f\\"]|\\{nl}|{escape})*',
'invalid2': r"\'([^\n\r\f\\']|\\{nl}|{escape})*",
'comment': r'\/\*[^*]*\*+([^/][^*]*\*+)*\/',
'ident': r'[-]?{nmstart}{nmchar}*',
'name': r'{nmchar}+',
'num': r'[0-9]*\.[0-9]+|[0-9]+', #r'[-]?\d+|[-]?\d*\.\d+',
'string': r'{string1}|{string2}',
# from CSS2.1
'invalid': r'{invalid1}|{invalid2}',
'url': r'[\x09\x21\x23-\x26\x28\x2a-\x7E]|{nonascii}|{escape}',
's': r'\t|\r|\n|\f|\x20',
'w': r'{s}*',
'nl': r'\n|\r\n|\r|\f',
'A': r'A|a|\\0{0,4}(?:41|61)(?:\r\n|[ \t\r\n\f])?',
'B': r'B|b|\\0{0,4}(?:42|62)(?:\r\n|[ \t\r\n\f])?',
'C': r'C|c|\\0{0,4}(?:43|63)(?:\r\n|[ \t\r\n\f])?',
'D': r'D|d|\\0{0,4}(?:44|64)(?:\r\n|[ \t\r\n\f])?',
'E': r'E|e|\\0{0,4}(?:45|65)(?:\r\n|[ \t\r\n\f])?',
'F': r'F|f|\\0{0,4}(?:46|66)(?:\r\n|[ \t\r\n\f])?',
'G': r'G|g|\\0{0,4}(?:47|67)(?:\r\n|[ \t\r\n\f])?|\\G|\\g',
'H': r'H|h|\\0{0,4}(?:48|68)(?:\r\n|[ \t\r\n\f])?|\\H|\\h',
'I': r'I|i|\\0{0,4}(?:49|69)(?:\r\n|[ \t\r\n\f])?|\\I|\\i',
'K': r'K|k|\\0{0,4}(?:4b|6b)(?:\r\n|[ \t\r\n\f])?|\\K|\\k',
'L': r'L|l|\\0{0,4}(?:4c|6c)(?:\r\n|[ \t\r\n\f])?|\\L|\\l',
'M': r'M|m|\\0{0,4}(?:4d|6d)(?:\r\n|[ \t\r\n\f])?|\\M|\\m',
'N': r'N|n|\\0{0,4}(?:4e|6e)(?:\r\n|[ \t\r\n\f])?|\\N|\\n',
'O': r'O|o|\\0{0,4}(?:4f|6f)(?:\r\n|[ \t\r\n\f])?|\\O|\\o',
'P': r'P|p|\\0{0,4}(?:50|70)(?:\r\n|[ \t\r\n\f])?|\\P|\\p',
'R': r'R|r|\\0{0,4}(?:52|72)(?:\r\n|[ \t\r\n\f])?|\\R|\\r',
'S': r'S|s|\\0{0,4}(?:53|73)(?:\r\n|[ \t\r\n\f])?|\\S|\\s',
'T': r'T|t|\\0{0,4}(?:54|74)(?:\r\n|[ \t\r\n\f])?|\\T|\\t',
'U': r'U|u|\\0{0,4}(?:55|75)(?:\r\n|[ \t\r\n\f])?|\\U|\\u',
'V': r'V|v|\\0{0,4}(?:56|76)(?:\r\n|[ \t\r\n\f])?|\\V|\\v',
'X': r'X|x|\\0{0,4}(?:58|78)(?:\r\n|[ \t\r\n\f])?|\\X|\\x',
'Z': r'Z|z|\\0{0,4}(?:5a|7a)(?:\r\n|[ \t\r\n\f])?|\\Z|\\z',
}
# The following productions are the complete list of tokens
# used by cssutils, a mix of CSS3 and some CSS2.1 productions.
# The productions are **ordered**:
PRODUCTIONS = [
# UTF8_BOM or UTF8_BOM_SIG will only be checked at beginning of CSS
('BOM', '\xfe\xff|\xef\xbb\xbf'),
('S', r'{s}+'), # 1st in list of general productions
('URI', r'{U}{R}{L}\({w}({string}|{url}*){w}\)'),
('FUNCTION', r'{ident}\('),
('UNICODE-RANGE', r'{U}\+[0-9A-Fa-f?]{1,6}(\-[0-9A-Fa-f]{1,6})?'),
('IDENT', r'{ident}'),
('DIMENSION', r'{num}{ident}'),
('PERCENTAGE', r'{num}\%'),
('NUMBER', r'{num}'),
('HASH', r'\#{name}'),
('COMMENT', r'{comment}'), #r'\/\*[^*]*\*+([^/][^*]*\*+)*\/'),
('STRING', r'{string}'),
('INVALID', r'{invalid}'), # from CSS2.1
('ATKEYWORD', r'@{ident}'), # other keywords are done in the tokenizer
('INCLUDES', '\~\='),
('DASHMATCH', r'\|\='),
('PREFIXMATCH', r'\^\='),
('SUFFIXMATCH', r'\$\='),
('SUBSTRINGMATCH', r'\*\='),
('CDO', r'\<\!\-\-'),
('CDC', r'\-\-\>'),
('CHAR', r'[^"\']') # MUST always be last
# valid ony at start so not checked everytime
#('CHARSET_SYM', r'@charset '), # from Errata includes ending space!
# checked specially if fullsheet is parsed
]
class CSSProductions(object):
"""
most attributes are set later
"""
EOF = True
# removed from productions as they simply are ATKEYWORD until
# tokenizing
CHARSET_SYM = u'CHARSET_SYM'
FONT_FACE_SYM = u'FONT_FACE_SYM'
MEDIA_SYM = u'MEDIA_SYM'
IMPORT_SYM = u'IMPORT_SYM'
NAMESPACE_SYM = u'NAMESPACE_SYM'
PAGE_SYM = u'PAGE_SYM'
VARIABLES_SYM = u'VARIABLES_SYM'
for i, t in enumerate(PRODUCTIONS):
setattr(CSSProductions, t[0].replace('-', '_'), t[0])
# may be enabled by settings.set
_DXImageTransform = (u'FUNCTION',
ur'progid\:DXImageTransform\.Microsoft\..+\('
)

118
libs/cssutils/errorhandler.py

@ -1,118 +0,0 @@
#!/usr/bin/env python
"""cssutils ErrorHandler
ErrorHandler
used as log with usual levels (debug, info, warn, error)
if instanciated with ``raiseExceptions=True`` raises exeptions instead
of logging
log
defaults to instance of ErrorHandler for any kind of log message from
lexerm, parser etc.
- raiseExceptions = [False, True]
- setloglevel(loglevel)
"""
__all__ = ['ErrorHandler']
__docformat__ = 'restructuredtext'
__version__ = '$Id$'
import logging
import urllib2
import xml.dom
class _ErrorHandler(object):
"""
handles all errors and log messages
"""
def __init__(self, log, defaultloglevel=logging.INFO,
raiseExceptions=True):
"""
inits log if none given
log
for parse messages, default logs to sys.stderr
defaultloglevel
if none give this is logging.DEBUG
raiseExceptions
- True: Errors will be raised e.g. during building
- False: Errors will be written to the log, this is the
default behaviour when parsing
"""
# may be disabled during setting of known valid items
self.enabled = True
if log:
self._log = log
else:
import sys
self._log = logging.getLogger('CSSUTILS')
hdlr = logging.StreamHandler(sys.stderr)
formatter = logging.Formatter('%(levelname)s\t%(message)s')
hdlr.setFormatter(formatter)
self._log.addHandler(hdlr)
self._log.setLevel(defaultloglevel)
self.raiseExceptions = raiseExceptions
def __getattr__(self, name):
"use self._log items"
calls = ('debug', 'info', 'warn', 'error', 'critical', 'fatal')
other = ('setLevel', 'getEffectiveLevel', 'addHandler', 'removeHandler')
if name in calls:
self._logcall = getattr(self._log, name)
return self.__handle
elif name in other:
return getattr(self._log, name)
else:
raise AttributeError(
'(errorhandler) No Attribute %r found' % name)
def __handle(self, msg=u'', token=None, error=xml.dom.SyntaxErr,
neverraise=False, args=None):
"""
handles all calls
logs or raises exception
"""
if self.enabled:
if error is None:
error = xml.dom.SyntaxErr
line, col = None, None
if token:
if isinstance(token, tuple):
value, line, col = token[1], token[2], token[3]
else:
value, line, col = token.value, token.line, token.col
msg = u'%s [%s:%s: %s]' % (
msg, line, col, value)
if error and self.raiseExceptions and not neverraise:
if isinstance(error, urllib2.HTTPError) or isinstance(error, urllib2.URLError):
raise
elif issubclass(error, xml.dom.DOMException):
error.line = line
error.col = col
raise error(msg)
else:
self._logcall(msg)
def setLog(self, log):
"""set log of errorhandler's log"""
self._log = log
class ErrorHandler(_ErrorHandler):
"Singleton, see _ErrorHandler"
instance = None
def __init__(self,
log=None, defaultloglevel=logging.INFO, raiseExceptions=True):
if ErrorHandler.instance is None:
ErrorHandler.instance = _ErrorHandler(log=log,
defaultloglevel=defaultloglevel,
raiseExceptions=raiseExceptions)
self.__dict__ = ErrorHandler.instance.__dict__

137
libs/cssutils/helper.py

@ -1,137 +0,0 @@
"""cssutils helper
"""
__docformat__ = 'restructuredtext'
__version__ = '$Id: errorhandler.py 1234 2008-05-22 20:26:12Z cthedot $'
import os
import re
import sys
import urllib
class Deprecated(object):
"""This is a decorator which can be used to mark functions
as deprecated. It will result in a warning being emitted
when the function is used.
It accepts a single paramter ``msg`` which is shown with the warning.
It should contain information which function or method to use instead.
"""
def __init__(self, msg):
self.msg = msg
def __call__(self, func):
def newFunc(*args, **kwargs):
import warnings
warnings.warn("Call to deprecated method %r. %s" %
(func.__name__, self.msg),
category=DeprecationWarning,
stacklevel=2)
return func(*args, **kwargs)
newFunc.__name__ = func.__name__
newFunc.__doc__ = func.__doc__
newFunc.__dict__.update(func.__dict__)
return newFunc
# simple escapes, all non unicodes
_simpleescapes = re.compile(ur'(\\[^0-9a-fA-F])').sub
def normalize(x):
"""
normalizes x, namely:
- remove any \ before non unicode sequences (0-9a-zA-Z) so for
x=="c\olor\" return "color" (unicode escape sequences should have
been resolved by the tokenizer already)
- lowercase
"""
if x:
def removeescape(matchobj):
return matchobj.group(0)[1:]
x = _simpleescapes(removeescape, x)
return x.lower()
else:
return x
def path2url(path):
"""Return file URL of `path`"""
return u'file:' + urllib.pathname2url(os.path.abspath(path))
def pushtoken(token, tokens):
"""Return new generator starting with token followed by all tokens in
``tokens``"""
# TODO: may use itertools.chain?
yield token
for t in tokens:
yield t
def string(value):
"""
Serialize value with quotes e.g.::
``a \'string`` => ``'a \'string'``
"""
# \n = 0xa, \r = 0xd, \f = 0xc
value = value.replace(u'\n', u'\\a ').replace(
u'\r', u'\\d ').replace(
u'\f', u'\\c ').replace(
u'"', u'\\"')
if value.endswith(u'\\'):
value = value[:-1] + u'\\\\'
return u'"%s"' % value
def stringvalue(string):
"""
Retrieve actual value of string without quotes. Escaped
quotes inside the value are resolved, e.g.::
``'a \'string'`` => ``a 'string``
"""
return string.replace(u'\\'+string[0], string[0])[1:-1]
_match_forbidden_in_uri = re.compile(ur'''.*?[\(\)\s\;,'"]''', re.U).match
def uri(value):
"""
Serialize value by adding ``url()`` and with quotes if needed e.g.::
``"`` => ``url("\"")``
"""
if _match_forbidden_in_uri(value):
value = string(value)
return u'url(%s)' % value
def urivalue(uri):
"""
Return actual content without surrounding "url(" and ")"
and removed surrounding quotes too including contained
escapes of quotes, e.g.::
``url("\"")`` => ``"``
"""
uri = uri[uri.find('(')+1:-1].strip()
if uri and (uri[0] in '\'"') and (uri[0] == uri[-1]):
return stringvalue(uri)
else:
return uri
#def normalnumber(num):
# """
# Return normalized number as string.
# """
# sign = ''
# if num.startswith('-'):
# sign = '-'
# num = num[1:]
# elif num.startswith('+'):
# num = num[1:]
#
# if float(num) == 0.0:
# return '0'
# else:
# if num.find('.') == -1:
# return sign + str(int(num))
# else:
# a, b = num.split('.')
# if not a:
# a = '0'
# return '%s%s.%s' % (sign, int(a), b)

232
libs/cssutils/parse.py

@ -1,232 +0,0 @@
#!/usr/bin/env python
"""A validating CSSParser"""
__all__ = ['CSSParser']
__docformat__ = 'restructuredtext'
__version__ = '$Id$'
from helper import path2url
import codecs
import cssutils
import os
import sys
import tokenize2
import urllib
from cssutils import css
if sys.version_info < (2,6):
bytes = str
class CSSParser(object):
"""Parse a CSS StyleSheet from URL, string or file and return a DOM Level 2
CSS StyleSheet object.
Usage::
parser = CSSParser()
# optionally
parser.setFetcher(fetcher)
sheet = parser.parseFile('test1.css', 'ascii')
print sheet.cssText
"""
def __init__(self, log=None, loglevel=None, raiseExceptions=None,
fetcher=None, parseComments=True,
validate=True):
"""
:param log:
logging object
:param loglevel:
logging loglevel
:param raiseExceptions:
if log should simply log (default) or raise errors during
parsing. Later while working with the resulting sheets
the setting used in cssutils.log.raiseExeptions is used
:param fetcher:
see ``setFetcher(fetcher)``
:param parseComments:
if comments should be added to CSS DOM or simply omitted
:param validate:
if parsing should validate, may be overwritten in parse methods
"""
if log is not None:
cssutils.log.setLog(log)
if loglevel is not None:
cssutils.log.setLevel(loglevel)
# remember global setting
self.__globalRaising = cssutils.log.raiseExceptions
if raiseExceptions:
self.__parseRaising = raiseExceptions
else:
# DEFAULT during parse
self.__parseRaising = False
self.__tokenizer = tokenize2.Tokenizer(doComments=parseComments)
self.setFetcher(fetcher)
self._validate = validate
def __parseSetting(self, parse):
"""during parse exceptions may be handled differently depending on
init parameter ``raiseExceptions``
"""
if parse:
cssutils.log.raiseExceptions = self.__parseRaising
else:
cssutils.log.raiseExceptions = self.__globalRaising
def parseStyle(self, cssText, encoding='utf-8', validate=None):
"""Parse given `cssText` which is assumed to be the content of
a HTML style attribute.
:param cssText:
CSS string to parse
:param encoding:
It will be used to decode `cssText` if given as a (byte)
string.
:param validate:
If given defines if validation is used. Uses CSSParser settings as
fallback
:returns:
:class:`~cssutils.css.CSSStyleDeclaration`
"""
self.__parseSetting(True)
if isinstance(cssText, bytes):
# TODO: use codecs.getdecoder('css') here?
cssText = cssText.decode(encoding)
if validate is None:
validate = self._validate
style = css.CSSStyleDeclaration(cssText, validating=validate)
self.__parseSetting(False)
return style
def parseString(self, cssText, encoding=None, href=None, media=None,
title=None,
validate=None):
"""Parse `cssText` as :class:`~cssutils.css.CSSStyleSheet`.
Errors may be raised (e.g. UnicodeDecodeError).
:param cssText:
CSS string to parse
:param encoding:
If ``None`` the encoding will be read from BOM or an @charset
rule or defaults to UTF-8.
If given overrides any found encoding including the ones for
imported sheets.
It also will be used to decode `cssText` if given as a (byte)
string.
:param href:
The ``href`` attribute to assign to the parsed style sheet.
Used to resolve other urls in the parsed sheet like @import hrefs.
:param media:
The ``media`` attribute to assign to the parsed style sheet
(may be a MediaList, list or a string).
:param title:
The ``title`` attribute to assign to the parsed style sheet.
:param validate:
If given defines if validation is used. Uses CSSParser settings as
fallback
:returns:
:class:`~cssutils.css.CSSStyleSheet`.
"""
self.__parseSetting(True)
# TODO: py3 needs bytes here!
if isinstance(cssText, bytes):
cssText = codecs.getdecoder('css')(cssText, encoding=encoding)[0]
if validate is None:
validate = self._validate
sheet = cssutils.css.CSSStyleSheet(href=href,
media=cssutils.stylesheets.MediaList(media),
title=title,
validating=validate)
sheet._setFetcher(self.__fetcher)
# tokenizing this ways closes open constructs and adds EOF
sheet._setCssTextWithEncodingOverride(self.__tokenizer.tokenize(cssText,
fullsheet=True),
encodingOverride=encoding)
self.__parseSetting(False)
return sheet
def parseFile(self, filename, encoding=None,
href=None, media=None, title=None,
validate=None):
"""Retrieve content from `filename` and parse it. Errors may be raised
(e.g. IOError).
:param filename:
of the CSS file to parse, if no `href` is given filename is
converted to a (file:) URL and set as ``href`` of resulting
stylesheet.
If `href` is given it is set as ``sheet.href``. Either way
``sheet.href`` is used to resolve e.g. stylesheet imports via
@import rules.
:param encoding:
Value ``None`` defaults to encoding detection via BOM or an
@charset rule.
Other values override detected encoding for the sheet at
`filename` including any imported sheets.
:returns:
:class:`~cssutils.css.CSSStyleSheet`.
"""
if not href:
# prepend // for file URL, urllib does not do this?
#href = u'file:' + urllib.pathname2url(os.path.abspath(filename))
href = path2url(filename)
return self.parseString(open(filename, 'rb').read(),
encoding=encoding, # read returns a str
href=href, media=media, title=title,
validate=validate)
def parseUrl(self, href, encoding=None, media=None, title=None,
validate=None):
"""Retrieve content from URL `href` and parse it. Errors may be raised
(e.g. URLError).
:param href:
URL of the CSS file to parse, will also be set as ``href`` of
resulting stylesheet
:param encoding:
Value ``None`` defaults to encoding detection via HTTP, BOM or an
@charset rule.
A value overrides detected encoding for the sheet at ``href``
including any imported sheets.
:returns:
:class:`~cssutils.css.CSSStyleSheet`.
"""
encoding, enctype, text = cssutils.util._readUrl(href,
fetcher=self.__fetcher,
overrideEncoding=encoding)
if enctype == 5:
# do not use if defaulting to UTF-8
encoding = None
if text is not None:
return self.parseString(text, encoding=encoding,
href=href, media=media, title=title,
validate=validate)
def setFetcher(self, fetcher=None):
"""Replace the default URL fetch function with a custom one.
:param fetcher:
A function which gets a single parameter
``url``
the URL to read
and must return ``(encoding, content)`` where ``encoding`` is the
HTTP charset normally given via the Content-Type header (which may
simply omit the charset in which case ``encoding`` would be
``None``) and ``content`` being the string (or unicode) content.
The Mimetype should be 'text/css' but this has to be checked by the
fetcher itself (the default fetcher emits a warning if encountering
a different mimetype).
Calling ``setFetcher`` with ``fetcher=None`` resets cssutils
to use its default function.
"""
self.__fetcher = fetcher

733
libs/cssutils/prodparser.py

@ -1,733 +0,0 @@
# -*- coding: utf-8 -*-
"""Productions parser used by css and stylesheets classes to parse
test into a cssutils.util.Seq and at the same time retrieving
additional specific cssutils.util.Item objects for later use.
TODO:
- ProdsParser
- handle EOF or STOP?
- handle unknown @rules
- handle S: maybe save to Seq? parameterized?
- store['_raw']: always?
- Sequence:
- opt first(), naive impl for now
"""
__all__ = ['ProdParser', 'Sequence', 'Choice', 'Prod', 'PreDef']
__docformat__ = 'restructuredtext'
__version__ = '$Id: parse.py 1418 2008-08-09 19:27:50Z cthedot $'
from helper import pushtoken
import cssutils
import re
import string
import sys
class ParseError(Exception):
"""Base Exception class for ProdParser (used internally)."""
pass
class Done(ParseError):
"""Raised if Sequence or Choice is finished and no more Prods left."""
pass
class Exhausted(ParseError):
"""Raised if Sequence or Choice is finished but token is given."""
pass
class Missing(ParseError):
"""Raised if Sequence or Choice is not finished but no matching token given."""
pass
class NoMatch(ParseError):
"""Raised if nothing in Sequence or Choice does match."""
pass
class Choice(object):
"""A Choice of productions (Sequence or single Prod)."""
def __init__(self, *prods, **options):
"""
*prods
Prod or Sequence objects
options:
optional=False
"""
self._prods = prods
try:
self.optional = options['optional']
except KeyError, e:
for p in self._prods:
if p.optional:
self.optional = True
break
else:
self.optional = False
self.reset()
def reset(self):
"""Start Choice from zero"""
self._exhausted = False
def matches(self, token):
"""Check if token matches"""
for prod in self._prods:
if prod.matches(token):
return True
return False
def nextProd(self, token):
"""
Return:
- next matching Prod or Sequence
- ``None`` if any Prod or Sequence is optional and no token matched
- raise ParseError if nothing matches and all are mandatory
- raise Exhausted if choice already done
``token`` may be None but this occurs when no tokens left."""
if not self._exhausted:
optional = False
for x in self._prods:
if x.matches(token):
self._exhausted = True
x.reset()
return x
elif x.optional:
optional = True
else:
if not optional:
# None matched but also None is optional
raise ParseError(u'No match in %s' % self)
elif token:
raise Exhausted(u'Extra token')
def __str__(self):
return u'Choice(%s)' % u', '.join([str(x) for x in self._prods])
class Sequence(object):
"""A Sequence of productions (Choice or single Prod)."""
def __init__(self, *prods, **options):
"""
*prods
Prod or Sequence objects
**options:
minmax = lambda: (1, 1)
callback returning number of times this sequence may run
"""
self._prods = prods
try:
minmax = options['minmax']
except KeyError:
minmax = lambda: (1, 1)
self._min, self._max = minmax()
if self._max is None:
# unlimited
try:
# py2.6/3
self._max = sys.maxsize
except AttributeError:
# py<2.6
self._max = sys.maxint
self._prodcount = len(self._prods)
self.reset()
def matches(self, token):
"""Called by Choice to try to find if Sequence matches."""
for prod in self._prods:
if prod.matches(token):
return True
try:
if not prod.optional:
break
except AttributeError:
pass
return False
def reset(self):
"""Reset this Sequence if it is nested."""
self._roundstarted = False
self._i = 0
self._round = 0
def _currentName(self):
"""Return current element of Sequence, used by name"""
# TODO: current impl first only if 1st if an prod!
for prod in self._prods[self._i:]:
if not prod.optional:
return str(prod)
else:
return 'Sequence'
optional = property(lambda self: self._min == 0)
def nextProd(self, token):
"""Return
- next matching Prod or Choice
- raises ParseError if nothing matches
- raises Exhausted if sequence already done
"""
while self._round < self._max:
# for this round
i = self._i
round = self._round
p = self._prods[i]
if i == 0:
self._roundstarted = False
# for next round
self._i += 1
if self._i == self._prodcount:
self._round += 1
self._i = 0
if p.matches(token):
self._roundstarted = True
# reset nested Choice or Prod to use from start
p.reset()
return p
elif p.optional:
continue
elif round < self._min:
raise Missing(u'Missing token for production %s' % p)
elif not token:
if self._roundstarted:
raise Missing(u'Missing token for production %s' % p)
else:
raise Done()
else:
raise NoMatch(u'No matching production for token')
if token:
raise Exhausted(u'Extra token')
def __str__(self):
return u'Sequence(%s)' % u', '.join([str(x) for x in self._prods])
class Prod(object):
"""Single Prod in Sequence or Choice."""
def __init__(self, name, match, optional=False,
toSeq=None, toStore=None,
stop=False, stopAndKeep=False,
nextSor=False, mayEnd=False,
storeToken=None,
exception=None):
"""
name
name used for error reporting
match callback
function called with parameters tokentype and tokenvalue
returning True, False or raising ParseError
toSeq callback (optional) or False
calling toSeq(token, tokens) returns (type_, val) == (token[0], token[1])
to be appended to seq else simply unaltered (type_, val)
if False nothing is added
toStore (optional)
key to save util.Item to store or callback(store, util.Item)
optional = False
wether Prod is optional or not
stop = False
if True stop parsing of tokens here
stopAndKeep
if True stop parsing of tokens here but return stopping
token in unused tokens
nextSor=False
next is S or other like , or / (CSSValue)
mayEnd = False
no token must follow even defined by Sequence.
Used for operator ',/ ' currently only
storeToken = None
if True toStore saves simple token tuple and not and Item object
to store. Old style processing, TODO: resolve
exception = None
exception to be raised in case of error, normaly SyntaxErr
"""
self._name = name
self.match = match
self.optional = optional
self.stop = stop
self.stopAndKeep = stopAndKeep
self.nextSor = nextSor
self.mayEnd = mayEnd
self.storeToken = storeToken
self.exception = exception
def makeToStore(key):
"Return a function used by toStore."
def toStore(store, item):
"Set or append store item."
if key in store:
_v = store[key]
if not isinstance(_v, list):
store[key] = [_v]
store[key].append(item)
else:
store[key] = item
return toStore
if toSeq or toSeq is False:
# called: seq.append(toSeq(value))
self.toSeq = toSeq
else:
self.toSeq = lambda t, tokens: (t[0], t[1])
if hasattr(toStore, '__call__'):
self.toStore = toStore
elif toStore:
self.toStore = makeToStore(toStore)
else:
# always set!
self.toStore = None
def matches(self, token):
"""Return if token matches."""
if not token:
return False
type_, val, line, col = token
return self.match(type_, val)
def reset(self):
pass
def __str__(self):
return self._name
def __repr__(self):
return "<cssutils.prodsparser.%s object name=%r at 0x%x>" % (
self.__class__.__name__, self._name, id(self))
# global tokenizer as there is only one!
tokenizer = cssutils.tokenize2.Tokenizer()
class ProdParser(object):
"""Productions parser."""
def __init__(self, clear=True):
self.types = cssutils.cssproductions.CSSProductions
self._log = cssutils.log
if clear:
tokenizer.clear()
def _texttotokens(self, text):
"""Build a generator which is the only thing that is parsed!
old classes may use lists etc
"""
if isinstance(text, basestring):
# DEFAULT, to tokenize strip space
return tokenizer.tokenize(text.strip())
elif isinstance(text, tuple):
# OLD: (token, tokens) or a single token
if len(text) == 2:
# (token, tokens)
chain([token], tokens)
else:
# single token
return iter([text])
elif isinstance(text, list):
# OLD: generator from list
return iter(text)
else:
# DEFAULT, already tokenized, assume generator
return text
def _SorTokens(self, tokens, until=',/'):
"""New tokens generator which has S tokens removed,
if followed by anything in ``until``, normally a ``,``."""
for token in tokens:
if token[0] == self.types.S:
try:
next_ = tokens.next()
except StopIteration:
yield token
else:
if next_[1] in until:
# omit S as e.g. ``,`` has been found
yield next_
elif next_[0] == self.types.COMMENT:
# pass COMMENT
yield next_
else:
yield token
yield next_
elif token[0] == self.types.COMMENT:
# pass COMMENT
yield token
else:
yield token
break
# normal mode again
for token in tokens:
yield token
def parse(self, text, name, productions, keepS=False, store=None):
"""
text (or token generator)
to parse, will be tokenized if not a generator yet
may be:
- a string to be tokenized
- a single token, a tuple
- a tuple of (token, tokensGenerator)
- already tokenized so a tokens generator
name
used for logging
productions
used to parse tokens
keepS
if WS should be added to Seq or just be ignored
store UPDATED
If a Prod defines ``toStore`` the key defined there
is a key in store to be set or if store[key] is a list
the next Item is appended here.
TODO: NEEDED? :
Key ``raw`` is always added and holds all unprocessed
values found
returns
:wellformed: True or False
:seq: a filled cssutils.util.Seq object which is NOT readonly yet
:store: filled keys defined by Prod.toStore
:unusedtokens: token generator containing tokens not used yet
"""
tokens = self._texttotokens(text)
if not tokens:
self._log.error(u'No content to parse.')
# TODO: return???
seq = cssutils.util.Seq(readonly=False)
if not store: # store for specific values
store = {}
prods = [productions] # stack of productions
wellformed = True
# while no real token is found any S are ignored
started = False
stopall = False
prod = None
# flag if default S handling should be done
defaultS = True
while True:
try:
token = tokens.next()
except StopIteration:
break
type_, val, line, col = token
# default productions
if type_ == self.types.COMMENT:
# always append COMMENT
seq.append(cssutils.css.CSSComment(val),
cssutils.css.CSSComment, line, col)
elif defaultS and type_ == self.types.S:
# append S (but ignore starting ones)
if not keepS or not started:
continue
else:
seq.append(val, type_, line, col)
# elif type_ == self.types.ATKEYWORD:
# # @rule
# r = cssutils.css.CSSUnknownRule(cssText=val)
# seq.append(r, type(r), line, col)
elif type_ == self.types.INVALID:
# invalidate parse
wellformed = False
self._log.error(u'Invalid token: %r' % (token,))
break
elif type_ == 'EOF':
# do nothing? (self.types.EOF == True!)
pass
else:
started = True # check S now
nextSor = False # reset
try:
while True:
# find next matching production
try:
prod = prods[-1].nextProd(token)
except (Exhausted, NoMatch), e:
# try next
prod = None
if isinstance(prod, Prod):
# found actual Prod, not a Choice or Sequence
break
elif prod:
# nested Sequence, Choice
prods.append(prod)
else:
# nested exhausted, try in parent
if len(prods) > 1:
prods.pop()
else:
raise ParseError('No match')
except ParseError, e:
wellformed = False
self._log.error(u'%s: %s: %r' % (name, e, token))
break
else:
# process prod
if prod.toSeq and not prod.stopAndKeep:
type_, val = prod.toSeq(token, tokens)
if val is not None:
seq.append(val, type_, line, col)
if prod.toStore:
if not prod.storeToken:
prod.toStore(store, seq[-1])
else:
# workaround for now for old style token
# parsing!
# TODO: remove when all new style
prod.toStore(store, token)
if prod.stop: # EOF?
# stop here and ignore following tokens
break
if prod.stopAndKeep: # e.g. ;
# stop here and ignore following tokens
# but keep this token for next run
tokenizer.push(token)
stopall = True
break
if prod.nextSor:
# following is S or other token (e.g. ",")?
# remove S if
tokens = self._SorTokens(tokens, ',/')
defaultS = False
else:
defaultS = True
lastprod = prod
if not stopall:
# stop immediately
while True:
# all productions exhausted?
try:
prod = prods[-1].nextProd(token=None)
except Done, e:
# ok
prod = None
except Missing, e:
prod = None
# last was a S operator which may End a Sequence, then ok
if hasattr(lastprod, 'mayEnd') and not lastprod.mayEnd:
wellformed = False
self._log.error(u'%s: %s' % (name, e))
except ParseError, e:
prod = None
wellformed = False
self._log.error(u'%s: %s' % (name, e))
else:
if prods[-1].optional:
prod = None
elif prod and prod.optional:
# ignore optional
continue
if prod and not prod.optional:
wellformed = False
self._log.error(u'%s: Missing token for production %r'
% (name, str(prod)))
break
elif len(prods) > 1:
# nested exhausted, next in parent
prods.pop()
else:
break
# trim S from end
seq.rstrip()
return wellformed, seq, store, tokens
class PreDef(object):
"""Predefined Prod definition for use in productions definition
for ProdParser instances.
"""
types = cssutils.cssproductions.CSSProductions
reHexcolor = re.compile(r'^\#(?:[0-9abcdefABCDEF]{3}|[0-9abcdefABCDEF]{6})$')
@staticmethod
def calc(toSeq=None, nextSor=False):
return Prod(name=u'calcfunction',
match=lambda t, v: u'calc(' == cssutils.helper.normalize(v),
toSeq=toSeq,
nextSor=nextSor)
@staticmethod
def char(name='char', char=u',', toSeq=None,
stop=False, stopAndKeep=False,
optional=True, nextSor=False):
"any CHAR"
return Prod(name=name, match=lambda t, v: v == char, toSeq=toSeq,
stop=stop, stopAndKeep=stopAndKeep, optional=optional,
nextSor=nextSor)
@staticmethod
def comma():
return PreDef.char(u'comma', u',')
@staticmethod
def dimension(nextSor=False, stop=False):
return Prod(name=u'dimension',
match=lambda t, v: t == PreDef.types.DIMENSION,
toSeq=lambda t, tokens: (t[0], cssutils.helper.normalize(t[1])),
stop=stop,
nextSor=nextSor)
@staticmethod
def function(toSeq=None, nextSor=False):
return Prod(name=u'function',
match=lambda t, v: t == PreDef.types.FUNCTION,
toSeq=toSeq,
nextSor=nextSor)
@staticmethod
def funcEnd(stop=False):
")"
return PreDef.char(u'end FUNC ")"', u')',
stop=stop)
@staticmethod
def hexcolor(stop=False, nextSor=False):
"#123 or #123456"
return Prod(name='HEX color',
match=lambda t, v: (
t == PreDef.types.HASH and
PreDef.reHexcolor.match(v)
),
stop=stop,
nextSor=nextSor)
@staticmethod
def ident(stop=False, toStore=None, nextSor=False):
return Prod(name=u'ident',
match=lambda t, v: t == PreDef.types.IDENT,
stop=stop,
toStore=toStore,
nextSor=nextSor)
@staticmethod
def number(stop=False, toSeq=None, nextSor=False):
return Prod(name=u'number',
match=lambda t, v: t == PreDef.types.NUMBER,
stop=stop,
toSeq=toSeq,
nextSor=nextSor)
@staticmethod
def percentage(stop=False, toSeq=None, nextSor=False):
return Prod(name=u'percentage',
match=lambda t, v: t == PreDef.types.PERCENTAGE,
stop=stop,
toSeq=toSeq,
nextSor=nextSor)
@staticmethod
def string(stop=False, nextSor=False):
"string delimiters are removed by default"
return Prod(name=u'string',
match=lambda t, v: t == PreDef.types.STRING,
toSeq=lambda t, tokens: (t[0], cssutils.helper.stringvalue(t[1])),
stop=stop,
nextSor=nextSor)
@staticmethod
def S(name=u'whitespace', toSeq=None, optional=False):
return Prod(name=name,
match=lambda t, v: t == PreDef.types.S,
toSeq=toSeq,
optional=optional,
mayEnd=True)
@staticmethod
def unary(stop=False, toSeq=None, nextSor=False):
"+ or -"
return Prod(name=u'unary +-', match=lambda t, v: v in (u'+', u'-'),
optional=True,
stop=stop,
toSeq=toSeq,
nextSor=nextSor)
@staticmethod
def uri(stop=False, nextSor=False):
"'url(' and ')' are removed and URI is stripped"
return Prod(name=u'URI',
match=lambda t, v: t == PreDef.types.URI,
toSeq=lambda t, tokens: (t[0], cssutils.helper.urivalue(t[1])),
stop=stop,
nextSor=nextSor)
@staticmethod
def unicode_range(stop=False, nextSor=False):
"u+123456-abc normalized to lower `u`"
return Prod(name='unicode-range',
match=lambda t, v: t == PreDef.types.UNICODE_RANGE,
toSeq=lambda t, tokens: (t[0], t[1].lower()),
stop=stop,
nextSor=nextSor
)
@staticmethod
def variable(toSeq=None, stop=False, nextSor=False):
return Prod(name=u'variable',
match=lambda t, v: u'var(' == cssutils.helper.normalize(v),
toSeq=toSeq,
stop=stop,
nextSor=nextSor)
# used for MarginRule for now:
@staticmethod
def unknownrule(name=u'@', toStore=None):
"""@rule dummy (matches ATKEYWORD to remove unknown rule tokens from
stream::
@x;
@x {...}
no nested yet!
"""
def rule(tokens):
saved = []
for t in tokens:
saved.append(t)
if (t[1] == u'}' or t[1] == u';'):
return cssutils.css.CSSUnknownRule(saved)
return Prod(name=name,
match=lambda t, v: t == u'ATKEYWORD',
toSeq=lambda t, tokens: (u'CSSUnknownRule',
rule(pushtoken(t, tokens))
),
toStore=toStore
)

791
libs/cssutils/profiles.py

@ -1,791 +0,0 @@
"""CSS profiles.
Profiles is based on code by Kevin D. Smith, orginally used as cssvalues,
thanks!
"""
__all__ = ['Profiles']
__docformat__ = 'restructuredtext'
__version__ = '$Id: cssproperties.py 1116 2008-03-05 13:52:23Z cthedot $'
import re
import types
class NoSuchProfileException(Exception):
"""Raised if no profile with given name is found"""
pass
# dummies, replaced in Profiles.addProfile
_fontRegexReplacements = {
'__FONT_FAMILY_SINGLE': lambda f: False,
'__FONT_WITH_1_FAMILY': lambda f: False
}
def _fontFamilyValidator(families):
"""Check if ``font-family`` value is valid, regex is too slow.
Splits on ``,`` and checks each family separately.
Somehow naive as font-family name could contain a "," but this is unlikely.
Still should be a TODO.
"""
match = _fontRegexReplacements['__FONT_FAMILY_SINGLE']
for f in families.split(u','):
if not match(f.strip()):
return False
return True
def _fontValidator(font):
"""Check if font value is valid, regex is too slow.
Checks everything before ``,`` on basic font value. Everything after should
be a valid font-family value.
"""
if u',' in font:
# split off until 1st family
font1, families2 = font.split(u',', 1)
else:
font1, families2 = font, None
if not _fontRegexReplacements['__FONT_WITH_1_FAMILY'](font1.strip()):
return False
if families2 and not _fontFamilyValidator(families2):
return False
return True
class Profiles(object):
"""
All profiles used for validation. ``cssutils.profile`` is a
preset object of this class and used by all properties for validation.
Predefined profiles are (use
:meth:`~cssutils.profiles.Profiles.propertiesByProfile` to
get a list of defined properties):
:attr:`~cssutils.profiles.Profiles.CSS_LEVEL_2`
Properties defined by CSS2.1
:attr:`~cssutils.profiles.Profiles.CSS3_BASIC_USER_INTERFACE`
Currently resize and outline properties only
:attr:`~cssutils.profiles.Profiles.CSS3_BOX`
Currently overflow related properties only
:attr:`~cssutils.profiles.Profiles.CSS3_COLOR`
CSS 3 color properties
:attr:`~cssutils.profiles.Profiles.CSS3_PAGED_MEDIA`
As defined at http://www.w3.org/TR/css3-page/ (at 090307)
Predefined macros are:
:attr:`~cssutils.profiles.Profiles._TOKEN_MACROS`
Macros containing the token values as defined to CSS2
:attr:`~cssutils.profiles.Profiles._MACROS`
Additional general macros.
If you want to redefine any of these macros do this in your custom
macros.
"""
CSS_LEVEL_2 = u'CSS Level 2.1'
CSS3_BACKGROUNDS_AND_BORDERS = u'CSS Backgrounds and Borders Module Level 3'
CSS3_BASIC_USER_INTERFACE = u'CSS3 Basic User Interface Module'
CSS3_BOX = CSS_BOX_LEVEL_3 = u'CSS Box Module Level 3'
CSS3_COLOR = CSS_COLOR_LEVEL_3 = u'CSS Color Module Level 3'
CSS3_FONTS = u'CSS Fonts Module Level 3'
CSS3_FONT_FACE = u'CSS Fonts Module Level 3 @font-face properties'
CSS3_PAGED_MEDIA = u'CSS3 Paged Media Module'
CSS3_TEXT = u'CSS Text Level 3'
_TOKEN_MACROS = {
'ident': r'[-]?{nmstart}{nmchar}*',
'name': r'{nmchar}+',
'nmstart': r'[_a-z]|{nonascii}|{escape}',
'nonascii': r'[^\0-\177]',
'unicode': r'\\[0-9a-f]{1,6}(\r\n|[ \n\r\t\f])?',
'escape': r'{unicode}|\\[ -~\200-\777]',
# 'escape': r'{unicode}|\\[ -~\200-\4177777]',
'int': r'[-]?\d+',
'nmchar': r'[\w-]|{nonascii}|{escape}',
'num': r'[-]?\d+|[-]?\d*\.\d+',
'positivenum': r'\d+|\d*\.\d+',
'number': r'{num}',
'string': r'{string1}|{string2}',
'string1': r'"(\\\"|[^\"])*"',
'uri': r'url\({w}({string}|(\\\)|[^\)])+){w}\)',
'string2': r"'(\\\'|[^\'])*'",
'nl': r'\n|\r\n|\r|\f',
'w': r'\s*',
}
_MACROS = {
'hexcolor': r'#[0-9a-f]{3}|#[0-9a-f]{6}',
'rgbcolor': r'rgb\({w}{int}{w}\,{w}{int}{w}\,{w}{int}{w}\)|rgb\({w}{num}%{w}\,{w}{num}%{w}\,{w}{num}%{w}\)',
'namedcolor': r'(transparent|orange|maroon|red|orange|yellow|olive|purple|fuchsia|white|lime|green|navy|blue|aqua|teal|black|silver|gray)',
'uicolor': r'(ActiveBorder|ActiveCaption|AppWorkspace|Background|ButtonFace|ButtonHighlight|ButtonShadow|ButtonText|CaptionText|GrayText|Highlight|HighlightText|InactiveBorder|InactiveCaption|InactiveCaptionText|InfoBackground|InfoText|Menu|MenuText|Scrollbar|ThreeDDarkShadow|ThreeDFace|ThreeDHighlight|ThreeDLightShadow|ThreeDShadow|Window|WindowFrame|WindowText)',
'color': r'{namedcolor}|{hexcolor}|{rgbcolor}|{uicolor}',
#'color': r'(maroon|red|orange|yellow|olive|purple|fuchsia|white|lime|green|navy|blue|aqua|teal|black|silver|gray|ActiveBorder|ActiveCaption|AppWorkspace|Background|ButtonFace|ButtonHighlight|ButtonShadow|ButtonText|CaptionText|GrayText|Highlight|HighlightText|InactiveBorder|InactiveCaption|InactiveCaptionText|InfoBackground|InfoText|Menu|MenuText|Scrollbar|ThreeDDarkShadow|ThreeDFace|ThreeDHighlight|ThreeDLightShadow|ThreeDShadow|Window|WindowFrame|WindowText)|#[0-9a-f]{3}|#[0-9a-f]{6}|rgb\({w}{int}{w},{w}{int}{w},{w}{int}{w}\)|rgb\({w}{num}%{w},{w}{num}%{w},{w}{num}%{w}\)',
'integer': r'{int}',
'length': r'0|{num}(em|ex|px|in|cm|mm|pt|pc)',
'positivelength': r'0|{positivenum}(em|ex|px|in|cm|mm|pt|pc)',
'angle': r'0|{num}(deg|grad|rad)',
'time': r'0|{num}m?s',
'frequency': r'0|{num}k?Hz',
'percentage': r'{num}%',
'shadow': '(inset)?{w}{length}{w}{length}{w}{length}?{w}{length}?{w}{color}?'
}
def __init__(self, log=None):
"""A few profiles are predefined."""
self._log = log
# macro cache
self._usedMacros = Profiles._TOKEN_MACROS.copy()
self._usedMacros.update(Profiles._MACROS.copy())
# to keep order, REFACTOR!
self._profileNames = []
# for reset if macro changes
self._rawProfiles = {}
# already compiled profiles: {profile: {property: checkfunc, ...}, ...}
self._profilesProperties = {}
self._defaultProfiles = None
self.addProfiles([(self.CSS_LEVEL_2,
properties[self.CSS_LEVEL_2],
macros[self.CSS_LEVEL_2]
),
(self.CSS3_BACKGROUNDS_AND_BORDERS,
properties[self.CSS3_BACKGROUNDS_AND_BORDERS],
macros[self.CSS3_BACKGROUNDS_AND_BORDERS]
),
(self.CSS3_BASIC_USER_INTERFACE,
properties[self.CSS3_BASIC_USER_INTERFACE],
macros[self.CSS3_BASIC_USER_INTERFACE]
),
(self.CSS3_BOX,
properties[self.CSS3_BOX],
macros[self.CSS3_BOX]
),
(self.CSS3_COLOR,
properties[self.CSS3_COLOR],
macros[self.CSS3_COLOR]
),
(self.CSS3_FONTS,
properties[self.CSS3_FONTS],
macros[self.CSS3_FONTS]
),
# new object for font-face only?
(self.CSS3_FONT_FACE,
properties[self.CSS3_FONT_FACE],
macros[self.CSS3_FONTS]
),
(self.CSS3_PAGED_MEDIA,
properties[self.CSS3_PAGED_MEDIA],
macros[self.CSS3_PAGED_MEDIA]
),
(self.CSS3_TEXT,
properties[self.CSS3_TEXT],
macros[self.CSS3_TEXT]
)
])
self.__update_knownNames()
def _expand_macros(self, dictionary, macros):
"""Expand macros in token dictionary"""
def macro_value(m):
return '(?:%s)' % macros[m.groupdict()['macro']]
for key, value in dictionary.items():
if not hasattr(value, '__call__'):
while re.search(r'{[a-z][a-z0-9-]*}', value):
value = re.sub(r'{(?P<macro>[a-z][a-z0-9-]*)}',
macro_value, value)
dictionary[key] = value
return dictionary
def _compile_regexes(self, dictionary):
"""Compile all regular expressions into callable objects"""
for key, value in dictionary.items():
# might be a function (font-family) as regex is too slow
if not hasattr(value, '__call__') and not isinstance(value,
types.FunctionType):
value = re.compile('^(?:%s)$' % value, re.I).match
dictionary[key] = value
return dictionary
def __update_knownNames(self):
self._knownNames = []
for properties in self._profilesProperties.values():
self._knownNames.extend(properties.keys())
def _getDefaultProfiles(self):
"If not explicitly set same as Profiles.profiles but in reverse order."
if not self._defaultProfiles:
return self.profiles
else:
return self._defaultProfiles
def _setDefaultProfiles(self, profiles):
"profiles may be a single or a list of profile names"
if isinstance(profiles, basestring):
self._defaultProfiles = (profiles,)
else:
self._defaultProfiles = profiles
defaultProfiles = property(_getDefaultProfiles,
_setDefaultProfiles,
doc=u"Names of profiles to use for validation."
u"To use e.g. the CSS2 profile set "
u"``cssutils.profile.defaultProfiles = "
u"cssutils.profile.CSS_LEVEL_2``")
profiles = property(lambda self: self._profileNames,
doc=u'Names of all profiles in order as defined.')
knownNames = property(lambda self: self._knownNames,
doc="All known property names of all profiles.")
def _resetProperties(self, newMacros=None):
"reset all props from raw values as changes in macros happened"
# base
macros = Profiles._TOKEN_MACROS.copy()
macros.update(Profiles._MACROS.copy())
# former
for profile in self._profileNames:
macros.update(self._rawProfiles[profile]['macros'])
# new
if newMacros:
macros.update(newMacros)
# reset properties
self._profilesProperties.clear()
for profile in self._profileNames:
properties = self._expand_macros(
# keep raw
self._rawProfiles[profile]['properties'].copy(),
macros)
self._profilesProperties[profile] = self._compile_regexes(properties)
# save
self._usedMacros = macros
def addProfiles(self, profiles):
"""Add a list of profiles at once. Useful as if profiles define custom
macros these are used in one go. Using `addProfile` instead my be
**very** slow instead.
"""
# add macros
for profile, properties, macros in profiles:
if macros:
self._usedMacros.update(macros)
self._rawProfiles[profile] = {'macros': macros.copy()}
# only add new properties
for profile, properties, macros in profiles:
self.addProfile(profile, properties.copy(), None)
def addProfile(self, profile, properties, macros=None):
"""Add a new profile with name `profile` (e.g. 'CSS level 2')
and the given `properties`.
:param profile:
the new `profile`'s name
:param properties:
a dictionary of ``{ property-name: propery-value }`` items where
property-value is a regex which may use macros defined in given
``macros`` or the standard macros Profiles.tokens and
Profiles.generalvalues.
``propery-value`` may also be a function which takes a single
argument which is the value to validate and which should return
True or False.
Any exceptions which may be raised during this custom validation
are reported or raised as all other cssutils exceptions depending
on cssutils.log.raiseExceptions which e.g during parsing normally
is False so the exceptions would be logged only.
:param macros:
may be used in the given properties definitions. There are some
predefined basic macros which may always be used in
:attr:`Profiles._TOKEN_MACROS` and :attr:`Profiles._MACROS`.
"""
if macros:
# check if known macros would change and if yes reset properties
if len(set(macros.keys()).intersection(self._usedMacros.keys())):
self._resetProperties(newMacros=macros)
else:
# no replacement, simply continue
self._usedMacros.update(macros)
else:
# might have been set by addProfiles before
try:
macros = self._rawProfiles[profile]['macros']
except KeyError, e:
macros = {}
# save name and raw props/macros if macros change to completely reset
self._profileNames.append(profile)
self._rawProfiles[profile] = {'properties': properties.copy(),
'macros': macros.copy()}
# prepare and save properties
properties = self._expand_macros(properties, self._usedMacros)
self._profilesProperties[profile] = self._compile_regexes(properties)
self.__update_knownNames()
# hack for font and font-family which are too slow with regexes
if '__FONT_WITH_1_FAMILY' in properties:
_fontRegexReplacements['__FONT_WITH_1_FAMILY'] = properties['__FONT_WITH_1_FAMILY']
if '__FONT_FAMILY_SINGLE' in properties:
_fontRegexReplacements['__FONT_FAMILY_SINGLE'] = properties['__FONT_FAMILY_SINGLE']
def removeProfile(self, profile=None, all=False):
"""Remove `profile` or remove `all` profiles.
If the removed profile used custom macros all remaining profiles
are reset to reflect the macro changes. This may be quite an expensive
operation!
:param profile:
profile name to remove
:param all:
if ``True`` removes all profiles to start with a clean state
:exceptions:
- :exc:`cssutils.profiles.NoSuchProfileException`:
If given `profile` cannot be found.
"""
if all:
self._profilesProperties.clear()
self._rawProfiles.clear()
del self._profileNames[:]
else:
reset = False
try:
if (self._rawProfiles[profile]['macros']):
reset = True
del self._profilesProperties[profile]
del self._rawProfiles[profile]
del self._profileNames[self._profileNames.index(profile)]
except KeyError:
raise NoSuchProfileException(u'No profile %r.' % profile)
else:
if reset:
# reset properties as macros were removed
self._resetProperties()
self.__update_knownNames()
def propertiesByProfile(self, profiles=None):
"""Generator: Yield property names, if no `profiles` is given all
profile's properties are used.
:param profiles:
a single profile name or a list of names.
"""
if not profiles:
profiles = self.profiles
elif isinstance(profiles, basestring):
profiles = (profiles, )
try:
for profile in sorted(profiles):
for name in sorted(self._profilesProperties[profile].keys()):
yield name
except KeyError, e:
raise NoSuchProfileException(e)
def validate(self, name, value):
"""Check if `value` is valid for given property `name` using **any**
profile.
:param name:
a property name
:param value:
a CSS value (string)
:returns:
if the `value` is valid for the given property `name` in any
profile
"""
for profile in self.profiles:
if name in self._profilesProperties[profile]:
try:
# custom validation errors are caught
r = bool(self._profilesProperties[profile][name](value))
except Exception, e:
# TODO: more specific exception?
# Validate should not be fatal though!
self._log.error(e, error=Exception)
r = False
if r:
return r
return False
def validateWithProfile(self, name, value, profiles=None):
"""Check if `value` is valid for given property `name` returning
``(valid, profile)``.
:param name:
a property name
:param value:
a CSS value (string)
:param profiles:
internal parameter used by Property.validate only
:returns:
``valid, matching, profiles`` where ``valid`` is if the `value`
is valid for the given property `name` in any profile,
``matching==True`` if it is valid in the given `profiles`
and ``profiles`` the profile names for which the value is valid
(or ``[]`` if not valid at all)
Example::
>>> cssutils.profile.defaultProfiles = cssutils.profile.CSS_LEVEL_2
>>> print cssutils.profile.validateWithProfile('color', 'rgba(1,1,1,1)')
(True, False, Profiles.CSS3_COLOR)
"""
if name not in self.knownNames:
return False, False, []
else:
if not profiles:
profiles = self.defaultProfiles
elif isinstance(profiles, basestring):
profiles = (profiles, )
for profilename in reversed(profiles):
# check given profiles
if name in self._profilesProperties[profilename]:
validate = self._profilesProperties[profilename][name]
try:
if validate(value):
return True, True, [profilename]
except Exception, e:
self._log.error(e, error=Exception)
for profilename in (p for p in self._profileNames
if p not in profiles):
# check remaining profiles as well
if name in self._profilesProperties[profilename]:
validate = self._profilesProperties[profilename][name]
try:
if validate(value):
return True, False, [profilename]
except Exception, e:
self._log.error(e, error=Exception)
names = []
for profilename, properties in self._profilesProperties.items():
# return profile to which name belongs
if name in properties.keys():
names.append(profilename)
names.sort()
return False, False, names
properties = {}
macros = {}
"""
Define some regular expression fragments that will be used as
macros within the CSS property value regular expressions.
"""
macros[Profiles.CSS_LEVEL_2] = {
'background-color': r'{color}|transparent|inherit',
'background-image': r'{uri}|none|inherit',
#'background-position': r'({percentage}|{length})(\s*({percentage}|{length}))?|((top|center|bottom)\s*(left|center|right)?)|((left|center|right)\s*(top|center|bottom)?)|inherit',
'background-position': r'({percentage}|{length}|left|center|right)(\s*({percentage}|{length}|top|center|bottom))?|((top|center|bottom)\s*(left|center|right)?)|((left|center|right)\s*(top|center|bottom)?)|inherit',
'background-repeat': r'repeat|repeat-x|repeat-y|no-repeat|inherit',
'background-attachment': r'scroll|fixed|inherit',
'shape': r'rect\(({w}({length}|auto}){w},){3}{w}({length}|auto){w}\)',
'counter': r'counter\({w}{ident}{w}(?:,{w}{list-style-type}{w})?\)',
'identifier': r'{ident}',
'family-name': r'{string}|{ident}({w}{ident})*',
'generic-family': r'serif|sans-serif|cursive|fantasy|monospace',
'absolute-size': r'(x?x-)?(small|large)|medium',
'relative-size': r'smaller|larger',
#[[ <family-name> | <generic-family> ] [, <family-name>| <generic-family>]* ] | inherit
#'font-family': r'(({family-name}|{generic-family})({w},{w}({family-name}|{generic-family}))*)|inherit',
# EXTREMELY SLOW REGEX
#'font-family': r'({family-name}({w},{w}{family-name})*)|inherit',
'font-size': r'{absolute-size}|{relative-size}|{positivelength}|{percentage}|inherit',
'font-style': r'normal|italic|oblique|inherit',
'font-variant': r'normal|small-caps|inherit',
'font-weight': r'normal|bold|bolder|lighter|[1-9]00|inherit',
'line-height': r'normal|{number}|{length}|{percentage}|inherit',
'list-style-image': r'{uri}|none|inherit',
'list-style-position': r'inside|outside|inherit',
'list-style-type': r'disc|circle|square|decimal|decimal-leading-zero|lower-roman|upper-roman|lower-greek|lower-(latin|alpha)|upper-(latin|alpha)|armenian|georgian|none|inherit',
'margin-width': r'{length}|{percentage}|auto',
'padding-width': r'{length}|{percentage}',
'specific-voice': r'{ident}',
'generic-voice': r'male|female|child',
'content': r'{string}|{uri}|{counter}|attr\({w}{ident}{w}\)|open-quote|close-quote|no-open-quote|no-close-quote',
'background-attrs': r'{background-color}|{background-image}|{background-repeat}|{background-attachment}|{background-position}',
'list-attrs': r'{list-style-type}|{list-style-position}|{list-style-image}',
'font-attrs': r'{font-style}|{font-variant}|{font-weight}',
'text-attrs': r'underline|overline|line-through|blink',
'overflow': r'visible|hidden|scroll|auto|inherit',
}
"""
Define the regular expressions for validation all CSS values
"""
properties[Profiles.CSS_LEVEL_2] = {
'azimuth': r'{angle}|(behind\s+)?(left-side|far-left|left|center-left|center|center-right|right|far-right|right-side)(\s+behind)?|behind|leftwards|rightwards|inherit',
'background-attachment': r'{background-attachment}',
'background-color': r'{background-color}',
'background-image': r'{background-image}',
'background-position': r'{background-position}',
'background-repeat': r'{background-repeat}',
# Each piece should only be allowed one time
'background': r'{background-attrs}(\s+{background-attrs})*|inherit',
'border-collapse': r'collapse|separate|inherit',
'border-spacing': r'{length}(\s+{length})?|inherit',
'bottom': r'{length}|{percentage}|auto|inherit',
'caption-side': r'top|bottom|inherit',
'clear': r'none|left|right|both|inherit',
'clip': r'{shape}|auto|inherit',
'color': r'{color}|inherit',
'content': r'none|normal|{content}(\s+{content})*|inherit',
'counter-increment': r'({ident}(\s+{integer})?)(\s+({ident}(\s+{integer})?))*|none|inherit',
'counter-reset': r'({ident}(\s+{integer})?)(\s+({ident}(\s+{integer})?))*|none|inherit',
'cue-after': r'{uri}|none|inherit',
'cue-before': r'{uri}|none|inherit',
'cue': r'({uri}|none|inherit){1,2}|inherit',
#'cursor': r'((({uri}{w},{w})*)?(auto|crosshair|default|pointer|move|(e|ne|nw|n|se|sw|s|w)-resize|text|wait|help|progress))|inherit',
'direction': r'ltr|rtl|inherit',
'display': r'inline|block|list-item|run-in|inline-block|table|inline-table|table-row-group|table-header-group|table-footer-group|table-row|table-column-group|table-column|table-cell|table-caption|none|inherit',
'elevation': r'{angle}|below|level|above|higher|lower|inherit',
'empty-cells': r'show|hide|inherit',
'float': r'left|right|none|inherit',
# regex too slow:
# 'font-family': r'{font-family}',
'font-family': _fontFamilyValidator,
'__FONT_FAMILY_SINGLE': r'{family-name}',
'font-size': r'{font-size}',
'font-style': r'{font-style}',
'font-variant': r'{font-variant}',
'font-weight': r'{font-weight}',
# regex too slow and wrong too:
# 'font': r'({font-attrs}\s+)*{font-size}({w}/{w}{line-height})?\s+{font-family}|caption|icon|menu|message-box|small-caption|status-bar|inherit',
'font': _fontValidator,
'__FONT_WITH_1_FAMILY': r'(({font-attrs}\s+)*{font-size}({w}/{w}{line-height})?\s+{family-name})|caption|icon|menu|message-box|small-caption|status-bar|inherit',
'height': r'{length}|{percentage}|auto|inherit',
'left': r'{length}|{percentage}|auto|inherit',
'letter-spacing': r'normal|{length}|inherit',
'line-height': r'{line-height}',
'list-style-image': r'{list-style-image}',
'list-style-position': r'{list-style-position}',
'list-style-type': r'{list-style-type}',
'list-style': r'{list-attrs}(\s+{list-attrs})*|inherit',
'margin-right': r'{margin-width}|inherit',
'margin-left': r'{margin-width}|inherit',
'margin-top': r'{margin-width}|inherit',
'margin-bottom': r'{margin-width}|inherit',
'margin': r'{margin-width}(\s+{margin-width}){0,3}|inherit',
'max-height': r'{length}|{percentage}|none|inherit',
'max-width': r'{length}|{percentage}|none|inherit',
'min-height': r'{length}|{percentage}|none|inherit',
'min-width': r'{length}|{percentage}|none|inherit',
'orphans': r'{integer}|inherit',
'overflow': r'{overflow}',
'padding-top': r'{padding-width}|inherit',
'padding-right': r'{padding-width}|inherit',
'padding-bottom': r'{padding-width}|inherit',
'padding-left': r'{padding-width}|inherit',
'padding': r'{padding-width}(\s+{padding-width}){0,3}|inherit',
'page-break-after': r'auto|always|avoid|left|right|inherit',
'page-break-before': r'auto|always|avoid|left|right|inherit',
'page-break-inside': r'avoid|auto|inherit',
'pause-after': r'{time}|{percentage}|inherit',
'pause-before': r'{time}|{percentage}|inherit',
'pause': r'({time}|{percentage}){1,2}|inherit',
'pitch-range': r'{number}|inherit',
'pitch': r'{frequency}|x-low|low|medium|high|x-high|inherit',
'play-during': r'{uri}(\s+(mix|repeat))*|auto|none|inherit',
'position': r'static|relative|absolute|fixed|inherit',
'quotes': r'({string}\s+{string})(\s+{string}\s+{string})*|none|inherit',
'richness': r'{number}|inherit',
'right': r'{length}|{percentage}|auto|inherit',
'speak-header': r'once|always|inherit',
'speak-numeral': r'digits|continuous|inherit',
'speak-punctuation': r'code|none|inherit',
'speak': r'normal|none|spell-out|inherit',
'speech-rate': r'{number}|x-slow|slow|medium|fast|x-fast|faster|slower|inherit',
'stress': r'{number}|inherit',
'table-layout': r'auto|fixed|inherit',
'text-align': r'left|right|center|justify|inherit',
'text-decoration': r'none|{text-attrs}(\s+{text-attrs})*|inherit',
'text-indent': r'{length}|{percentage}|inherit',
'text-transform': r'capitalize|uppercase|lowercase|none|inherit',
'top': r'{length}|{percentage}|auto|inherit',
'unicode-bidi': r'normal|embed|bidi-override|inherit',
'vertical-align': r'baseline|sub|super|top|text-top|middle|bottom|text-bottom|{percentage}|{length}|inherit',
'visibility': r'visible|hidden|collapse|inherit',
'voice-family': r'({specific-voice}|{generic-voice}{w},{w})*({specific-voice}|{generic-voice})|inherit',
'volume': r'{number}|{percentage}|silent|x-soft|soft|medium|loud|x-loud|inherit',
'white-space': r'normal|pre|nowrap|pre-wrap|pre-line|inherit',
'widows': r'{integer}|inherit',
'width': r'{length}|{percentage}|auto|inherit',
'word-spacing': r'normal|{length}|inherit',
'z-index': r'auto|{integer}|inherit',
}
macros[Profiles.CSS3_BACKGROUNDS_AND_BORDERS] = {
'border-style': 'none|hidden|dotted|dashed|solid|double|groove|ridge|inset|outset',
'border-width': '{length}|thin|medium|thick',
'b1': r'{border-width}?({w}{border-style})?({w}{color})?',
'b2': r'{border-width}?({w}{color})?({w}{border-style})?',
'b3': r'{border-style}?({w}{border-width})?({w}{color})?',
'b4': r'{border-style}?({w}{color})?({w}{border-width})?',
'b5': r'{color}?({w}{border-style})?({w}{border-width})?',
'b6': r'{color}?({w}{border-width})?({w}{border-style})?',
'border-attrs': r'{b1}|{b2}|{b3}|{b4}|{b5}|{b6}',
'border-radius-part': '({length}|{percentage})(\s+({length}|{percentage}))?'
}
properties[Profiles.CSS3_BACKGROUNDS_AND_BORDERS] = {
'border-color': r'({color}|transparent)(\s+({color}|transparent)){0,3}|inherit',
'border-style': r'{border-style}(\s+{border-style}){0,3}|inherit',
'border-top': r'{border-attrs}|inherit',
'border-right': r'{border-attrs}|inherit',
'border-bottom': r'{border-attrs}|inherit',
'border-left': r'{border-attrs}|inherit',
'border-top-color': r'{color}|transparent|inherit',
'border-right-color': r'{color}|transparent|inherit',
'border-bottom-color': r'{color}|transparent|inherit',
'border-left-color': r'{color}|transparent|inherit',
'border-top-style': r'{border-style}|inherit',
'border-right-style': r'{border-style}|inherit',
'border-bottom-style': r'{border-style}|inherit',
'border-left-style': r'{border-style}|inherit',
'border-top-width': r'{border-width}|inherit',
'border-right-width': r'{border-width}|inherit',
'border-bottom-width': r'{border-width}|inherit',
'border-left-width': r'{border-width}|inherit',
'border-width': r'{border-width}(\s+{border-width}){0,3}|inherit',
'border': r'{border-attrs}|inherit',
'border-top-right-radius': '{border-radius-part}',
'border-bottom-right-radius': '{border-radius-part}',
'border-bottom-left-radius': '{border-radius-part}',
'border-top-left-radius': '{border-radius-part}',
'border-radius': '({length}{w}|{percentage}{w}){1,4}(/{w}({length}{w}|{percentage}{w}){1,4})?',
'box-shadow': 'none|{shadow}({w},{w}{shadow})*',
}
# CSS3 Basic User Interface Module
macros[Profiles.CSS3_BASIC_USER_INTERFACE] = {
'border-style': macros[Profiles.CSS3_BACKGROUNDS_AND_BORDERS]['border-style'],
'border-width': macros[Profiles.CSS3_BACKGROUNDS_AND_BORDERS]['border-width'],
'outline-1': r'{outline-color}(\s+{outline-style})?(\s+{outline-width})?',
'outline-2': r'{outline-color}(\s+{outline-width})?(\s+{outline-style})?',
'outline-3': r'{outline-style}(\s+{outline-color})?(\s+{outline-width})?',
'outline-4': r'{outline-style}(\s+{outline-width})?(\s+{outline-color})?',
'outline-5': r'{outline-width}(\s+{outline-color})?(\s+{outline-style})?',
'outline-6': r'{outline-width}(\s+{outline-style})?(\s+{outline-color})?',
'outline-color': r'{color}|invert|inherit',
'outline-style': r'auto|{border-style}|inherit',
'outline-width': r'{border-width}|inherit',
}
properties[Profiles.CSS3_BASIC_USER_INTERFACE] = {
'box-sizing': r'content-box|border-box',
'cursor': r'((({uri}{w}({number}{w}{number}{w})?,{w})*)?(auto|default|none|context-menu|help|pointer|progress|wait|cell|crosshair|text|vertical-text|alias|copy|move|no-drop|not-allowed|(e|n|ne|nw|s|se|sw|w|ew|ns|nesw|nwse|col|row)-resize|all-scroll))|inherit',
'nav-index': r'auto|{number}|inherit',
'outline-color': r'{outline-color}',
'outline-style': r'{outline-style}',
'outline-width': r'{outline-width}',
'outline-offset': r'{length}|inherit',
#'outline': r'{outline-attrs}(\s+{outline-attrs})*|inherit',
'outline': r'{outline-1}|{outline-2}|{outline-3}|{outline-4}|{outline-5}|{outline-6}|inherit',
'resize': 'none|both|horizontal|vertical|inherit',
}
# CSS Box Module Level 3
macros[Profiles.CSS3_BOX] = {
'overflow': macros[Profiles.CSS_LEVEL_2]['overflow']
}
properties[Profiles.CSS3_BOX] = {
'overflow': '{overflow}{w}{overflow}?|inherit',
'overflow-x': '{overflow}|inherit',
'overflow-y': '{overflow}|inherit'
}
# CSS Color Module Level 3
macros[Profiles.CSS3_COLOR] = {
# orange and transparent in CSS 2.1
'namedcolor': r'(currentcolor|transparent|aqua|black|blue|fuchsia|gray|green|lime|maroon|navy|olive|orange|purple|red|silver|teal|white|yellow)',
# orange?
'rgbacolor': r'rgba\({w}{int}{w}\,{w}{int}{w}\,{w}{int}{w}\,{w}{num}{w}\)|rgba\({w}{num}%{w}\,{w}{num}%{w}\,{w}{num}%{w}\,{w}{num}{w}\)',
'hslcolor': r'hsl\({w}{int}{w}\,{w}{num}%{w}\,{w}{num}%{w}\)|hsla\({w}{int}{w}\,{w}{num}%{w}\,{w}{num}%{w}\,{w}{num}{w}\)',
'x11color': r'aliceblue|antiquewhite|aqua|aquamarine|azure|beige|bisque|black|blanchedalmond|blue|blueviolet|brown|burlywood|cadetblue|chartreuse|chocolate|coral|cornflowerblue|cornsilk|crimson|cyan|darkblue|darkcyan|darkgoldenrod|darkgray|darkgreen|darkgrey|darkkhaki|darkmagenta|darkolivegreen|darkorange|darkorchid|darkred|darksalmon|darkseagreen|darkslateblue|darkslategray|darkslategrey|darkturquoise|darkviolet|deeppink|deepskyblue|dimgray|dimgrey|dodgerblue|firebrick|floralwhite|forestgreen|fuchsia|gainsboro|ghostwhite|gold|goldenrod|gray|green|greenyellow|grey|honeydew|hotpink|indianred|indigo|ivory|khaki|lavender|lavenderblush|lawngreen|lemonchiffon|lightblue|lightcoral|lightcyan|lightgoldenrodyellow|lightgray|lightgreen|lightgrey|lightpink|lightsalmon|lightseagreen|lightskyblue|lightslategray|lightslategrey|lightsteelblue|lightyellow|lime|limegreen|linen|magenta|maroon|mediumaquamarine|mediumblue|mediumorchid|mediumpurple|mediumseagreen|mediumslateblue|mediumspringgreen|mediumturquoise|mediumvioletred|midnightblue|mintcream|mistyrose|moccasin|navajowhite|navy|oldlace|olive|olivedrab|orange|orangered|orchid|palegoldenrod|palegreen|paleturquoise|palevioletred|papayawhip|peachpuff|peru|pink|plum|powderblue|purple|red|rosybrown|royalblue|saddlebrown|salmon|sandybrown|seagreen|seashell|sienna|silver|skyblue|slateblue|slategray|slategrey|snow|springgreen|steelblue|tan|teal|thistle|tomato|turquoise|violet|wheat|white|whitesmoke|yellow|yellowgreen',
'uicolor': r'(ActiveBorder|ActiveCaption|AppWorkspace|Background|ButtonFace|ButtonHighlight|ButtonShadow|ButtonText|CaptionText|GrayText|Highlight|HighlightText|InactiveBorder|InactiveCaption|InactiveCaptionText|InfoBackground|InfoText|Menu|MenuText|Scrollbar|ThreeDDarkShadow|ThreeDFace|ThreeDHighlight|ThreeDLightShadow|ThreeDShadow|Window|WindowFrame|WindowText)',
'color': r'{namedcolor}|{hexcolor}|{rgbcolor}|{rgbacolor}|{hslcolor}|{x11color}|inherit',
}
properties[Profiles.CSS3_COLOR] = {
'opacity': r'{num}|inherit',
}
# CSS Fonts Module Level 3 http://www.w3.org/TR/css3-fonts/
macros[Profiles.CSS3_FONTS] = {
#'family-name': r'{string}|{ident}',
'family-name': r'{string}|{ident}({w}{ident})*',
'font-face-name': 'local\({w}{family-name}{w}\)',
'font-stretch-names': r'(ultra-condensed|extra-condensed|condensed|semi-condensed|semi-expanded|expanded|extra-expanded|ultra-expanded)',
'unicode-range': r'[uU]\+[0-9A-Fa-f?]{1,6}(\-[0-9A-Fa-f]{1,6})?'
}
properties[Profiles.CSS3_FONTS] = {
'font-size-adjust': r'{number}|none|inherit',
'font-stretch': r'normal|wider|narrower|{font-stretch-names}|inherit'
}
properties[Profiles.CSS3_FONT_FACE] = {
'font-family': '{family-name}',
'font-stretch': r'{font-stretch-names}',
'font-style': r'normal|italic|oblique',
'font-weight': r'normal|bold|[1-9]00',
'src': r'({uri}{w}(format\({w}{string}{w}(\,{w}{string}{w})*\))?|{font-face-name})({w},{w}({uri}{w}(format\({w}{string}{w}(\,{w}{string}{w})*\))?|{font-face-name}))*',
'unicode-range': '{unicode-range}({w},{w}{unicode-range})*'
}
# CSS3 Paged Media
macros[Profiles.CSS3_PAGED_MEDIA] = {
'page-size': 'a5|a4|a3|b5|b4|letter|legal|ledger',
'page-orientation': 'portrait|landscape',
'page-1': '{page-size}(?:{w}{page-orientation})?',
'page-2': '{page-orientation}(?:{w}{page-size})?',
'page-size-orientation': '{page-1}|{page-2}',
'pagebreak': 'auto|always|avoid|left|right'
}
properties[Profiles.CSS3_PAGED_MEDIA] = {
'fit': 'fill|hidden|meet|slice',
'fit-position': r'auto|(({percentage}|{length})(\s*({percentage}|{length}))?|((top|center|bottom)\s*(left|center|right)?)|((left|center|right)\s*(top|center|bottom)?))',
'image-orientation': 'auto|{angle}',
'orphans': r'{integer}|inherit',
'page': 'auto|{ident}',
'page-break-before': '{pagebreak}|inherit',
'page-break-after': '{pagebreak}|inherit',
'page-break-inside': 'auto|avoid|inherit',
'size': '({length}{w}){1,2}|auto|{page-size-orientation}',
'widows': r'{integer}|inherit'
}
macros[Profiles.CSS3_TEXT] = {
}
properties[Profiles.CSS3_TEXT] = {
'text-shadow': 'none|{shadow}({w},{w}{shadow})*',
}

428
libs/cssutils/sac.py

@ -1,428 +0,0 @@
#!/usr/bin/env python
"""A validating CSSParser"""
__all__ = ['CSSParser']
__docformat__ = 'restructuredtext'
__version__ = '$Id: parse.py 1754 2009-05-30 14:50:13Z cthedot $'
import helper
import codecs
import errorhandler
import os
import tokenize2
import urllib
import sys
class ErrorHandler(object):
"""Basic class for CSS error handlers.
This class class provides a default implementation ignoring warnings and
recoverable errors and throwing a SAXParseException for fatal errors.
If a CSS application needs to implement customized error handling, it must
extend this class and then register an instance with the CSS parser
using the parser's setErrorHandler method. The parser will then report all
errors and warnings through this interface.
The parser shall use this class instead of throwing an exception: it is
up to the application whether to throw an exception for different types of
errors and warnings. Note, however, that there is no requirement that the
parser continue to provide useful information after a call to fatalError
(in other words, a CSS driver class could catch an exception and report a
fatalError).
"""
def __init__(self):
self._log = errorhandler.ErrorHandler()
def error(self, exception, token=None):
self._log.error(exception, token, neverraise=True)
def fatal(self, exception, token=None):
self._log.fatal(exception, token)
def warn(self, exception, token=None):
self._log.warn(exception, token, neverraise=True)
class DocumentHandler(object):
"""
void endFontFace()
Receive notification of the end of a font face statement.
void endMedia(SACMediaList media)
Receive notification of the end of a media statement.
void endPage(java.lang.String name, java.lang.String pseudo_page)
Receive notification of the end of a media statement.
void importStyle(java.lang.String uri, SACMediaList media, java.lang.String defaultNamespaceURI)
Receive notification of a import statement in the style sheet.
void startFontFace()
Receive notification of the beginning of a font face statement.
void startMedia(SACMediaList media)
Receive notification of the beginning of a media statement.
void startPage(java.lang.String name, java.lang.String pseudo_page)
Receive notification of the beginning of a page statement.
"""
def __init__(self):
def log(msg):
sys.stderr.write('INFO\t%s\n' % msg)
self._log = log
def comment(self, text, line=None, col=None):
"Receive notification of a comment."
self._log("comment %r at [%s, %s]" % (text, line, col))
def startDocument(self, encoding):
"Receive notification of the beginning of a style sheet."
# source
self._log("startDocument encoding=%s" % encoding)
def endDocument(self, source=None, line=None, col=None):
"Receive notification of the end of a document."
self._log("endDocument EOF")
def importStyle(self, uri, media, name, line=None, col=None):
"Receive notification of a import statement in the style sheet."
# defaultNamespaceURI???
self._log("importStyle at [%s, %s]" % (line, col))
def namespaceDeclaration(self, prefix, uri, line=None, col=None):
"Receive notification of an unknown rule t-rule not supported by this parser."
# prefix might be None!
self._log("namespaceDeclaration at [%s, %s]" % (line, col))
def startSelector(self, selectors=None, line=None, col=None):
"Receive notification of the beginning of a rule statement."
# TODO selectorList!
self._log("startSelector at [%s, %s]" % (line, col))
def endSelector(self, selectors=None, line=None, col=None):
"Receive notification of the end of a rule statement."
self._log("endSelector at [%s, %s]" % (line, col))
def property(self, name, value='TODO', important=False, line=None, col=None):
"Receive notification of a declaration."
# TODO: value is LexicalValue?
self._log("property %r at [%s, %s]" % (name, line, col))
def ignorableAtRule(self, atRule, line=None, col=None):
"Receive notification of an unknown rule t-rule not supported by this parser."
self._log("ignorableAtRule %r at [%s, %s]" % (atRule, line, col))
class EchoHandler(DocumentHandler):
"Echos all input to property `out`"
def __init__(self):
super(EchoHandler, self).__init__()
self._out = []
out = property(lambda self: u''.join(self._out))
def startDocument(self, encoding):
super(EchoHandler, self).startDocument(encoding)
if u'utf-8' != encoding:
self._out.append(u'@charset "%s";\n' % encoding)
# def comment(self, text, line=None, col=None):
# self._out.append(u'/*%s*/' % text)
def importStyle(self, uri, media, name, line=None, col=None):
"Receive notification of a import statement in the style sheet."
# defaultNamespaceURI???
super(EchoHandler, self).importStyle(uri, media, name, line, col)
self._out.append(u'@import %s%s%s;\n' % (helper.string(uri),
u'%s ' % media if media else u'',
u'%s ' % name if name else u'')
)
def namespaceDeclaration(self, prefix, uri, line=None, col=None):
super(EchoHandler, self).namespaceDeclaration(prefix, uri, line, col)
self._out.append(u'@namespace %s%s;\n' % (u'%s ' % prefix if prefix else u'',
helper.string(uri)))
def startSelector(self, selectors=None, line=None, col=None):
super(EchoHandler, self).startSelector(selectors, line, col)
if selectors:
self._out.append(u', '.join(selectors))
self._out.append(u' {\n')
def endSelector(self, selectors=None, line=None, col=None):
self._out.append(u' }')
def property(self, name, value, important=False, line=None, col=None):
super(EchoHandler, self).property(name, value, line, col)
self._out.append(u' %s: %s%s;\n' % (name, value,
u' !important' if important else u''))
class Parser(object):
"""
java.lang.String getParserVersion()
Returns a string about which CSS language is supported by this parser.
boolean parsePriority(InputSource source)
Parse a CSS priority value (e.g.
LexicalUnit parsePropertyValue(InputSource source)
Parse a CSS property value.
void parseRule(InputSource source)
Parse a CSS rule.
SelectorList parseSelectors(InputSource source)
Parse a comma separated list of selectors.
void parseStyleDeclaration(InputSource source)
Parse a CSS style declaration (without '{' and '}').
void parseStyleSheet(InputSource source)
Parse a CSS document.
void parseStyleSheet(java.lang.String uri)
Parse a CSS document from a URI.
void setConditionFactory(ConditionFactory conditionFactory)
void setDocumentHandler(DocumentHandler handler)
Allow an application to register a document event handler.
void setErrorHandler(ErrorHandler handler)
Allow an application to register an error event handler.
void setLocale(java.util.Locale locale)
Allow an application to request a locale for errors and warnings.
void setSelectorFactory(SelectorFactory selectorFactory)
"""
def __init__(self, documentHandler=None, errorHandler=None):
self._tokenizer = tokenize2.Tokenizer()
if documentHandler:
self.setDocumentHandler(documentHandler)
else:
self.setDocumentHandler(DocumentHandler())
if errorHandler:
self.setErrorHandler(errorHandler)
else:
self.setErrorHandler(ErrorHandler())
def parseString(self, cssText, encoding=None):
if isinstance(cssText, str):
cssText = codecs.getdecoder('css')(cssText, encoding=encoding)[0]
tokens = self._tokenizer.tokenize(cssText, fullsheet=True)
def COMMENT(val, line, col):
self._handler.comment(val[2:-2], line, col)
def EOF(val, line, col):
self._handler.endDocument(val, line, col)
def simple(t):
map = {'COMMENT': COMMENT,
'S': lambda val, line, col: None,
'EOF': EOF}
type_, val, line, col = t
if type_ in map:
map[type_](val, line, col)
return True
else:
return False
# START PARSING
t = tokens.next()
type_, val, line, col = t
encoding = 'utf-8'
if 'CHARSET_SYM' == type_:
# @charset "encoding";
# S
encodingtoken = tokens.next()
semicolontoken = tokens.next()
if 'STRING' == type_:
encoding = helper.stringvalue(val)
# ;
if 'STRING' == encodingtoken[0] and semicolontoken:
encoding = helper.stringvalue(encodingtoken[1])
else:
self._errorHandler.fatal(u'Invalid @charset')
t = tokens.next()
type_, val, line, col = t
self._handler.startDocument(encoding)
while True:
start = (line, col)
try:
if simple(t):
pass
elif 'ATKEYWORD' == type_ or type_ in ('PAGE_SYM', 'MEDIA_SYM', 'FONT_FACE_SYM'):
atRule = [val]
braces = 0
while True:
# read till end ;
# TODO: or {}
t = tokens.next()
type_, val, line, col = t
atRule.append(val)
if u';' == val and not braces:
break
elif u'{' == val:
braces += 1
elif u'}' == val:
braces -= 1
if braces == 0:
break
self._handler.ignorableAtRule(u''.join(atRule), *start)
elif 'IMPORT_SYM' == type_:
# import URI or STRING media? name?
uri, media, name = None, None, None
while True:
t = tokens.next()
type_, val, line, col = t
if 'STRING' == type_:
uri = helper.stringvalue(val)
elif 'URI' == type_:
uri = helper.urivalue(val)
elif u';' == val:
break
if uri:
self._handler.importStyle(uri, media, name)
else:
self._errorHandler.error(u'Invalid @import'
u' declaration at %r'
% (start,))
elif 'NAMESPACE_SYM' == type_:
prefix, uri = None, None
while True:
t = tokens.next()
type_, val, line, col = t
if 'IDENT' == type_:
prefix = val
elif 'STRING' == type_:
uri = helper.stringvalue(val)
elif 'URI' == type_:
uri = helper.urivalue(val)
elif u';' == val:
break
if uri:
self._handler.namespaceDeclaration(prefix, uri, *start)
else:
self._errorHandler.error(u'Invalid @namespace'
u' declaration at %r'
% (start,))
else:
# CSSSTYLERULE
selector = []
selectors = []
while True:
# selectors[, selector]* {
if 'S' == type_:
selector.append(u' ')
elif simple(t):
pass
elif u',' == val:
selectors.append(u''.join(selector).strip())
selector = []
elif u'{' == val:
selectors.append(u''.join(selector).strip())
self._handler.startSelector(selectors, *start)
break
else:
selector.append(val)
t = tokens.next()
type_, val, line, col = t
end = None
while True:
# name: value [!important][;name: value [!important]]*;?
name, value, important = None, [], False
while True:
# name:
t = tokens.next()
type_, val, line, col = t
if 'S' == type_:
pass
elif simple(t):
pass
elif 'IDENT' == type_:
if name:
self._errorHandler.error('more than one property name', t)
else:
name = val
elif u':' == val:
if not name:
self._errorHandler.error('no property name', t)
break
elif u';' == val:
self._errorHandler.error('premature end of property', t)
end = val
break
elif u'}' == val:
if name:
self._errorHandler.error('premature end of property', t)
end = val
break
else:
self._errorHandler.error('unexpected property name token %r' % val, t)
while not u';' == end and not u'}' == end:
# value !;}
t = tokens.next()
type_, val, line, col = t
if 'S' == type_:
value.append(u' ')
elif simple(t):
pass
elif u'!' == val or u';' == val or u'}' == val:
value = ''.join(value).strip()
if not value:
self._errorHandler.error('premature end of property (no value)', t)
end = val
break
else:
value.append(val)
while u'!' == end:
# !important
t = tokens.next()
type_, val, line, col = t
if simple(t):
pass
elif u'IDENT' == type_ and not important:
important = True
elif u';' == val or u'}' == val:
end = val
break
else:
self._errorHandler.error('unexpected priority token %r' % val)
if name and value:
self._handler.property(name, value, important)
if u'}' == end:
self._handler.endSelector(selectors, line=line, col=col)
break
else:
# reset
end = None
else:
self._handler.endSelector(selectors, line=line, col=col)
t = tokens.next()
type_, val, line, col = t
except StopIteration:
break
def setDocumentHandler(self, handler):
"Allow an application to register a document event `handler`."
self._handler = handler
def setErrorHandler(self, handler):
"TODO"
self._errorHandler = handler

362
libs/cssutils/script.py

@ -1,362 +0,0 @@
"""classes and functions used by cssutils scripts
"""
__all__ = ['CSSCapture', 'csscombine']
__docformat__ = 'restructuredtext'
__version__ = '$Id: parse.py 1323 2008-07-06 18:13:57Z cthedot $'
import HTMLParser
import codecs
import cssutils
import errno
import logging
import os
import sys
import urllib2
import urlparse
try:
import cssutils.encutils as encutils
except ImportError:
try:
import encutils
except ImportError:
sys.exit("You need encutils from http://cthedot.de/encutils/")
# types of sheets in HTML
LINK = 0 # <link rel="stylesheet" type="text/css" href="..." [@title="..." @media="..."]/>
STYLE = 1 # <style type="text/css" [@title="..."]>...</style>
class CSSCaptureHTMLParser(HTMLParser.HTMLParser):
"""CSSCapture helper: Parse given data for link and style elements"""
curtag = u''
sheets = [] # (type, [atts, cssText])
def _loweratts(self, atts):
return dict([(a.lower(), v.lower()) for a, v in atts])
def handle_starttag(self, tag, atts):
if tag == u'link':
atts = self._loweratts(atts)
if u'text/css' == atts.get(u'type', u''):
self.sheets.append((LINK, atts))
elif tag == u'style':
# also get content of style
atts = self._loweratts(atts)
if u'text/css' == atts.get(u'type', u''):
self.sheets.append((STYLE, [atts, u'']))
self.curtag = tag
else:
# close as only intersting <style> cannot contain any elements
self.curtag = u''
def handle_data(self, data):
if self.curtag == u'style':
self.sheets[-1][1][1] = data # replace cssText
def handle_comment(self, data):
# style might have comment content, treat same as data
self.handle_data(data)
def handle_endtag(self, tag):
# close as style cannot contain any elements
self.curtag = u''
class CSSCapture(object):
"""
Retrieve all CSS stylesheets including embedded for a given URL.
Optional setting of User-Agent used for retrieval possible
to handle browser sniffing servers.
raises urllib2.HTTPError
"""
def __init__(self, ua=None, log=None, defaultloglevel=logging.INFO):
"""
initialize a new Capture object
ua
init User-Agent to use for requests
log
supply a log object which is used instead of the default
log which writes to sys.stderr
defaultloglevel
constant of logging package which defines the level of the
default log if no explicit log given
"""
self._ua = ua
if log:
self._log = log
else:
self._log = logging.getLogger('CSSCapture')
hdlr = logging.StreamHandler(sys.stderr)
formatter = logging.Formatter('%(message)s')
hdlr.setFormatter(formatter)
self._log.addHandler(hdlr)
self._log.setLevel(defaultloglevel)
self._log.debug(u'Using default log')
self._htmlparser = CSSCaptureHTMLParser()
self._cssparser = cssutils.CSSParser(log = self._log)
def _doRequest(self, url):
"""Do an HTTP request
Return (url, rawcontent)
url might have been changed by server due to redirects etc
"""
self._log.debug(u' CSSCapture._doRequest\n * URL: %s' % url)
req = urllib2.Request(url)
if self._ua:
req.add_header('User-agent', self._ua)
self._log.info(' * Using User-Agent: %s', self._ua)
try:
res = urllib2.urlopen(req)
except urllib2.HTTPError, e:
self._log.critical(' %s\n%s %s\n%s' % (
e.geturl(), e.code, e.msg, e.headers))
return None, None
# get real url
if url != res.geturl():
url = res.geturl()
self._log.info(' URL retrieved: %s', url)
return url, res
def _createStyleSheet(self, href=None,
media=None,
parentStyleSheet=None,
title=u'',
cssText=None,
encoding=None):
"""
Return CSSStyleSheet read from href or if cssText is given use that.
encoding
used if inline style found, same as self.docencoding
"""
if cssText is None:
encoding, enctype, cssText = cssutils.util._readUrl(href, parentEncoding=self.docencoding)
encoding = None # already decoded???
sheet = self._cssparser.parseString(cssText, href=href, media=media, title=title,
encoding=encoding)
if not sheet:
return None
else:
self._log.info(u' %s\n' % sheet)
self._nonparsed[sheet] = cssText
return sheet
def _findStyleSheets(self, docurl, doctext):
"""
parse text for stylesheets
fills stylesheetlist with all found StyleSheets
docurl
to build a full url of found StyleSheets @href
doctext
to parse
"""
# TODO: ownerNode should be set to the <link> node
self._htmlparser.feed(doctext)
for typ, data in self._htmlparser.sheets:
sheet = None
if LINK == typ:
self._log.info(u'+ PROCESSING <link> %r' % data)
atts = data
href = urlparse.urljoin(docurl, atts.get(u'href', None))
sheet = self._createStyleSheet(href=href,
media=atts.get(u'media', None),
title=atts.get(u'title', None))
elif STYLE == typ:
self._log.info(u'+ PROCESSING <style> %r' % data)
atts, cssText = data
sheet = self._createStyleSheet(cssText=cssText,
href = docurl,
media=atts.get(u'media', None),
title=atts.get(u'title', None),
encoding=self.docencoding)
if sheet:
sheet._href = None # inline have no href!
print sheet.cssText
if sheet:
self.stylesheetlist.append(sheet)
self._doImports(sheet, base=docurl)
def _doImports(self, parentStyleSheet, base=None):
"""
handle all @import CSS stylesheet recursively
found CSS stylesheets are appended to stylesheetlist
"""
# TODO: only if not parsed these have to be read extra!
for rule in parentStyleSheet.cssRules:
if rule.type == rule.IMPORT_RULE:
self._log.info(u'+ PROCESSING @import:')
self._log.debug(u' IN: %s\n' % parentStyleSheet.href)
sheet = rule.styleSheet
href = urlparse.urljoin(base, rule.href)
if sheet:
self._log.info(u' %s\n' % sheet)
self.stylesheetlist.append(sheet)
self._doImports(sheet, base=href)
def capture(self, url):
"""
Capture all stylesheets at given URL's HTML document.
Any HTTPError is raised to caller.
url
to capture CSS from
Returns ``cssutils.stylesheets.StyleSheetList``.
"""
self._log.info(u'\nCapturing CSS from URL:\n %s\n', url)
self._nonparsed = {}
self.stylesheetlist = cssutils.stylesheets.StyleSheetList()
# used to save inline styles
scheme, loc, path, query, fragment = urlparse.urlsplit(url)
self._filename = os.path.basename(path)
# get url content
url, res = self._doRequest(url)
if not res:
sys.exit(1)
rawdoc = res.read()
self.docencoding = encutils.getEncodingInfo(
res, rawdoc, log=self._log).encoding
self._log.info(u'\nUsing Encoding: %s\n', self.docencoding)
doctext = rawdoc.decode(self.docencoding)
# fill list of stylesheets and list of raw css
self._findStyleSheets(url, doctext)
return self.stylesheetlist
def saveto(self, dir, saveraw=False, minified=False):
"""
saves css in "dir" in the same layout as on the server
internal stylesheets are saved as "dir/__INLINE_STYLE__.html.css"
dir
directory to save files to
saveparsed
save literal CSS from server or save the parsed CSS
minified
save minified CSS
Both parsed and minified (which is also parsed of course) will
loose information which cssutils is unable to understand or where
it is simple buggy. You might to first save the raw version before
parsing of even minifying it.
"""
msg = 'parsed'
if saveraw:
msg = 'raw'
if minified:
cssutils.ser.prefs.useMinified()
msg = 'minified'
inlines = 0
for i, sheet in enumerate(self.stylesheetlist):
url = sheet.href
if not url:
inlines += 1
url = u'%s_INLINE_%s.css' % (self._filename, inlines)
# build savepath
scheme, loc, path, query, fragment = urlparse.urlsplit(url)
# no absolute path
if path and path.startswith('/'):
path = path[1:]
path = os.path.normpath(path)
path, fn = os.path.split(path)
savepath = os.path.join(dir, path)
savefn = os.path.join(savepath, fn)
try:
os.makedirs(savepath)
except OSError, e:
if e.errno != errno.EEXIST:
raise e
self._log.debug(u'Path "%s" already exists.', savepath)
self._log.info(u'SAVING %s, %s %r' % (i+1, msg, savefn))
sf = open(savefn, 'wb')
if saveraw:
cssText = self._nonparsed[sheet]
uf = codecs.getwriter('css')(sf)
uf.write(cssText)
else:
sf.write(sheet.cssText)
sf.close()
def csscombine(path=None, url=None, cssText=None, href=None,
sourceencoding=None, targetencoding=None,
minify=True, resolveVariables=True):
"""Combine sheets referred to by @import rules in given CSS proxy sheet
into a single new sheet.
:returns: combined cssText, normal or minified
:Parameters:
`path` or `url` or `cssText` + `href`
path or URL to a CSSStyleSheet or a cssText of a sheet which imports
other sheets which are then combined into one sheet.
`cssText` normally needs `href` to be able to resolve relative
imports.
`sourceencoding` = 'utf-8'
explicit encoding of the source proxysheet
`targetencoding`
encoding of the combined stylesheet
`minify` = True
defines if the combined sheet should be minified, in this case
comments are not parsed at all!
`resolveVariables` = True
defines if variables in combined sheet should be resolved
"""
cssutils.log.info(u'Combining files from %r' % url,
neverraise=True)
if sourceencoding is not None:
cssutils.log.info(u'Using source encoding %r' % sourceencoding,
neverraise=True)
parser = cssutils.CSSParser(parseComments=not minify)
if path and not cssText:
src = parser.parseFile(path, encoding=sourceencoding)
elif url:
src = parser.parseUrl(url, encoding=sourceencoding)
elif cssText:
src = parser.parseString(cssText, href=href, encoding=sourceencoding)
else:
sys.exit('Path or URL must be given')
result = cssutils.resolveImports(src)
result.encoding = targetencoding
cssutils.log.info(u'Using target encoding: %r' % targetencoding, neverraise=True)
oldser = cssutils.ser
cssutils.setSerializer(cssutils.serialize.CSSSerializer())
if minify:
cssutils.ser.prefs.useMinified()
cssutils.ser.prefs.resolveVariables = resolveVariables
cssText = result.cssText
cssutils.setSerializer(oldser)
return cssText

4
libs/cssutils/scripts/__init__.py

@ -1,4 +0,0 @@
from csscombine import csscombine
__all__ = ["csscapture", "csscombine", "cssparse"]

69
libs/cssutils/scripts/csscapture.py

@ -1,69 +0,0 @@
#!/usr/bin/env python
"""Retrieve all CSS stylesheets including embedded for a given URL.
Retrieve as StyleSheetList or save to disk - raw, parsed or minified version.
TODO:
- maybe use DOM 3 load/save?
- logger class which handles all cases when no log is given...
- saveto: why does urllib2 hang?
"""
__all__ = ['CSSCapture']
__docformat__ = 'restructuredtext'
__version__ = '$Id$'
from cssutils.script import CSSCapture
import logging
import optparse
import sys
def main(args=None):
usage = "usage: %prog [options] URL"
parser = optparse.OptionParser(usage=usage)
parser.add_option('-d', '--debug', action='store_true', dest='debug',
help='show debug messages during capturing')
parser.add_option('-m', '--minified', action='store_true', dest='minified',
help='saves minified version of captured files')
parser.add_option('-n', '--notsave', action='store_true', dest='notsave',
help='if given files are NOT saved, only log is written')
# parser.add_option('-r', '--saveraw', action='store_true', dest='saveraw',
# help='if given saves raw css otherwise cssutils\' parsed files')
parser.add_option('-s', '--saveto', action='store', dest='saveto',
help='saving retrieved files to "saveto", defaults to "_CSSCapture_SAVED"')
parser.add_option('-u', '--useragent', action='store', dest='ua',
help='useragent to use for request of URL, default is urllib2s default')
options, url = parser.parse_args()
# TODO:
options.saveraw = False
if not url:
parser.error('no URL given')
else:
url = url[0]
if options.debug:
level = logging.DEBUG
else:
level = logging.INFO
# START
c = CSSCapture(ua=options.ua, defaultloglevel=level)
stylesheetlist = c.capture(url)
if options.notsave is None or not options.notsave:
if options.saveto:
saveto = options.saveto
else:
saveto = u'_CSSCapture_SAVED'
c.saveto(saveto, saveraw=options.saveraw, minified=options.minified)
else:
for i, s in enumerate(stylesheetlist):
print u'''%s.
encoding: %r
title: %r
href: %r''' % (i + 1, s.encoding, s.title, s.href)
if __name__ == "__main__":
sys.exit(main())

94
libs/cssutils/scripts/csscombine.py

@ -1,94 +0,0 @@
#!/usr/bin/env python
"""Combine all sheets referred to a given CSS *proxy* sheet
into a single new sheet.
- no ``url()`` values are adjusted so currently when using relative references
for e.g. images it is best to have all sheets in a single folder
- in @import rules only relative paths do work for now but should be used
anyway
- messages are send to stderr
- output to stdout.
Example::
csscombine sheets\csscombine-proxy.css -m -t ascii -s utf-8
1>combined.css 2>log.txt
results in log.txt::
COMBINING sheets/csscombine-proxy.css
USING SOURCE ENCODING: css
* PROCESSING @import sheets\csscombine-1.css
* PROCESSING @import sheets\csscombine-2.css
INFO Nested @imports are not combined: @import "1.css";
SETTING TARGET ENCODING: ascii
and combined.css::
@charset "ascii";@import"1.css";@namespaces2"uri";s2|sheet-1{top:1px}s2|sheet-2{top:2px}proxy{top:3px}
or without option -m::
@charset "ascii";
@import "1.css";
@namespace s2 "uri";
@namespace other "other";
/* proxy sheet were imported sheets should be combined */
/* non-ascii chars: \F6 \E4 \FC */
/* @import "csscombine-1.css"; */
/* combined sheet 1 */
s2|sheet-1 {
top: 1px
}
/* @import url(csscombine-2.css); */
/* combined sheet 2 */
s2|sheet-2 {
top: 2px
}
proxy {
top: 3px
}
"""
__all__ = ['csscombine']
__docformat__ = 'restructuredtext'
__version__ = '$Id$'
from cssutils.script import csscombine
import optparse
import sys
def main(args=None):
usage = "usage: %prog [options] [path]"
parser = optparse.OptionParser(usage=usage)
parser.add_option('-u', '--url', action='store',
dest='url',
help='URL to parse (path is ignored if URL given)')
parser.add_option('-s', '--sourceencoding', action='store',
dest='sourceencoding',
help='encoding of input, defaulting to "css". If given overwrites other encoding information like @charset declarations')
parser.add_option('-t', '--targetencoding', action='store',
dest='targetencoding',
help='encoding of output, defaulting to "UTF-8"', default='utf-8')
parser.add_option('-m', '--minify', action='store_true', dest='minify',
default=False,
help='saves minified version of combined files, defaults to False')
options, path = parser.parse_args()
if options.url:
print csscombine(url=options.url,
sourceencoding=options.sourceencoding,
targetencoding=options.targetencoding,
minify=options.minify)
elif path:
print csscombine(path=path[0],
sourceencoding=options.sourceencoding,
targetencoding=options.targetencoding,
minify=options.minify)
else:
parser.error('no path or URL (-u) given')
if __name__ == '__main__':
sys.exit(main())

62
libs/cssutils/scripts/cssparse.py

@ -1,62 +0,0 @@
#!/usr/bin/env python
"""utility script to parse given filenames or string
"""
__docformat__ = 'restructuredtext'
__version__ = '$Id$'
import cssutils
import logging
import optparse
import sys
def main(args=None):
"""
Parses given filename(s) or string or URL (using optional encoding) and
prints the parsed style sheet to stdout.
Redirect stdout to save CSS. Redirect stderr to save parser log infos.
"""
usage = """usage: %prog [options] filename1.css [filename2.css ...]
[>filename_combined.css] [2>parserinfo.log] """
p = optparse.OptionParser(usage=usage)
p.add_option('-s', '--string', action='store_true', dest='string',
help='parse given string')
p.add_option('-u', '--url', action='store', dest='url',
help='parse given url')
p.add_option('-e', '--encoding', action='store', dest='encoding',
help='encoding of the file or override encoding found')
p.add_option('-m', '--minify', action='store_true', dest='minify',
help='minify parsed CSS', default=False)
p.add_option('-d', '--debug', action='store_true', dest='debug',
help='activate debugging output')
(options, params) = p.parse_args(args)
if not params and not options.url:
p.error("no filename given")
if options.debug:
p = cssutils.CSSParser(loglevel=logging.DEBUG)
else:
p = cssutils.CSSParser()
if options.minify:
cssutils.ser.prefs.useMinified()
if options.string:
sheet = p.parseString(u''.join(params), encoding=options.encoding)
print sheet.cssText
elif options.url:
sheet = p.parseUrl(options.url, encoding=options.encoding)
print sheet.cssText
else:
for filename in params:
sys.stderr.write('=== CSS FILE: "%s" ===\n' % filename)
sheet = p.parseFile(filename, encoding=options.encoding)
print sheet.cssText
print
sys.stderr.write('\n')
if __name__ == "__main__":
sys.exit(main())

1138
libs/cssutils/serialize.py

File diff suppressed because it is too large

15
libs/cssutils/settings.py

@ -1,15 +0,0 @@
"""Experimental settings for special stuff."""
def set(key, value):
"""Call to enable special settings:
('DXImageTransform.Microsoft', True)
enable support for parsing special MS only filter values
Clears the tokenizer cache which holds the compiled productions!
"""
if key == 'DXImageTransform.Microsoft' and value == True:
import cssproductions
import tokenize2
tokenize2._TOKENIZER_CACHE.clear()
cssproductions.PRODUCTIONS.insert(1, cssproductions._DXImageTransform)

11
libs/cssutils/stylesheets/__init__.py

@ -1,11 +0,0 @@
"""Implements Document Object Model Level 2 Style Sheets
http://www.w3.org/TR/2000/PR-DOM-Level-2-Style-20000927/stylesheets.html
"""
__all__ = ['MediaList', 'MediaQuery', 'StyleSheet', 'StyleSheetList']
__docformat__ = 'restructuredtext'
__version__ = '$Id$'
from medialist import *
from mediaquery import *
from stylesheet import *
from stylesheetlist import *

235
libs/cssutils/stylesheets/medialist.py

@ -1,235 +0,0 @@
"""MediaList implements DOM Level 2 Style Sheets MediaList.
TODO:
- delete: maybe if deleting from all, replace *all* with all others?
- is unknown media an exception?
"""
__all__ = ['MediaList']
__docformat__ = 'restructuredtext'
__version__ = '$Id$'
from cssutils.css import csscomment
from mediaquery import MediaQuery
import cssutils
import xml.dom
class MediaList(cssutils.util.Base, cssutils.util.ListSeq):
"""Provides the abstraction of an ordered collection of media,
without defining or constraining how this collection is
implemented.
A single media in the list is an instance of :class:`MediaQuery`.
An empty list is the same as a list that contains the medium "all".
Format from CSS2.1::
medium [ COMMA S* medium ]*
New format with :class:`MediaQuery`::
<media_query> [, <media_query> ]*
"""
def __init__(self, mediaText=None, parentRule=None, readonly=False):
"""
:param mediaText:
Unicodestring of parsable comma separared media
or a (Python) list of media.
:param parentRule:
CSSRule this medialist is used in, e.g. an @import or @media.
:param readonly:
Not used yet.
"""
super(MediaList, self).__init__()
self._wellformed = False
if isinstance(mediaText, list):
mediaText = u','.join(mediaText)
self._parentRule = parentRule
if mediaText:
self.mediaText = mediaText
self._readonly = readonly
def __repr__(self):
return "cssutils.stylesheets.%s(mediaText=%r)" % (
self.__class__.__name__, self.mediaText)
def __str__(self):
return "<cssutils.stylesheets.%s object mediaText=%r at 0x%x>" % (
self.__class__.__name__, self.mediaText, id(self))
length = property(lambda self: len(self),
doc="The number of media in the list (DOM readonly).")
def _getMediaText(self):
return cssutils.ser.do_stylesheets_medialist(self)
def _setMediaText(self, mediaText):
"""
:param mediaText:
simple value or comma-separated list of media
:exceptions:
- - :exc:`~xml.dom.SyntaxErr`:
Raised if the specified string value has a syntax error and is
unparsable.
- - :exc:`~xml.dom.NoModificationAllowedErr`:
Raised if this media list is readonly.
"""
self._checkReadonly()
wellformed = True
tokenizer = self._tokenize2(mediaText)
newseq = []
expected = None
while True:
# find all upto and including next ",", EOF or nothing
mqtokens = self._tokensupto2(tokenizer, listseponly=True)
if mqtokens:
if self._tokenvalue(mqtokens[-1]) == ',':
expected = mqtokens.pop()
else:
expected = None
mq = MediaQuery(mqtokens)
if mq.wellformed:
newseq.append(mq)
else:
wellformed = False
self._log.error(u'MediaList: Invalid MediaQuery: %s' %
self._valuestr(mqtokens))
else:
break
# post condition
if expected:
wellformed = False
self._log.error(u'MediaList: Cannot end with ",".')
if wellformed:
del self[:]
for mq in newseq:
self.appendMedium(mq)
self._wellformed = True
mediaText = property(_getMediaText, _setMediaText,
doc="The parsable textual representation of the media list.")
def __prepareset(self, newMedium):
# used by appendSelector and __setitem__
self._checkReadonly()
if not isinstance(newMedium, MediaQuery):
newMedium = MediaQuery(newMedium)
if newMedium.wellformed:
return newMedium
def __setitem__(self, index, newMedium):
"""Overwriting ListSeq.__setitem__
Any duplicate items are **not yet** removed.
"""
newMedium = self.__prepareset(newMedium)
if newMedium:
self.seq[index] = newMedium
# TODO: remove duplicates?
def appendMedium(self, newMedium):
"""Add the `newMedium` to the end of the list.
If the `newMedium` is already used, it is first removed.
:param newMedium:
a string or a :class:`~cssutils.stylesheets.MediaQuery`
:returns: Wellformedness of `newMedium`.
:exceptions:
- :exc:`~xml.dom.InvalidCharacterErr`:
If the medium contains characters that are invalid in the
underlying style language.
- :exc:`~xml.dom.InvalidModificationErr`:
If mediaText is "all" and a new medium is tried to be added.
Exception is "handheld" which is set in any case (Opera does handle
"all, handheld" special, this special case might be removed in the
future).
- :exc:`~xml.dom.NoModificationAllowedErr`:
Raised if this list is readonly.
"""
newMedium = self.__prepareset(newMedium)
if newMedium:
mts = [self._normalize(mq.mediaType) for mq in self]
newmt = self._normalize(newMedium.mediaType)
if newmt in mts:
self.deleteMedium(newmt)
self.seq.append(newMedium)
elif u'all' == newmt:
# remove all except handheld (Opera)
h = None
for mq in self:
if mq.mediaType == u'handheld':
h = mq
del self[:]
self.seq.append(newMedium)
if h:
self.append(h)
elif u'all' in mts:
if u'handheld' == newmt:
self.seq.append(newMedium)
self._log.info(u'MediaList: Already specified "all" but still setting new medium: %r' %
newMedium, error=xml.dom.InvalidModificationErr, neverraise=True)
else:
self._log.info(u'MediaList: Ignoring new medium %r as already specified "all" (set ``mediaText`` instead).' %
newMedium, error=xml.dom.InvalidModificationErr)
else:
self.seq.append(newMedium)
return True
else:
return False
def append(self, newMedium):
"Same as :meth:`appendMedium`."
self.appendMedium(newMedium)
def deleteMedium(self, oldMedium):
"""Delete a medium from the list.
:param oldMedium:
delete this medium from the list.
:exceptions:
- :exc:`~xml.dom.NotFoundErr`:
Raised if `oldMedium` is not in the list.
- :exc:`~xml.dom.NoModificationAllowedErr`:
Raised if this list is readonly.
"""
self._checkReadonly()
oldMedium = self._normalize(oldMedium)
for i, mq in enumerate(self):
if self._normalize(mq.mediaType) == oldMedium:
del self[i]
break
else:
self._log.error(u'"%s" not in this MediaList' % oldMedium,
error=xml.dom.NotFoundErr)
def item(self, index):
"""Return the mediaType of the `index`'th element in the list.
If `index` is greater than or equal to the number of media in the
list, returns ``None``.
"""
try:
return self[index].mediaType
except IndexError:
return None
parentRule = property(lambda self: self._parentRule,
doc=u"The CSSRule (e.g. an @media or @import rule "
u"this list is part of or None")
wellformed = property(lambda self: self._wellformed)

207
libs/cssutils/stylesheets/mediaquery.py

@ -1,207 +0,0 @@
"""Implements a DOM for MediaQuery, see
http://www.w3.org/TR/css3-mediaqueries/.
A cssutils implementation, not defined in official DOM.
"""
__all__ = ['MediaQuery']
__docformat__ = 'restructuredtext'
__version__ = '$Id$'
import cssutils
import re
import xml.dom
class MediaQuery(cssutils.util.Base):
"""
A Media Query consists of one of :const:`MediaQuery.MEDIA_TYPES`
and one or more expressions involving media features.
Format::
media_query: [[only | not]? <media_type> [ and <expression> ]*]
| <expression> [ and <expression> ]*
expression: ( <media_feature> [: <value>]? )
media_type: all | braille | handheld | print |
projection | speech | screen | tty | tv | embossed
media_feature: width | min-width | max-width
| height | min-height | max-height
| device-width | min-device-width | max-device-width
| device-height | min-device-height | max-device-height
| device-aspect-ratio | min-device-aspect-ratio | max-device-aspect-ratio
| color | min-color | max-color
| color-index | min-color-index | max-color-index
| monochrome | min-monochrome | max-monochrome
| resolution | min-resolution | max-resolution
| scan | grid
"""
MEDIA_TYPES = [u'all', u'braille', u'embossed', u'handheld',
u'print', u'projection', u'screen', u'speech', u'tty', u'tv']
# From the HTML spec (see MediaQuery):
# "[...] character that isn't a US ASCII letter [a-zA-Z] (Unicode
# decimal 65-90, 97-122), digit [0-9] (Unicode hex 30-39), or hyphen (45)."
# so the following is a valid mediaType
__mediaTypeMatch = re.compile(ur'^[-a-zA-Z0-9]+$', re.U).match
def __init__(self, mediaText=None, readonly=False):
"""
:param mediaText:
unicodestring of parsable media
"""
super(MediaQuery, self).__init__()
self.seq = []
self._mediaType = u''
if mediaText:
self.mediaText = mediaText # sets self._mediaType too
self._readonly = readonly
def __repr__(self):
return "cssutils.stylesheets.%s(mediaText=%r)" % (
self.__class__.__name__, self.mediaText)
def __str__(self):
return "<cssutils.stylesheets.%s object mediaText=%r at 0x%x>" % (
self.__class__.__name__, self.mediaText, id(self))
def _getMediaText(self):
return cssutils.ser.do_stylesheets_mediaquery(self)
def _setMediaText(self, mediaText):
"""
:param mediaText:
a single media query string, e.g. ``print and (min-width: 25cm)``
:exceptions:
- :exc:`~xml.dom.SyntaxErr`:
Raised if the specified string value has a syntax error and is
unparsable.
- :exc:`~xml.dom.InvalidCharacterErr`:
Raised if the given mediaType is unknown.
- :exc:`~xml.dom.NoModificationAllowedErr`:
Raised if this media query is readonly.
"""
self._checkReadonly()
tokenizer = self._tokenize2(mediaText)
if not tokenizer:
self._log.error(u'MediaQuery: No MediaText given.')
else:
# for closures: must be a mutable
new = {'mediatype': None,
'wellformed': True }
def _ident_or_dim(expected, seq, token, tokenizer=None):
# only|not or mediatype or and
val = self._tokenvalue(token)
nval = self._normalize(val)
if expected.endswith('mediatype'):
if nval in (u'only', u'not'):
# only or not
seq.append(val)
return 'mediatype'
else:
# mediatype
new['mediatype'] = val
seq.append(val)
return 'and'
elif 'and' == nval and expected.startswith('and'):
seq.append(u'and')
return 'feature'
else:
new['wellformed'] = False
self._log.error(
u'MediaQuery: Unexpected syntax.', token=token)
return expected
def _char(expected, seq, token, tokenizer=None):
# starting a feature which basically is a CSS Property
# but may simply be a property name too
val = self._tokenvalue(token)
if val == u'(' and expected == 'feature':
proptokens = self._tokensupto2(
tokenizer, funcendonly=True)
if proptokens and u')' == self._tokenvalue(proptokens[-1]):
proptokens.pop()
property = cssutils.css.Property(_mediaQuery=True)
property.cssText = proptokens
seq.append(property)
return 'and or EOF'
else:
new['wellformed'] = False
self._log.error(
u'MediaQuery: Unexpected syntax, expected "and" but found "%s".' %
val, token)
return expected
# expected: only|not or mediatype, mediatype, feature, and
newseq = []
wellformed, expected = self._parse(expected='only|not or mediatype',
seq=newseq, tokenizer=tokenizer,
productions={'IDENT': _ident_or_dim, # e.g. "print"
'DIMENSION': _ident_or_dim, # e.g. "3d"
'CHAR': _char})
wellformed = wellformed and new['wellformed']
# post conditions
if not new['mediatype']:
wellformed = False
self._log.error(u'MediaQuery: No mediatype found: %s' %
self._valuestr(mediaText))
if wellformed:
# set
self.mediaType = new['mediatype']
self.seq = newseq
mediaText = property(_getMediaText, _setMediaText,
doc="The parsable textual representation of the media list.")
def _setMediaType(self, mediaType):
"""
:param mediaType:
one of :attr:`MEDIA_TYPES`
:exceptions:
- :exc:`~xml.dom.SyntaxErr`:
Raised if the specified string value has a syntax error and is
unparsable.
- :exc:`~xml.dom.InvalidCharacterErr`:
Raised if the given mediaType is unknown.
- :exc:`~xml.dom.NoModificationAllowedErr`:
Raised if this media query is readonly.
"""
self._checkReadonly()
nmediaType = self._normalize(mediaType)
if not MediaQuery.__mediaTypeMatch(nmediaType):
self._log.error(
u'MediaQuery: Syntax Error in media type "%s".' % mediaType,
error=xml.dom.SyntaxErr)
else:
if nmediaType not in MediaQuery.MEDIA_TYPES:
self._log.warn(
u'MediaQuery: Unknown media type "%s".' % mediaType,
error=xml.dom.InvalidCharacterErr)
return
# set
self._mediaType = mediaType
# update seq
for i, x in enumerate(self.seq):
if isinstance(x, basestring):
if self._normalize(x) in (u'only', u'not'):
continue
else:
self.seq[i] = mediaType
break
else:
self.seq.insert(0, mediaType)
mediaType = property(lambda self: self._mediaType, _setMediaType,
doc="The media type of this MediaQuery (one of "
":attr:`MEDIA_TYPES`).")
wellformed = property(lambda self: bool(len(self.seq)))

123
libs/cssutils/stylesheets/stylesheet.py

@ -1,123 +0,0 @@
"""StyleSheet implements DOM Level 2 Style Sheets StyleSheet."""
__all__ = ['StyleSheet']
__docformat__ = 'restructuredtext'
__version__ = '$Id$'
import cssutils
import urlparse
class StyleSheet(cssutils.util.Base2):
"""
The StyleSheet interface is the abstract base interface
for any type of style sheet. It represents a single style
sheet associated with a structured document.
In HTML, the StyleSheet interface represents either an
external style sheet, included via the HTML LINK element,
or an inline STYLE element (also an @import stylesheet?).
In XML, this interface represents
an external style sheet, included via a style sheet
processing instruction.
"""
def __init__(self, type='text/css',
href=None,
media=None,
title=u'',
ownerNode=None,
parentStyleSheet=None,
alternate=False,
disabled=None,
validating=True):
"""
type
readonly
href: readonly
If the style sheet is a linked style sheet, the value
of this attribute is its location. For inline style
sheets, the value of this attribute is None. See the
href attribute definition for the LINK element in HTML
4.0, and the href pseudo-attribute for the XML style
sheet processing instruction.
media: of type MediaList, readonly
The intended destination media for style information.
The media is often specified in the ownerNode. If no
media has been specified, the MediaList will be empty.
See the media attribute definition for the LINK element
in HTML 4.0, and the media pseudo-attribute for the XML
style sheet processing instruction. Modifying the media
list may cause a change to the attribute disabled.
title: readonly
The advisory title. The title is often specified in
the ownerNode. See the title attribute definition for
the LINK element in HTML 4.0, and the title
pseudo-attribute for the XML style sheet processing
instruction.
disabled: False if the style sheet is applied to the
document. True if it is not. Modifying this attribute
may cause a new resolution of style for the document.
A stylesheet only applies if both an appropriate medium
definition is present and the disabled attribute is False.
So, if the media doesn't apply to the current user agent,
the disabled attribute is ignored.
ownerNode: of type Node, readonly
The node that associates this style sheet with the
document. For HTML, this may be the corresponding LINK
or STYLE element. For XML, it may be the linking
processing instruction. For style sheets that are
included by other style sheets, the value of this
attribute is None.
parentStyleSheet: of type StyleSheet, readonly
a StyleSheet or None
alternate = False
a flag stating if a style sheet is an alternate one or not.
Currently not used in cssutils
validating = True
a flag defining if this sheet should be validate on change.
"""
super(StyleSheet, self).__init__()
self.validating = validating
self._alternate = alternate
self._href = href
self._ownerNode = ownerNode
self._parentStyleSheet = parentStyleSheet
self._type = type
self.disabled = bool(disabled)
self.media = media
self.title = title
alternate = property(lambda self: self._alternate,
doc="Not used in cssutils yet.")
href = property(lambda self: self._href,
doc="If the style sheet is a linked style sheet, the value "
"of this attribute is its location. For inline style "
"sheets, the value of this attribute is None. See the "
"href attribute definition for the LINK element in HTML "
"4.0, and the href pseudo-attribute for the XML style "
"sheet processing instruction.")
ownerNode = property(lambda self: self._ownerNode,
doc="Not used in cssutils yet.")
parentStyleSheet = property(lambda self: self._parentStyleSheet,
doc="For style sheet languages that support the concept "
"of style sheet inclusion, this attribute represents "
"the including style sheet, if one exists. If the style "
"sheet is a top-level style sheet, or the style sheet "
"language does not support inclusion, the value of this "
"attribute is None.")
type = property(lambda self: self._type,
doc="This specifies the style sheet language for this "
"style sheet. The style sheet language is specified "
"as a content type (e.g. ``text/css``). The content "
"type is often specified in the ownerNode. Also see "
"the type attribute definition for the LINK element "
"in HTML 4.0, and the type pseudo-attribute for the "
"XML style sheet processing instruction. "
"For CSS this is always ``text/css``.")

32
libs/cssutils/stylesheets/stylesheetlist.py

@ -1,32 +0,0 @@
"""StyleSheetList implements DOM Level 2 Style Sheets StyleSheetList."""
__all__ = ['StyleSheetList']
__docformat__ = 'restructuredtext'
__version__ = '$Id$'
class StyleSheetList(list):
"""Interface `StyleSheetList` (introduced in DOM Level 2)
The `StyleSheetList` interface provides the abstraction of an ordered
collection of :class:`~cssutils.stylesheets.StyleSheet` objects.
The items in the `StyleSheetList` are accessible via an integral index,
starting from 0.
This Python implementation is based on a standard Python list so e.g.
allows ``examplelist[index]`` usage.
"""
def item(self, index):
"""
Used to retrieve a style sheet by ordinal `index`. If `index` is
greater than or equal to the number of style sheets in the list,
this returns ``None``.
"""
try:
return self[index]
except IndexError:
return None
length = property(lambda self: len(self),
doc="The number of :class:`StyleSheet` objects in the list. The range"
" of valid child stylesheet indices is 0 to length-1 inclusive.")

223
libs/cssutils/tokenize2.py

@ -1,223 +0,0 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""New CSS Tokenizer (a generator)
"""
__all__ = ['Tokenizer', 'CSSProductions']
__docformat__ = 'restructuredtext'
__version__ = '$Id$'
from cssproductions import *
from helper import normalize
import itertools
import re
import sys
_TOKENIZER_CACHE = {}
class Tokenizer(object):
"""
generates a list of Token tuples:
(Tokenname, value, startline, startcolumn)
"""
_atkeywords = {
u'@font-face': CSSProductions.FONT_FACE_SYM,
u'@import': CSSProductions.IMPORT_SYM,
u'@media': CSSProductions.MEDIA_SYM,
u'@namespace': CSSProductions.NAMESPACE_SYM,
u'@page': CSSProductions.PAGE_SYM,
u'@variables': CSSProductions.VARIABLES_SYM
}
_linesep = u'\n'
unicodesub = re.compile(r'\\[0-9a-fA-F]{1,6}(?:\r\n|[\t\r\n\f\x20])?').sub
cleanstring = re.compile(r'\\((\r\n)|[\n\r\f])').sub
def __init__(self, macros=None, productions=None, doComments=True):
"""
inits tokenizer with given macros and productions which default to
cssutils own macros and productions
"""
if isinstance(macros, dict):
macros_hash_key = sorted(macros.items())
else:
macros_hash_key = macros
hash_key = str((macros_hash_key, productions))
if hash_key in _TOKENIZER_CACHE:
(tokenmatches, commentmatcher, urimatcher) = _TOKENIZER_CACHE[hash_key]
else:
if not macros:
macros = MACROS
if not productions:
productions = PRODUCTIONS
tokenmatches = self._compile_productions(self._expand_macros(macros,
productions))
commentmatcher = [x[1] for x in tokenmatches if x[0] == 'COMMENT'][0]
urimatcher = [x[1] for x in tokenmatches if x[0] == 'URI'][0]
_TOKENIZER_CACHE[hash_key] = (tokenmatches, commentmatcher, urimatcher)
self.tokenmatches = tokenmatches
self.commentmatcher = commentmatcher
self.urimatcher = urimatcher
self._doComments = doComments
self._pushed = []
def _expand_macros(self, macros, productions):
"""returns macro expanded productions, order of productions is kept"""
def macro_value(m):
return '(?:%s)' % macros[m.groupdict()['macro']]
expanded = []
for key, value in productions:
while re.search(r'{[a-zA-Z][a-zA-Z0-9-]*}', value):
value = re.sub(r'{(?P<macro>[a-zA-Z][a-zA-Z0-9-]*)}',
macro_value, value)
expanded.append((key, value))
return expanded
def _compile_productions(self, expanded_productions):
"""compile productions into callable match objects, order is kept"""
compiled = []
for key, value in expanded_productions:
compiled.append((key, re.compile('^(?:%s)' % value, re.U).match))
return compiled
def push(self, *tokens):
"""Push back tokens which have been pulled but not processed."""
self._pushed = itertools.chain(tokens, self._pushed)
def clear(self):
self._pushed = []
def tokenize(self, text, fullsheet=False):
"""Generator: Tokenize text and yield tokens, each token is a tuple
of::
(name, value, line, col)
The token value will contain a normal string, meaning CSS unicode
escapes have been resolved to normal characters. The serializer
escapes needed characters back to unicode escapes depending on
the stylesheet target encoding.
text
to be tokenized
fullsheet
if ``True`` appends EOF token as last one and completes incomplete
COMMENT or INVALID (to STRING) tokens
"""
def _repl(m):
"used by unicodesub"
num = int(m.group(0)[1:], 16)
if num <= sys.maxunicode:
return unichr(num)
else:
return m.group(0)
def _normalize(value):
"normalize and do unicodesub"
return normalize(self.unicodesub(_repl, value))
line = col = 1
# check for BOM first as it should only be max one at the start
(BOM, matcher), productions = self.tokenmatches[0], self.tokenmatches[1:]
match = matcher(text)
if match:
found = match.group(0)
yield (BOM, found, line, col)
text = text[len(found):]
# check for @charset which is valid only at start of CSS
if text.startswith('@charset '):
found = '@charset ' # production has trailing S!
yield (CSSProductions.CHARSET_SYM, found, line, col)
text = text[len(found):]
col += len(found)
while text:
# do pushed tokens before new ones
for pushed in self._pushed:
yield pushed
# speed test for most used CHARs, sadly . not possible :(
c = text[0]
if c in u',:;{}>+[]':
yield ('CHAR', c, line, col)
col += 1
text = text[1:]
else:
# check all other productions, at least CHAR must match
for name, matcher in productions:
# TODO: USE bad comment?
if fullsheet and name == 'CHAR' and text.startswith(u'/*'):
# before CHAR production test for incomplete comment
possiblecomment = u'%s*/' % text
match = self.commentmatcher(possiblecomment)
if match and self._doComments:
yield ('COMMENT', possiblecomment, line, col)
text = None # ate all remaining text
break
match = matcher(text) # if no match try next production
if match:
found = match.group(0) # needed later for line/col
if fullsheet:
# check if found may be completed into a full token
if 'INVALID' == name and text == found:
# complete INVALID to STRING with start char " or '
name, found = 'STRING', '%s%s' % (found, found[0])
elif 'FUNCTION' == name and\
u'url(' == _normalize(found):
# url( is a FUNCTION if incomplete sheet
# FUNCTION production MUST BE after URI production
for end in (u"')", u'")', u')'):
possibleuri = '%s%s' % (text, end)
match = self.urimatcher(possibleuri)
if match:
name, found = 'URI', match.group(0)
break
if name in ('DIMENSION', 'IDENT', 'STRING', 'URI',
'HASH', 'COMMENT', 'FUNCTION', 'INVALID',
'UNICODE-RANGE'):
# may contain unicode escape, replace with normal
# char but do not _normalize (?)
value = self.unicodesub(_repl, found)
if name in ('STRING', 'INVALID'): #'URI'?
# remove \ followed by nl (so escaped) from string
value = self.cleanstring('', value)
else:
if 'ATKEYWORD' == name:
try:
# get actual ATKEYWORD SYM
name = self._atkeywords[_normalize(found)]
except KeyError, e:
# might also be misplace @charset...
if '@charset' == found and u' ' == text[len(found):len(found)+1]:
# @charset needs tailing S!
name = CSSProductions.CHARSET_SYM
found += u' '
else:
name = 'ATKEYWORD'
value = found # should not contain unicode escape (?)
if self._doComments or (not self._doComments and
name != 'COMMENT'):
yield (name, value, line, col)
text = text[len(found):]
nls = found.count(self._linesep)
line += nls
if nls:
col = len(found[found.rfind(self._linesep):])
else:
col += len(found)
break
if fullsheet:
yield ('EOF', u'', line, col)

884
libs/cssutils/util.py

@ -1,884 +0,0 @@
"""base classes and helper functions for css and stylesheets packages
"""
__all__ = []
__docformat__ = 'restructuredtext'
__version__ = '$Id$'
from helper import normalize
from itertools import ifilter, chain
import cssutils
import codec
import codecs
import errorhandler
import tokenize2
import types
import xml.dom
try:
from _fetchgae import _defaultFetcher
except ImportError, e:
from _fetch import _defaultFetcher
log = errorhandler.ErrorHandler()
class _BaseClass(object):
"""
Base class for Base, Base2 and _NewBase.
**Base and Base2 will be removed in the future!**
"""
_log = errorhandler.ErrorHandler()
_prods = tokenize2.CSSProductions
def _checkReadonly(self):
"Raise xml.dom.NoModificationAllowedErr if rule/... is readonly"
if hasattr(self, '_readonly') and self._readonly:
raise xml.dom.NoModificationAllowedErr(
u'%s is readonly.' % self.__class__)
return True
return False
def _valuestr(self, t):
"""
Return string value of t (t may be a string, a list of token tuples
or a single tuple in format (type, value, line, col).
Mainly used to get a string value of t for error messages.
"""
if not t:
return u''
elif isinstance(t, basestring):
return t
else:
return u''.join([x[1] for x in t])
class _NewBase(_BaseClass):
"""
New base class for classes using ProdParser.
**Currently CSSValue and related ones only.**
"""
def __init__(self):
self._seq = Seq()
def _setSeq(self, newseq):
"""Set value of ``seq`` which is readonly."""
newseq._readonly = True
self._seq = newseq
def _tempSeq(self, readonly=False):
"Get a writeable Seq() which is used to set ``seq`` later"
return Seq(readonly=readonly)
seq = property(lambda self: self._seq,
doc="Internal readonly attribute, **DO NOT USE**!")
class Base(_BaseClass):
"""
**Superceded by _NewBase**
**Superceded by Base2 which is used for new seq handling class.**
Base class for most CSS and StyleSheets classes
Contains helper methods for inheriting classes helping parsing
``_normalize`` is static as used by Preferences.
"""
__tokenizer2 = tokenize2.Tokenizer()
# for more on shorthand properties see
# http://www.dustindiaz.com/css-shorthand/
# format: shorthand: [(propname, mandatorycheck?)*]
_SHORTHANDPROPERTIES = {
u'background': [],
#u'background-position': [], # list of 2 values!
u'border': [],
u'border-left': [],
u'border-right': [],
u'border-top': [],
u'border-bottom': [],
#u'border-color': [], # list or single but same values
#u'border-style': [], # list or single but same values
#u'border-width': [], # list or single but same values
u'cue': [],
u'font': [],
u'list-style': [],
#u'margin': [], # list or single but same values
u'outline': [],
#u'padding': [], # list or single but same values
u'pause': []
}
@staticmethod
def _normalize(x):
"""
normalizes x, namely:
- remove any \ before non unicode sequences (0-9a-zA-Z) so for
x=="c\olor\" return "color" (unicode escape sequences should have
been resolved by the tokenizer already)
- lowercase
"""
return normalize(x)
def _splitNamespacesOff(self, text_namespaces_tuple):
"""
returns tuple (text, dict-of-namespaces) or if no namespaces are
in cssText returns (cssText, {})
used in Selector, SelectorList, CSSStyleRule, CSSMediaRule and
CSSStyleSheet
"""
if isinstance(text_namespaces_tuple, tuple):
return text_namespaces_tuple[0], _SimpleNamespaces(self._log,
text_namespaces_tuple[1])
else:
return text_namespaces_tuple, _SimpleNamespaces(log=self._log)
def _tokenize2(self, textortokens):
"""
returns tokens of textortokens which may already be tokens in which
case simply returns input
"""
if not textortokens:
return None
elif isinstance(textortokens, basestring):
# needs to be tokenized
return self.__tokenizer2.tokenize(
textortokens)
elif isinstance(textortokens, tuple):
# a single token (like a comment)
return [textortokens]
else:
# already tokenized but return an iterator
return iter(textortokens)
def _nexttoken(self, tokenizer, default=None):
"returns next token in generator tokenizer or the default value"
try:
return tokenizer.next()
# TypeError for py3
except (StopIteration, AttributeError, TypeError):
return default
def _type(self, token):
"returns type of Tokenizer token"
if token:
return token[0]
else:
return None
def _tokenvalue(self, token, normalize=False):
"returns value of Tokenizer token"
if token and normalize:
return Base._normalize(token[1])
elif token:
return token[1]
else:
return None
def _stringtokenvalue(self, token):
"""
for STRING returns the actual content without surrounding "" or ''
and without respective escapes, e.g.::
"with \" char" => with " char
"""
if token:
value = token[1]
return value.replace('\\' + value[0], value[0])[1: - 1]
else:
return None
def _uritokenvalue(self, token):
"""
for URI returns the actual content without surrounding url()
or url(""), url('') and without respective escapes, e.g.::
url("\"") => "
"""
if token:
value = token[1][4: - 1].strip()
if value and (value[0] in '\'"') and (value[0] == value[ - 1]):
# a string "..." or '...'
value = value.replace('\\' + value[0], value[0])[1: - 1]
return value
else:
return None
def _tokensupto2(self,
tokenizer,
starttoken=None,
blockstartonly=False, # {
blockendonly=False, # }
mediaendonly=False,
importmediaqueryendonly=False, # ; or STRING
mediaqueryendonly=False, # { or STRING
semicolon=False, # ;
propertynameendonly=False, # :
propertyvalueendonly=False, # ! ; }
propertypriorityendonly=False, # ; }
selectorattendonly=False, # ]
funcendonly=False, # )
listseponly=False, # ,
separateEnd=False # returns (resulttokens, endtoken)
):
"""
returns tokens upto end of atrule and end index
end is defined by parameters, might be ; } ) or other
default looks for ending "}" and ";"
"""
ends = u';}'
endtypes = ()
brace = bracket = parant = 0 # {}, [], ()
if blockstartonly: # {
ends = u'{'
brace = - 1 # set to 0 with first {
elif blockendonly: # }
ends = u'}'
brace = 1
elif mediaendonly: # }
ends = u'}'
brace = 1 # rules } and mediarules }
elif importmediaqueryendonly:
# end of mediaquery which may be ; or STRING
ends = u';'
endtypes = ('STRING',)
elif mediaqueryendonly:
# end of mediaquery which may be { or STRING
# special case, see below
ends = u'{'
brace = - 1 # set to 0 with first {
endtypes = ('STRING',)
elif semicolon:
ends = u';'
elif propertynameendonly: # : and ; in case of an error
ends = u':;'
elif propertyvalueendonly: # ; or !important
ends = u';!'
elif propertypriorityendonly: # ;
ends = u';'
elif selectorattendonly: # ]
ends = u']'
if starttoken and self._tokenvalue(starttoken) == u'[':
bracket = 1
elif funcendonly: # )
ends = u')'
parant = 1
elif listseponly: # ,
ends = u','
resulttokens = []
if starttoken:
resulttokens.append(starttoken)
val = starttoken[1]
if u'[' == val:
bracket += 1
elif u'{' == val:
brace += 1
elif u'(' == val:
parant += 1
if tokenizer:
for token in tokenizer:
typ, val, line, col = token
if 'EOF' == typ:
resulttokens.append(token)
break
if u'{' == val:
brace += 1
elif u'}' == val:
brace -= 1
elif u'[' == val:
bracket += 1
elif u']' == val:
bracket -= 1
# function( or single (
elif u'(' == val or \
Base._prods.FUNCTION == typ:
parant += 1
elif u')' == val:
parant -= 1
resulttokens.append(token)
if (brace == bracket == parant == 0) and (
val in ends or typ in endtypes):
break
elif mediaqueryendonly and brace == - 1 and (
bracket == parant == 0) and typ in endtypes:
# mediaqueryendonly with STRING
break
if separateEnd:
# TODO: use this method as generator, then this makes sense
if resulttokens:
return resulttokens[: - 1], resulttokens[ - 1]
else:
return resulttokens, None
else:
return resulttokens
def _adddefaultproductions(self, productions, new=None):
"""
adds default productions if not already present, used by
_parse only
each production should return the next expected token
normaly a name like "uri" or "EOF"
some have no expectation like S or COMMENT, so simply return
the current value of self.__expected
"""
def ATKEYWORD(expected, seq, token, tokenizer=None):
"default impl for unexpected @rule"
if expected != 'EOF':
# TODO: parentStyleSheet=self
rule = cssutils.css.CSSUnknownRule()
rule.cssText = self._tokensupto2(tokenizer, token)
if rule.wellformed:
seq.append(rule)
return expected
else:
new['wellformed'] = False
self._log.error(u'Expected EOF.', token=token)
return expected
def COMMENT(expected, seq, token, tokenizer=None):
"default implementation for COMMENT token adds CSSCommentRule"
seq.append(cssutils.css.CSSComment([token]))
return expected
def S(expected, seq, token, tokenizer=None):
"default implementation for S token, does nothing"
return expected
def EOF(expected=None, seq=None, token=None, tokenizer=None):
"default implementation for EOF token"
return 'EOF'
p = {'ATKEYWORD': ATKEYWORD,
'COMMENT': COMMENT,
'S': S,
'EOF': EOF # only available if fullsheet
}
p.update(productions)
return p
def _parse(self, expected, seq, tokenizer, productions, default=None,
new=None, initialtoken=None):
"""
puts parsed tokens in seq by calling a production with
(seq, tokenizer, token)
expected
a name what token or value is expected next, e.g. 'uri'
seq
to add rules etc to
tokenizer
call tokenizer.next() to get next token
productions
callbacks {tokentype: callback}
default
default callback if tokentype not in productions
new
used to init default productions
initialtoken
will be used together with tokenizer running 1st this token
and then all tokens in tokenizer
returns (wellformed, expected) which the last prod might have set
"""
wellformed = True
if initialtoken:
# add initialtoken to tokenizer
def tokens():
"Build new tokenizer including initialtoken"
yield initialtoken
for item in tokenizer:
yield item
fulltokenizer = chain([initialtoken], tokenizer)
else:
fulltokenizer = tokenizer
if fulltokenizer:
prods = self._adddefaultproductions(productions, new)
for token in fulltokenizer:
p = prods.get(token[0], default)
if p:
expected = p(expected, seq, token, tokenizer)
else:
wellformed = False
self._log.error(u'Unexpected token (%s, %s, %s, %s)' % token)
return wellformed, expected
class Base2(Base, _NewBase):
"""
**Superceded by _NewBase.**
Base class for new seq handling.
"""
def __init__(self):
self._seq = Seq()
def _adddefaultproductions(self, productions, new=None):
"""
adds default productions if not already present, used by
_parse only
each production should return the next expected token
normaly a name like "uri" or "EOF"
some have no expectation like S or COMMENT, so simply return
the current value of self.__expected
"""
def ATKEYWORD(expected, seq, token, tokenizer=None):
"default impl for unexpected @rule"
if expected != 'EOF':
# TODO: parentStyleSheet=self
rule = cssutils.css.CSSUnknownRule()
rule.cssText = self._tokensupto2(tokenizer, token)
if rule.wellformed:
seq.append(rule, cssutils.css.CSSRule.UNKNOWN_RULE,
line=token[2], col=token[3])
return expected
else:
new['wellformed'] = False
self._log.error(u'Expected EOF.', token=token)
return expected
def COMMENT(expected, seq, token, tokenizer=None):
"default impl, adds CSSCommentRule if not token == EOF"
if expected == 'EOF':
new['wellformed'] = False
self._log.error(u'Expected EOF but found comment.', token=token)
seq.append(cssutils.css.CSSComment([token]), 'COMMENT')
return expected
def S(expected, seq, token, tokenizer=None):
"default impl, does nothing if not token == EOF"
if expected == 'EOF':
new['wellformed'] = False
self._log.error(u'Expected EOF but found whitespace.', token=token)
return expected
def EOF(expected=None, seq=None, token=None, tokenizer=None):
"default implementation for EOF token"
return 'EOF'
defaultproductions = {'ATKEYWORD': ATKEYWORD,
'COMMENT': COMMENT,
'S': S,
'EOF': EOF # only available if fullsheet
}
defaultproductions.update(productions)
return defaultproductions
class Seq(object):
"""
property seq of Base2 inheriting classes, holds a list of Item objects.
used only by Selector for now
is normally readonly, only writable during parsing
"""
def __init__(self, readonly=True):
"""
only way to write to a Seq is to initialize it with new items
each itemtuple has (value, type, line) where line is optional
"""
self._seq = []
self._readonly = readonly
def __repr__(self):
"returns a repr same as a list of tuples of (value, type)"
return u'cssutils.%s.%s([\n %s], readonly=%r)' % (self.__module__,
self.__class__.__name__,
u',\n '.join([u'%r' % item for item in self._seq]
), self._readonly)
def __str__(self):
vals = []
for v in self:
if isinstance(v.value, basestring):
vals.append(v.value)
elif isinstance(v, tuple):
vals.append(v.value[1])
else:
vals.append(str(v))
return "<cssutils.%s.%s object length=%r values=%r readonly=%r at 0x%x>" % (
self.__module__, self.__class__.__name__, len(self),
u', '.join(vals), self._readonly, id(self))
def __delitem__(self, i):
del self._seq[i]
def __getitem__(self, i):
return self._seq[i]
def __setitem__(self, i, (val, typ, line, col)):
self._seq[i] = Item(val, typ, line, col)
def __iter__(self):
return iter(self._seq)
def __len__(self):
return len(self._seq)
def append(self, val, typ, line=None, col=None):
"If not readonly add new Item()"
if self._readonly:
raise AttributeError('Seq is readonly.')
else:
self._seq.append(Item(val, typ, line, col))
def appendItem(self, item):
"if not readonly add item which must be an Item"
if self._readonly:
raise AttributeError('Seq is readonly.')
else:
self._seq.append(item)
def insert(self, index, val, typ, line=None, col=None):
"Insert new Item() at index # even if readony!? TODO!"
self._seq.insert(index, Item(val, typ, line, col))
def replace(self, index=-1, val=None, typ=None, line=None, col=None):
"""
if not readonly replace Item at index with new Item or
simply replace value or type
"""
if self._readonly:
raise AttributeError('Seq is readonly.')
else:
self._seq[index] = Item(val, typ, line, col)
def rstrip(self):
"trims S items from end of Seq"
while self._seq and self._seq[ - 1].type == tokenize2.CSSProductions.S:
# TODO: removed S before CSSComment /**/ /**/
del self._seq[ - 1]
def appendToVal(self, val=None, index= - 1):
"""
if not readonly append to Item's value at index
"""
if self._readonly:
raise AttributeError('Seq is readonly.')
else:
old = self._seq[index]
self._seq[index] = Item(old.value + val, old.type,
old.line, old.col)
class Item(object):
"""
an item in the seq list of classes (successor to tuple items in old seq)
each item has attributes:
type
a sematic type like "element", "attribute"
value
the actual value which may be a string, number etc or an instance
of e.g. a CSSComment
*line*
**NOT IMPLEMENTED YET, may contain the line in the source later**
"""
def __init__(self, value, type, line=None, col=None):
self.__value = value
self.__type = type
self.__line = line
self.__col = col
type = property(lambda self: self.__type)
value = property(lambda self: self.__value)
line = property(lambda self: self.__line)
col = property(lambda self: self.__col)
def __repr__(self):
return "%s.%s(value=%r, type=%r, line=%r, col=%r)" % (
self.__module__, self.__class__.__name__,
self.__value, self.__type, self.__line, self.__col)
class ListSeq(object):
"""
(EXPERIMENTAL)
A base class used for list classes like cssutils.css.SelectorList or
stylesheets.MediaList
adds list like behaviour running on inhering class' property ``seq``
- item in x => bool
- len(x) => integer
- get, set and del x[i]
- for item in x
- append(item)
some methods must be overwritten in inheriting class
"""
def __init__(self):
self.seq = [] # does not need to use ``Seq`` as simple list only
def __contains__(self, item):
return item in self.seq
def __delitem__(self, index):
del self.seq[index]
def __getitem__(self, index):
return self.seq[index]
def __iter__(self):
def gen():
for x in self.seq:
yield x
return gen()
def __len__(self):
return len(self.seq)
def __setitem__(self, index, item):
"must be overwritten"
raise NotImplementedError
def append(self, item):
"must be overwritten"
raise NotImplementedError
class _Namespaces(object):
"""
A dictionary like wrapper for @namespace rules used in a CSSStyleSheet.
Works on effective namespaces, so e.g. if::
@namespace p1 "uri";
@namespace p2 "uri";
only the second rule is effective and kept.
namespaces
a dictionary {prefix: namespaceURI} containing the effective namespaces
only. These are the latest set in the CSSStyleSheet.
parentStyleSheet
the parent CSSStyleSheet
"""
def __init__(self, parentStyleSheet, log=None, *args):
"no initial values are set, only the relevant sheet is"
self.parentStyleSheet = parentStyleSheet
self._log = log
def __repr__(self):
return "%r" % self.namespaces
def __contains__(self, prefix):
return prefix in self.namespaces
def __delitem__(self, prefix):
"""deletes CSSNamespaceRule(s) with rule.prefix == prefix
prefix '' and None are handled the same
"""
if not prefix:
prefix = u''
delrule = self.__findrule(prefix)
for i, rule in enumerate(ifilter(lambda r: r.type == r.NAMESPACE_RULE,
self.parentStyleSheet.cssRules)):
if rule == delrule:
self.parentStyleSheet.deleteRule(i)
return
self._log.error('Prefix %s not found.' % prefix,
error=xml.dom.NamespaceErr)
def __getitem__(self, prefix):
try:
return self.namespaces[prefix]
except KeyError, e:
self._log.error('Prefix %s not found.' % prefix,
error=xml.dom.NamespaceErr)
def __iter__(self):
return self.namespaces.__iter__()
def __len__(self):
return len(self.namespaces)
def __setitem__(self, prefix, namespaceURI):
"replaces prefix or sets new rule, may raise NoModificationAllowedErr"
if not prefix:
prefix = u'' # None or ''
rule = self.__findrule(prefix)
if not rule:
self.parentStyleSheet.insertRule(cssutils.css.CSSNamespaceRule(
prefix=prefix,
namespaceURI=namespaceURI),
inOrder=True)
else:
if prefix in self.namespaces:
rule.namespaceURI = namespaceURI # raises NoModificationAllowedErr
if namespaceURI in self.namespaces.values():
rule.prefix = prefix
def __findrule(self, prefix):
# returns namespace rule where prefix == key
for rule in ifilter(lambda r: r.type == r.NAMESPACE_RULE,
reversed(self.parentStyleSheet.cssRules)):
if rule.prefix == prefix:
return rule
@property
def namespaces(self):
"""
A property holding only effective @namespace rules in
self.parentStyleSheets.
"""
namespaces = {}
for rule in ifilter(lambda r: r.type == r.NAMESPACE_RULE,
reversed(self.parentStyleSheet.cssRules)):
if rule.namespaceURI not in namespaces.values():
namespaces[rule.prefix] = rule.namespaceURI
return namespaces
def get(self, prefix, default):
return self.namespaces.get(prefix, default)
def items(self):
return self.namespaces.items()
def keys(self):
return self.namespaces.keys()
def values(self):
return self.namespaces.values()
def prefixForNamespaceURI(self, namespaceURI):
"""
returns effective prefix for given namespaceURI or raises IndexError
if this cannot be found"""
for prefix, uri in self.namespaces.items():
if uri == namespaceURI:
return prefix
raise IndexError(u'NamespaceURI %s not found.' % namespaceURI)
def __str__(self):
return u"<cssutils.util.%s object parentStyleSheet=%r at 0x%x>" % (
self.__class__.__name__, str(self.parentStyleSheet), id(self))
class _SimpleNamespaces(_Namespaces):
"""
namespaces used in objects like Selector as long as they are not connected
to a CSSStyleSheet
"""
def __init__(self, log=None, *args):
"""init"""
super(_SimpleNamespaces, self).__init__(parentStyleSheet=None, log=log)
self.__namespaces = dict(*args)
def __setitem__(self, prefix, namespaceURI):
self.__namespaces[prefix] = namespaceURI
namespaces = property(lambda self: self.__namespaces,
doc=u'Dict Wrapper for self.sheets @namespace rules.')
def __str__(self):
return u"<cssutils.util.%s object namespaces=%r at 0x%x>" % (
self.__class__.__name__, self.namespaces, id(self))
def __repr__(self):
return u"cssutils.util.%s(%r)" % (self.__class__.__name__,
self.namespaces)
def _readUrl(url, fetcher=None, overrideEncoding=None, parentEncoding=None):
"""
Read cssText from url and decode it using all relevant methods (HTTP
header, BOM, @charset). Returns
- encoding used to decode text (which is needed to set encoding of
stylesheet properly)
- type of encoding (how it was retrieved, see list below)
- decodedCssText
``fetcher``
see cssutils.CSSParser.setFetcher for details
``overrideEncoding``
If given this encoding is used and all other encoding information is
ignored (HTTP, BOM etc)
``parentEncoding``
Encoding of parent stylesheet (while e.g. reading @import references
sheets) or document if available.
Priority or encoding information
--------------------------------
**cssutils only**: 0. overrideEncoding
1. An HTTP "charset" parameter in a "Content-Type" field (or similar
parameters in other protocols)
2. BOM and/or @charset (see below)
3. <link charset=""> or other metadata from the linking mechanism (if any)
4. charset of referring style sheet or document (if any)
5. Assume UTF-8
"""
enctype = None
if not fetcher:
fetcher = _defaultFetcher
r = fetcher(url)
if r and len(r) == 2 and r[1] is not None:
httpEncoding, content = r
if overrideEncoding:
enctype = 0 # 0. override encoding
encoding = overrideEncoding
elif httpEncoding:
enctype = 1 # 1. HTTP
encoding = httpEncoding
else:
# BOM or @charset
if isinstance(content, unicode):
contentEncoding, explicit = codec.detectencoding_unicode(content)
else:
contentEncoding, explicit = codec.detectencoding_str(content)
if explicit:
enctype = 2 # 2. BOM/@charset: explicitly
encoding = contentEncoding
elif parentEncoding:
enctype = 4 # 4. parent stylesheet or document
# may also be None in which case 5. is used in next step anyway
encoding = parentEncoding
else:
enctype = 5 # 5. assume UTF-8
encoding = 'utf-8'
if isinstance(content, unicode):
decodedCssText = content
else:
try:
# encoding may still be wrong if encoding *is lying*!
try:
decodedCssText = codecs.lookup("css")[1](content, encoding=encoding)[0]
except AttributeError, ae:
# at least in GAE
decodedCssText = content.decode(encoding if encoding else 'utf-8')
except UnicodeDecodeError, e:
log.warn(e, neverraise=True)
decodedCssText = None
return encoding, enctype, decodedCssText
else:
return None, None, None

690
libs/encutils/__init__.py

@ -1,690 +0,0 @@
# -*- coding: utf-8 -*-
#!/usr/bin/env python
"""encutils - encoding detection collection for Python
:Version: 0.9.8
:Author: Christof Hoeke, see http://cthedot.de/encutils/
:Contributor: Robert Siemer, Fredrik Hedman <fredrik.hedman@me.com> ported to python3
:Copyright: 2005-2012: Christof Hoeke
:License: encutils has a dual-license, please choose whatever you prefer:
* encutils is published under the
`LGPL 3 or later <http://cthedot.de/encutils/license/>`__
* encutils is published under the
`Creative Commons License <http://creativecommons.org/licenses/by/3.0/>`__.
encutils is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
encutils is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License
along with encutils. If not, see <http://www.gnu.org/licenses/>.
A collection of helper functions to detect encodings of text files (like HTML, XHTML, XML, CSS, etc.) retrieved via HTTP, file or string.
:func:`getEncodingInfo` is probably the main function of interest which uses
other supplied functions itself and gathers all information together and
supplies an :class:`EncodingInfo` object.
example::
>>> import encutils
>>> info = encutils.getEncodingInfo(url='http://cthedot.de/encutils/')
>>> str(info)
utf-8
>>> repr(info) # doctest:+ELLIPSIS
<encutils.EncodingInfo object encoding='utf-8' mismatch=False at...>
>>> info.logtext
HTTP media_type: text/html
HTTP encoding: utf-8
Encoding (probably): utf-8 (Mismatch: False)
<BLANKLINE>
references
XML
RFC 3023 (http://www.ietf.org/rfc/rfc3023.txt)
easier explained in
- http://feedparser.org/docs/advanced.html
- http://www.xml.com/pub/a/2004/07/21/dive.html
HTML
http://www.w3.org/TR/REC-html40/charset.html#h-5.2.2
TODO
- parse @charset of HTML elements?
- check for more texttypes if only text given
"""
__all__ = ['buildlog',
'encodingByMediaType',
'getHTTPInfo',
'getMetaInfo',
'detectXMLEncoding',
'getEncodingInfo',
'tryEncodings',
'EncodingInfo']
__docformat__ = 'restructuredtext'
__author__ = 'Christof Hoeke, Robert Siemer, Fredrik Hedman'
__version__ = '$Id$'
import HTMLParser
import StringIO
import cgi
import httplib
import re
import sys
import types
import urllib
VERSION = '0.9.8'
PY2x = sys.version_info < (3,0)
class _MetaHTMLParser(HTMLParser.HTMLParser):
"""Parse given data for <meta http-equiv="content-type">."""
content_type = None
def handle_starttag(self, tag, attrs):
if tag == 'meta' and not self.content_type:
atts = dict([(a.lower(), v.lower()) for a, v in attrs])
if atts.get('http-equiv', u'').strip() == u'content-type':
self.content_type = atts.get('content')
# application/xml, application/xml-dtd, application/xml-external-parsed-entity, or a subtype like application/rss+xml.
_XML_APPLICATION_TYPE = 0
# text/xml, text/xml-external-parsed-entity, or a subtype like text/AnythingAtAll+xml
_XML_TEXT_TYPE = 1
# text/html
_HTML_TEXT_TYPE = 2
# any other of text/* like text/plain, ...
_TEXT_TYPE = 3
# any text/* like which defaults to UTF-8 encoding, for now only text/css
_TEXT_UTF8 = 5
# types not fitting in above types
_OTHER_TYPE = 4
class EncodingInfo(object):
"""
All encoding related information, returned by :func:`getEncodingInfo`.
Attributes filled:
- ``encoding``: The guessed encoding
Encoding is the explicit or implicit encoding or None and
always lowercase.
- from HTTP response
* ``http_encoding``
* ``http_media_type``
- from HTML <meta> element
* ``meta_encoding``
* ``meta_media_type``
- from XML declaration
* ``xml_encoding``
- ``mismatch``: True if mismatch between XML declaration and HTTP
header.
Mismatch is True if any mismatches between HTTP header, XML
declaration or textcontent (meta) are found. More detailed
mismatch reports are written to the optional log or ``logtext``
Mismatches are not necessarily errors as preferences are defined.
For details see the specifications.
- ``logtext``: if no log was given log reports are given here
"""
def __init__(self):
"""Initialize all possible properties to ``None``, see class
description
"""
self.encoding = self.mismatch = self.logtext =\
self.http_encoding = self.http_media_type =\
self.meta_encoding = self.meta_media_type =\
self.xml_encoding =\
None
def __str__(self):
"""Output the guessed encoding itself or the empty string."""
if self.encoding:
return self.encoding
else:
return u''
def __repr__(self):
return "<%s.%s object encoding=%r mismatch=%s at 0x%x>" % (
self.__class__.__module__, self.__class__.__name__,
self.encoding, self.mismatch, id(self))
def buildlog(logname='encutils', level='INFO', stream=sys.stderr,
filename=None, filemode="w",
format='%(levelname)s\t%(message)s'):
"""Helper to build a basic log
- if `filename` is given returns a log logging to `filename` with
mode `filemode`
- else uses a log streaming to `stream` which defaults to
`sys.stderr`
- `level` defines the level of the log
- `format` defines the formatter format of the log
:returns:
a log with the name `logname`
"""
import logging
log = logging.getLogger(logname)
if filename:
hdlr = logging.FileHandler(filename, filemode)
else:
hdlr = logging.StreamHandler(stream)
formatter = logging.Formatter(format)
hdlr.setFormatter(formatter)
log.addHandler(hdlr)
log.setLevel(logging.__dict__.get(level, logging.INFO))
return log
def _getTextTypeByMediaType(media_type, log=None):
"""
:returns:
type as defined by constants in this class
"""
if not media_type:
return _OTHER_TYPE
xml_application_types = [
ur'application/.*?\+xml',
u'application/xml',
u'application/xml-dtd',
u'application/xml-external-parsed-entity']
xml_text_types = [
ur'text\/.*?\+xml',
u'text/xml',
u'text/xml-external-parsed-entity']
media_type = media_type.strip().lower()
if media_type in xml_application_types or\
re.match(xml_application_types[0], media_type, re.I|re.S|re.X):
return _XML_APPLICATION_TYPE
elif media_type in xml_text_types or\
re.match(xml_text_types[0], media_type, re.I|re.S|re.X):
return _XML_TEXT_TYPE
elif media_type == u'text/html':
return _HTML_TEXT_TYPE
elif media_type == u'text/css':
return _TEXT_UTF8
elif media_type.startswith(u'text/'):
return _TEXT_TYPE
else:
return _OTHER_TYPE
def _getTextType(text, log=None):
"""Check if given text is XML (**naive test!**)
used if no content-type given
"""
if text[:30].find(u'<?xml version=') != -1:
return _XML_APPLICATION_TYPE
else:
return _OTHER_TYPE
def encodingByMediaType(media_type, log=None):
"""
:param media_type:
a media type like "text/html"
:returns:
a default encoding for given `media_type`. For example
``"utf-8"`` for ``media_type="application/xml"``.
If no default encoding is available returns ``None``.
Refers to RFC 3023 and HTTP MIME specification.
"""
defaultencodings = {
_XML_APPLICATION_TYPE: u'utf-8',
_XML_TEXT_TYPE: u'ascii',
_HTML_TEXT_TYPE: u'iso-8859-1', # should be None?
_TEXT_TYPE: u'iso-8859-1', # should be None?
_TEXT_UTF8: u'utf-8',
_OTHER_TYPE: None}
texttype = _getTextTypeByMediaType(media_type)
encoding = defaultencodings.get(texttype, None)
if log:
if not encoding:
log.debug(u'"%s" Media-Type has no default encoding',
media_type)
else:
log.debug(
u'Default encoding for Media Type "%s": %s',
media_type, encoding)
return encoding
def getHTTPInfo(response, log=None):
"""
:param response:
a HTTP response object
:returns:
``(media_type, encoding)`` information from the `response`
Content-Type HTTP header. (Case of headers is ignored.)
May be ``(None, None)`` e.g. if no Content-Type header is
available.
"""
info = response.info()
if PY2x:
media_type, encoding = info.gettype(), info.getparam('charset')
else:
media_type, encoding = info.get_content_type(), info.get_content_charset()
if encoding:
encoding = encoding.lower()
if log:
log.info(u'HTTP media_type: %s', media_type)
log.info(u'HTTP encoding: %s', encoding)
return media_type, encoding
def getMetaInfo(text, log=None):
"""
:param text:
a byte string
:returns:
``(media_type, encoding)`` information from (first)
X/HTML Content-Type ``<meta>`` element if available in `text`.
XHTML format::
<meta http-equiv="Content-Type"
content="media_type;charset=encoding" />
"""
p = _MetaHTMLParser()
try:
p.feed(text)
except HTMLParser.HTMLParseError, e:
pass
if p.content_type:
media_type, params = cgi.parse_header(p.content_type)
encoding = params.get('charset') # defaults to None
if encoding:
encoding = encoding.lower()
if log:
log.info(u'HTML META media_type: %s', media_type)
log.info(u'HTML META encoding: %s', encoding)
else:
media_type = encoding = None
return media_type, encoding
def detectXMLEncoding(fp, log=None, includeDefault=True):
"""Attempt to detect the character encoding of the xml file
given by a file object `fp`. `fp` must not be a codec wrapped file
object! `fp` may be a string or unicode string though.
Based on a recipe by Lars Tiede:
http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/363841
which itself is based on Paul Prescotts recipe:
http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/52257
:returns:
- if detection of the BOM succeeds, the codec name of the
corresponding unicode charset is returned
- if BOM detection fails, the xml declaration is searched for
the encoding attribute and its value returned. the "<"
character has to be the very first in the file then (it's xml
standard after all).
- if BOM and xml declaration fail, utf-8 is returned according
to XML 1.0.
"""
if PY2x and isinstance(fp, types.StringTypes):
fp = StringIO.StringIO(fp)
elif isinstance(fp, (str,)):
fp = StringIO.StringIO(fp)
### detection using BOM
## the BOMs we know, by their pattern
bomDict={ # bytepattern: name
(0x00, 0x00, 0xFE, 0xFF) : "utf_32_be",
(0xFF, 0xFE, 0x00, 0x00) : "utf_32_le",
(0xFE, 0xFF, None, None) : "utf_16_be",
(0xFF, 0xFE, None, None) : "utf_16_le",
(0xEF, 0xBB, 0xBF, None) : "utf-8",
}
## go to beginning of file and get the first 4 bytes
oldFP = fp.tell()
fp.seek(0)
(byte1, byte2, byte3, byte4) = tuple(map(ord, fp.read(4)))
## try bom detection using 4 bytes, 3 bytes, or 2 bytes
bomDetection = bomDict.get((byte1, byte2, byte3, byte4))
if not bomDetection:
bomDetection = bomDict.get((byte1, byte2, byte3, None))
if not bomDetection:
bomDetection = bomDict.get((byte1, byte2, None, None))
## if BOM detected, we're done :-)
if bomDetection:
if log:
log.info(u'XML BOM encoding: %s' % bomDetection)
fp.seek(oldFP)
return bomDetection
## still here? BOM detection failed.
## now that BOM detection has failed we assume one byte character
## encoding behaving ASCII
### search xml declaration for encoding attribute
## assume xml declaration fits into the first 2 KB (*cough*)
fp.seek(0)
buffer = fp.read(2048)
## set up regular expression
xmlDeclPattern = r"""
^<\?xml # w/o BOM, xmldecl starts with <?xml at the first byte
.+? # some chars (version info), matched minimal
encoding= # encoding attribute begins
["'] # attribute start delimiter
(?P<encstr> # what's matched in the brackets will be named encstr
[^"']+ # every character not delimiter (not overly exact!)
) # closes the brackets pair for the named group
["'] # attribute end delimiter
.*? # some chars optionally (standalone decl or whitespace)
\?> # xmldecl end
"""
xmlDeclRE = re.compile(xmlDeclPattern, re.VERBOSE)
## search and extract encoding string
match = xmlDeclRE.search(buffer)
fp.seek(oldFP)
if match:
enc = match.group("encstr").lower()
if log:
log.info(u'XML encoding="%s"' % enc)
return enc
else:
if includeDefault:
if log:
log.info(u'XML encoding default utf-8')
return u'utf-8'
else:
return None
def tryEncodings(text, log=None):
"""If installed uses chardet http://chardet.feedparser.org/ to detect
encoding, else tries different encodings on `text` and returns the one
that does not raise an exception which is not very advanced or may
be totally wrong. The tried encoding are in order 'ascii', 'iso-8859-1',
'windows-1252' (which probably will never happen as 'iso-8859-1' can decode
these strings too) and lastly 'utf-8'.
:param text:
a byte string
:returns:
Working encoding or ``None`` if no encoding does work at all.
The returned encoding might nevertheless be not the one intended by
the author as it is only checked if the text might be encoded in
that encoding. Some texts might be working in "iso-8859-1" *and*
"windows-1252" *and* "ascii" *and* "utf-8" and ...
"""
try:
import chardet
encoding = chardet.detect(text)["encoding"]
except ImportError:
msg = 'Using simplified encoding detection, you might want to install chardet.'
if log:
log.warn(msg)
else:
print msg
encodings = (
'ascii',
'iso-8859-1',
#'windows-1252', # test later
'utf-8'
)
encoding = None
for e in encodings:
try:
text.decode(e)
except UnicodeDecodeError:
pass
else:
if 'iso-8859-1' == e:
try:
if u'' in text.decode('windows-1252'):
return 'windows-1252'
except UnicodeDecodeError:
pass
return e
return encoding
def getEncodingInfo(response=None, text=u'', log=None, url=None):
"""Find all encoding related information in given `text`.
Information in headers of supplied HTTPResponse, possible XML
declaration and X/HTML ``<meta>`` elements are used.
:param response:
HTTP response object, e.g. via ``urllib.urlopen('url')``
:param text:
a byte string to guess encoding for. XML prolog with
encoding pseudo attribute or HTML meta element will be used to detect
the encoding
:param url:
When given fetches document at `url` and all needed information.
No `reponse` or `text` parameters are needed in this case.
:param log:
an optional logging logger to which messages may go, if
no log given all log messages are available from resulting
``EncodingInfo``
:returns:
instance of :class:`EncodingInfo`.
How the resulting encoding is retrieved:
XML
RFC 3023 states if media type given in the Content-Type HTTP header is
application/xml, application/xml-dtd,
application/xml-external-parsed-entity, or any one of the subtypes of
application/xml such as application/atom+xml or application/rss+xml
etc then the character encoding is determined in this order:
1. the encoding given in the charset parameter of the Content-Type HTTP
header, or
2. the encoding given in the encoding attribute of the XML declaration
within the document, or
3. utf-8.
Mismatch possibilities:
- HTTP + XMLdecla
- HTTP + HTMLmeta
application/xhtml+xml ?
XMLdecla + HTMLmeta
If the media type given in the Content-Type HTTP header is text/xml,
text/xml-external-parsed-entity, or a subtype like text/Anything+xml,
the encoding attribute of the XML declaration is ignored completely
and the character encoding is determined in the order:
1. the encoding given in the charset parameter of the Content-Type HTTP
header, or
2. ascii.
No mismatch possible.
If no media type is given the XML encoding pseuso attribute is used
if present.
No mismatch possible.
HTML
For HTML served as text/html:
http://www.w3.org/TR/REC-html40/charset.html#h-5.2.2
1. An HTTP "charset" parameter in a "Content-Type" field.
(maybe defaults to ISO-8859-1, but should not assume this)
2. A META declaration with "http-equiv" set to "Content-Type" and a
value set for "charset".
3. The charset attribute set on an element that designates an external
resource. (NOT IMPLEMENTED HERE YET)
Mismatch possibilities:
- HTTP + HTMLmeta
TEXT
For most text/* types the encoding will be reported as iso-8859-1.
Exceptions are XML formats send as text/* mime type (see above) and
text/css which has a default encoding of UTF-8.
"""
if url:
# may cause IOError which is raised
response = urllib.urlopen(url)
if text is None:
# read text from response only if not explicitly given
try:
text = response.read()
except IOError, e:
pass
if text is None:
# text must be a string (not None)
text = ''
encinfo = EncodingInfo()
logstream = StringIO.StringIO()
if not log:
log = buildlog(stream=logstream, format='%(message)s')
# HTTP
if response:
encinfo.http_media_type, encinfo.http_encoding = getHTTPInfo(
response, log)
texttype = _getTextTypeByMediaType(encinfo.http_media_type, log)
else:
# check if maybe XML or (TODO:) HTML
texttype = _getTextType(text, log)
# XML only served as application/xml ! #(also XHTML served as text/html)
if texttype == _XML_APPLICATION_TYPE:# or texttype == _XML_TEXT_TYPE:
try:
encinfo.xml_encoding = detectXMLEncoding(text, log)
except (AttributeError, ValueError), e:
encinfo.xml_encoding = None
# XML (also XHTML served as text/html)
if texttype == _HTML_TEXT_TYPE:
try:
encinfo.xml_encoding = detectXMLEncoding(text, log, includeDefault=False)
except (AttributeError, ValueError), e:
encinfo.xml_encoding = None
# HTML
if texttype == _HTML_TEXT_TYPE or texttype == _TEXT_TYPE:
encinfo.meta_media_type, encinfo.meta_encoding = getMetaInfo(
text, log)
# guess
# 1. HTTP charset?
encinfo.encoding = encinfo.http_encoding
encinfo.mismatch = False
# 2. media_type?
# XML application/...
if texttype == _XML_APPLICATION_TYPE:
if not encinfo.encoding:
encinfo.encoding = encinfo.xml_encoding
# xml_encoding has default of utf-8
# text/html
elif texttype == _HTML_TEXT_TYPE:
if not encinfo.encoding:
encinfo.encoding = encinfo.meta_encoding
if not encinfo.encoding:
encinfo.encoding = encodingByMediaType(encinfo.http_media_type)
if not encinfo.encoding:
encinfo.encoding = tryEncodings(text)
# text/... + xml or text/*
elif texttype == _XML_TEXT_TYPE or texttype == _TEXT_TYPE:
if not encinfo.encoding:
encinfo.encoding = encodingByMediaType(encinfo.http_media_type)
elif texttype == _TEXT_UTF8:
if not encinfo.encoding:
encinfo.encoding = encodingByMediaType(encinfo.http_media_type)
# possible mismatches, checks if present at all and then if equal
# HTTP + XML
if encinfo.http_encoding and encinfo.xml_encoding and\
encinfo.http_encoding != encinfo.xml_encoding:
encinfo.mismatch = True
log.warn(u'"%s" (HTTP) != "%s" (XML) encoding mismatch' %
(encinfo.http_encoding, encinfo.xml_encoding))
# HTTP + Meta
if encinfo.http_encoding and encinfo.meta_encoding and\
encinfo.http_encoding != encinfo.meta_encoding:
encinfo.mismatch = True
log.warn(u'"%s" (HTTP) != "%s" (HTML <meta>) encoding mismatch' %
(encinfo.http_encoding, encinfo.meta_encoding))
# XML + Meta
if encinfo.xml_encoding and encinfo.meta_encoding and\
encinfo.xml_encoding != encinfo.meta_encoding:
encinfo.mismatch = True
log.warn(u'"%s" (XML) != "%s" (HTML <meta>) encoding mismatch' %
(encinfo.xml_encoding, encinfo.meta_encoding))
log.info(u'Encoding (probably): %s (Mismatch: %s)',
encinfo.encoding, encinfo.mismatch)
encinfo.logtext = logstream.getvalue()
return encinfo
if __name__ == '__main__':
import pydoc
pydoc.help(__name__)

202
libs/minify/cssmin.py

@ -0,0 +1,202 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# `cssmin.py` - A Python port of the YUI CSS compressor.
from StringIO import StringIO # The pure-Python StringIO supports unicode.
import re
__version__ = '0.1.1'
def remove_comments(css):
"""Remove all CSS comment blocks."""
iemac = False
preserve = False
comment_start = css.find("/*")
while comment_start >= 0:
# Preserve comments that look like `/*!...*/`.
# Slicing is used to make sure we don"t get an IndexError.
preserve = css[comment_start + 2:comment_start + 3] == "!"
comment_end = css.find("*/", comment_start + 2)
if comment_end < 0:
if not preserve:
css = css[:comment_start]
break
elif comment_end >= (comment_start + 2):
if css[comment_end - 1] == "\\":
# This is an IE Mac-specific comment; leave this one and the
# following one alone.
comment_start = comment_end + 2
iemac = True
elif iemac:
comment_start = comment_end + 2
iemac = False
elif not preserve:
css = css[:comment_start] + css[comment_end + 2:]
else:
comment_start = comment_end + 2
comment_start = css.find("/*", comment_start)
return css
def remove_unnecessary_whitespace(css):
"""Remove unnecessary whitespace characters."""
def pseudoclasscolon(css):
"""
Prevents 'p :link' from becoming 'p:link'.
Translates 'p :link' into 'p ___PSEUDOCLASSCOLON___link'; this is
translated back again later.
"""
regex = re.compile(r"(^|\})(([^\{\:])+\:)+([^\{]*\{)")
match = regex.search(css)
while match:
css = ''.join([
css[:match.start()],
match.group().replace(":", "___PSEUDOCLASSCOLON___"),
css[match.end():]])
match = regex.search(css)
return css
css = pseudoclasscolon(css)
# Remove spaces from before things.
css = re.sub(r"\s+([!{};:>+\(\)\],])", r"\1", css)
# If there is a `@charset`, then only allow one, and move to the beginning.
css = re.sub(r"^(.*)(@charset \"[^\"]*\";)", r"\2\1", css)
css = re.sub(r"^(\s*@charset [^;]+;\s*)+", r"\1", css)
# Put the space back in for a few cases, such as `@media screen` and
# `(-webkit-min-device-pixel-ratio:0)`.
css = re.sub(r"\band\(", "and (", css)
# Put the colons back.
css = css.replace('___PSEUDOCLASSCOLON___', ':')
# Remove spaces from after things.
css = re.sub(r"([!{}:;>+\(\[,])\s+", r"\1", css)
return css
def remove_unnecessary_semicolons(css):
"""Remove unnecessary semicolons."""
return re.sub(r";+\}", "}", css)
def remove_empty_rules(css):
"""Remove empty rules."""
return re.sub(r"[^\}\{]+\{\}", "", css)
def normalize_rgb_colors_to_hex(css):
"""Convert `rgb(51,102,153)` to `#336699`."""
regex = re.compile(r"rgb\s*\(\s*([0-9,\s]+)\s*\)")
match = regex.search(css)
while match:
colors = match.group(1).split(",")
hexcolor = '#%.2x%.2x%.2x' % tuple(map(int, colors))
css = css.replace(match.group(), hexcolor)
match = regex.search(css)
return css
def condense_zero_units(css):
"""Replace `0(px, em, %, etc)` with `0`."""
return re.sub(r"([\s:])(0)(px|em|%|in|cm|mm|pc|pt|ex)", r"\1\2", css)
def condense_multidimensional_zeros(css):
"""Replace `:0 0 0 0;`, `:0 0 0;` etc. with `:0;`."""
css = css.replace(":0 0 0 0;", ":0;")
css = css.replace(":0 0 0;", ":0;")
css = css.replace(":0 0;", ":0;")
# Revert `background-position:0;` to the valid `background-position:0 0;`.
css = css.replace("background-position:0;", "background-position:0 0;")
return css
def condense_floating_points(css):
"""Replace `0.6` with `.6` where possible."""
return re.sub(r"(:|\s)0+\.(\d+)", r"\1.\2", css)
def condense_hex_colors(css):
"""Shorten colors from #AABBCC to #ABC where possible."""
regex = re.compile(r"([^\"'=\s])(\s*)#([0-9a-fA-F])([0-9a-fA-F])([0-9a-fA-F])([0-9a-fA-F])([0-9a-fA-F])([0-9a-fA-F])")
match = regex.search(css)
while match:
first = match.group(3) + match.group(5) + match.group(7)
second = match.group(4) + match.group(6) + match.group(8)
if first.lower() == second.lower():
css = css.replace(match.group(), match.group(1) + match.group(2) + '#' + first)
match = regex.search(css, match.end() - 3)
else:
match = regex.search(css, match.end())
return css
def condense_whitespace(css):
"""Condense multiple adjacent whitespace characters into one."""
return re.sub(r"\s+", " ", css)
def condense_semicolons(css):
"""Condense multiple adjacent semicolon characters into one."""
return re.sub(r";;+", ";", css)
def wrap_css_lines(css, line_length):
"""Wrap the lines of the given CSS to an approximate length."""
lines = []
line_start = 0
for i, char in enumerate(css):
# It's safe to break after `}` characters.
if char == '}' and (i - line_start >= line_length):
lines.append(css[line_start:i + 1])
line_start = i + 1
if line_start < len(css):
lines.append(css[line_start:])
return '\n'.join(lines)
def cssmin(css, wrap = None):
css = remove_comments(css)
css = condense_whitespace(css)
# A pseudo class for the Box Model Hack
# (see http://tantek.com/CSS/Examples/boxmodelhack.html)
css = css.replace('"\\"}\\""', "___PSEUDOCLASSBMH___")
#css = remove_unnecessary_whitespace(css)
css = remove_unnecessary_semicolons(css)
css = condense_zero_units(css)
css = condense_multidimensional_zeros(css)
css = condense_floating_points(css)
css = normalize_rgb_colors_to_hex(css)
css = condense_hex_colors(css)
if wrap is not None:
css = wrap_css_lines(css, wrap)
css = css.replace("___PSEUDOCLASSBMH___", '"\\"}\\""')
css = condense_semicolons(css)
return css.strip()
Loading…
Cancel
Save