58 changed files with 236 additions and 17924 deletions
@ -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') |
@ -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'] |
@ -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, |
|||
} |
@ -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__ |
@ -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) |
@ -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) |
@ -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() |
@ -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) |
@ -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 |
@ -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 * |
@ -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), |
|||
} |
@ -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)) |
@ -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) |
@ -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) |
@ -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) |
@ -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) |
@ -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) |
|||
|
@ -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) |
@ -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))) |
@ -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) |
@ -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 |
@ -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!") |
@ -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) |
@ -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) |
@ -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)) |
|||
|
File diff suppressed because it is too large
@ -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.") |
@ -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) |
@ -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)) |
|||
|
@ -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.") |
@ -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") |
@ -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))) |
|||
|
@ -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 |
|||
) |
|||
) |
|||
) |
@ -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]) |
@ -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\..+\(' |
|||
) |
@ -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__ |
@ -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) |
@ -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 |
@ -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 |
|||
) |
@ -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})*', |
|||
} |
@ -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 |
|||
|
@ -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 |
@ -1,4 +0,0 @@ |
|||
from csscombine import csscombine |
|||
__all__ = ["csscapture", "csscombine", "cssparse"] |
|||
|
|||
|
@ -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()) |
@ -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()) |
@ -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()) |
File diff suppressed because it is too large
@ -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) |
@ -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 * |
@ -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) |
@ -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))) |
@ -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``.") |
@ -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.") |
|||
|
@ -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) |
@ -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 |
@ -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__) |
@ -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…
Reference in new issue