@ -0,0 +1,6 @@ |
|||
from .main import Criticker |
|||
|
|||
def start(): |
|||
return Criticker() |
|||
|
|||
config = [] |
@ -0,0 +1,6 @@ |
|||
from couchpotato.core.providers.userscript.base import UserscriptBase |
|||
|
|||
|
|||
class Criticker(UserscriptBase): |
|||
|
|||
includes = ['http://www.criticker.com/film/*'] |
After Width: | Height: | Size: 213 KiB |
After Width: | Height: | Size: 25 KiB |
After Width: | Height: | Size: 26 KiB |
After Width: | Height: | Size: 27 KiB |
After Width: | Height: | Size: 25 KiB |
Before Width: | Height: | Size: 1.8 KiB After Width: | Height: | Size: 2.7 KiB |
@ -1,487 +0,0 @@ |
|||
/** |
|||
* StyleFix 1.0.3 & PrefixFree 1.0.7 |
|||
* @author Lea Verou |
|||
* MIT license |
|||
*/ |
|||
|
|||
(function(){ |
|||
|
|||
if(!window.addEventListener) { |
|||
return; |
|||
} |
|||
|
|||
var self = window.StyleFix = { |
|||
link: function(link) { |
|||
try { |
|||
// Ignore stylesheets with data-noprefix attribute as well as alternate stylesheets
|
|||
if(link.rel !== 'stylesheet' || link.hasAttribute('data-noprefix')) { |
|||
return; |
|||
} |
|||
} |
|||
catch(e) { |
|||
return; |
|||
} |
|||
|
|||
var url = link.href || link.getAttribute('data-href'), |
|||
base = url.replace(/[^\/]+$/, ''), |
|||
base_scheme = (/^[a-z]{3,10}:/.exec(base) || [''])[0], |
|||
base_domain = (/^[a-z]{3,10}:\/\/[^\/]+/.exec(base) || [''])[0], |
|||
base_query = /^([^?]*)\??/.exec(url)[1], |
|||
parent = link.parentNode, |
|||
xhr = new XMLHttpRequest(), |
|||
process; |
|||
|
|||
xhr.onreadystatechange = function() { |
|||
if(xhr.readyState === 4) { |
|||
process(); |
|||
} |
|||
}; |
|||
|
|||
process = function() { |
|||
var css = xhr.responseText; |
|||
|
|||
if(css && link.parentNode && (!xhr.status || xhr.status < 400 || xhr.status > 600)) { |
|||
css = self.fix(css, true, link); |
|||
|
|||
// Convert relative URLs to absolute, if needed
|
|||
if(base) { |
|||
css = css.replace(/url\(\s*?((?:"|')?)(.+?)\1\s*?\)/gi, function($0, quote, url) { |
|||
if(/^([a-z]{3,10}:|#)/i.test(url)) { // Absolute & or hash-relative
|
|||
return $0; |
|||
} |
|||
else if(/^\/\//.test(url)) { // Scheme-relative
|
|||
// May contain sequences like /../ and /./ but those DO work
|
|||
return 'url("' + base_scheme + url + '")'; |
|||
} |
|||
else if(/^\//.test(url)) { // Domain-relative
|
|||
return 'url("' + base_domain + url + '")'; |
|||
} |
|||
else if(/^\?/.test(url)) { // Query-relative
|
|||
return 'url("' + base_query + url + '")'; |
|||
} |
|||
else { |
|||
// Path-relative
|
|||
return 'url("' + base + url + '")'; |
|||
} |
|||
}); |
|||
|
|||
// behavior URLs shoudn’t be converted (Issue #19)
|
|||
// base should be escaped before added to RegExp (Issue #81)
|
|||
var escaped_base = base.replace(/([\\\^\$*+[\]?{}.=!:(|)])/g,"\\$1"); |
|||
css = css.replace(RegExp('\\b(behavior:\\s*?url\\(\'?"?)' + escaped_base, 'gi'), '$1'); |
|||
} |
|||
|
|||
var style = document.createElement('style'); |
|||
style.textContent = css; |
|||
style.media = link.media; |
|||
style.disabled = link.disabled; |
|||
style.setAttribute('data-href', link.getAttribute('href')); |
|||
|
|||
parent.insertBefore(style, link); |
|||
parent.removeChild(link); |
|||
|
|||
style.media = link.media; // Duplicate is intentional. See issue #31
|
|||
} |
|||
}; |
|||
|
|||
try { |
|||
xhr.open('GET', url); |
|||
xhr.send(null); |
|||
} catch (e) { |
|||
// Fallback to XDomainRequest if available
|
|||
if (typeof XDomainRequest != "undefined") { |
|||
xhr = new XDomainRequest(); |
|||
xhr.onerror = xhr.onprogress = function() {}; |
|||
xhr.onload = process; |
|||
xhr.open("GET", url); |
|||
xhr.send(null); |
|||
} |
|||
} |
|||
|
|||
link.setAttribute('data-inprogress', ''); |
|||
}, |
|||
|
|||
styleElement: function(style) { |
|||
if (style.hasAttribute('data-noprefix')) { |
|||
return; |
|||
} |
|||
var disabled = style.disabled; |
|||
|
|||
style.textContent = self.fix(style.textContent, true, style); |
|||
|
|||
style.disabled = disabled; |
|||
}, |
|||
|
|||
styleAttribute: function(element) { |
|||
var css = element.getAttribute('style'); |
|||
|
|||
css = self.fix(css, false, element); |
|||
|
|||
element.setAttribute('style', css); |
|||
}, |
|||
|
|||
process: function() { |
|||
// Linked stylesheets
|
|||
$('link[rel="stylesheet"]:not([data-inprogress])').forEach(StyleFix.link); |
|||
|
|||
// Inline stylesheets
|
|||
$('style').forEach(StyleFix.styleElement); |
|||
|
|||
// Inline styles
|
|||
$('[style]').forEach(StyleFix.styleAttribute); |
|||
}, |
|||
|
|||
register: function(fixer, index) { |
|||
(self.fixers = self.fixers || []) |
|||
.splice(index === undefined? self.fixers.length : index, 0, fixer); |
|||
}, |
|||
|
|||
fix: function(css, raw, element) { |
|||
for(var i=0; i<self.fixers.length; i++) { |
|||
css = self.fixers[i](css, raw, element) || css; |
|||
} |
|||
|
|||
return css; |
|||
}, |
|||
|
|||
camelCase: function(str) { |
|||
return str.replace(/-([a-z])/g, function($0, $1) { return $1.toUpperCase(); }).replace('-',''); |
|||
}, |
|||
|
|||
deCamelCase: function(str) { |
|||
return str.replace(/[A-Z]/g, function($0) { return '-' + $0.toLowerCase() }); |
|||
} |
|||
}; |
|||
|
|||
/************************************** |
|||
* Process styles |
|||
**************************************/ |
|||
(function(){ |
|||
setTimeout(function(){ |
|||
$('link[rel="stylesheet"]').forEach(StyleFix.link); |
|||
}, 10); |
|||
|
|||
document.addEventListener('DOMContentLoaded', StyleFix.process, false); |
|||
})(); |
|||
|
|||
function $(expr, con) { |
|||
return [].slice.call((con || document).querySelectorAll(expr)); |
|||
} |
|||
|
|||
})(); |
|||
|
|||
/** |
|||
* PrefixFree |
|||
*/ |
|||
(function(root){ |
|||
|
|||
if(!window.StyleFix || !window.getComputedStyle) { |
|||
return; |
|||
} |
|||
|
|||
// Private helper
|
|||
function fix(what, before, after, replacement, css) { |
|||
what = self[what]; |
|||
|
|||
if(what.length) { |
|||
var regex = RegExp(before + '(' + what.join('|') + ')' + after, 'gi'); |
|||
|
|||
css = css.replace(regex, replacement); |
|||
} |
|||
|
|||
return css; |
|||
} |
|||
|
|||
var self = window.PrefixFree = { |
|||
prefixCSS: function(css, raw, element) { |
|||
var prefix = self.prefix; |
|||
|
|||
// Gradient angles hotfix
|
|||
if(self.functions.indexOf('linear-gradient') > -1) { |
|||
// Gradients are supported with a prefix, convert angles to legacy
|
|||
css = css.replace(/(\s|:|,)(repeating-)?linear-gradient\(\s*(-?\d*\.?\d*)deg/ig, function ($0, delim, repeating, deg) { |
|||
return delim + (repeating || '') + 'linear-gradient(' + (90-deg) + 'deg'; |
|||
}); |
|||
} |
|||
|
|||
css = fix('functions', '(\\s|:|,)', '\\s*\\(', '$1' + prefix + '$2(', css); |
|||
css = fix('keywords', '(\\s|:)', '(\\s|;|\\}|$)', '$1' + prefix + '$2$3', css); |
|||
css = fix('properties', '(^|\\{|\\s|;)', '\\s*:', '$1' + prefix + '$2:', css); |
|||
|
|||
// Prefix properties *inside* values (issue #8)
|
|||
if (self.properties.length) { |
|||
var regex = RegExp('\\b(' + self.properties.join('|') + ')(?!:)', 'gi'); |
|||
|
|||
css = fix('valueProperties', '\\b', ':(.+?);', function($0) { |
|||
return $0.replace(regex, prefix + "$1") |
|||
}, css); |
|||
} |
|||
|
|||
if(raw) { |
|||
css = fix('selectors', '', '\\b', self.prefixSelector, css); |
|||
css = fix('atrules', '@', '\\b', '@' + prefix + '$1', css); |
|||
} |
|||
|
|||
// Fix double prefixing
|
|||
css = css.replace(RegExp('-' + prefix, 'g'), '-'); |
|||
|
|||
// Prefix wildcard
|
|||
css = css.replace(/-\*-(?=[a-z]+)/gi, self.prefix); |
|||
|
|||
return css; |
|||
}, |
|||
|
|||
property: function(property) { |
|||
return (self.properties.indexOf(property)? self.prefix : '') + property; |
|||
}, |
|||
|
|||
value: function(value, property) { |
|||
value = fix('functions', '(^|\\s|,)', '\\s*\\(', '$1' + self.prefix + '$2(', value); |
|||
value = fix('keywords', '(^|\\s)', '(\\s|$)', '$1' + self.prefix + '$2$3', value); |
|||
|
|||
// TODO properties inside values
|
|||
|
|||
return value; |
|||
}, |
|||
|
|||
// Warning: Prefixes no matter what, even if the selector is supported prefix-less
|
|||
prefixSelector: function(selector) { |
|||
return selector.replace(/^:{1,2}/, function($0) { return $0 + self.prefix }) |
|||
}, |
|||
|
|||
// Warning: Prefixes no matter what, even if the property is supported prefix-less
|
|||
prefixProperty: function(property, camelCase) { |
|||
var prefixed = self.prefix + property; |
|||
|
|||
return camelCase? StyleFix.camelCase(prefixed) : prefixed; |
|||
} |
|||
}; |
|||
|
|||
/************************************** |
|||
* Properties |
|||
**************************************/ |
|||
(function() { |
|||
var prefixes = {}, |
|||
properties = [], |
|||
shorthands = {}, |
|||
style = getComputedStyle(document.documentElement, null), |
|||
dummy = document.createElement('div').style; |
|||
|
|||
// Why are we doing this instead of iterating over properties in a .style object? Cause Webkit won't iterate over those.
|
|||
var iterate = function(property) { |
|||
if(property.charAt(0) === '-') { |
|||
properties.push(property); |
|||
|
|||
var parts = property.split('-'), |
|||
prefix = parts[1]; |
|||
|
|||
// Count prefix uses
|
|||
prefixes[prefix] = ++prefixes[prefix] || 1; |
|||
|
|||
// This helps determining shorthands
|
|||
while(parts.length > 3) { |
|||
parts.pop(); |
|||
|
|||
var shorthand = parts.join('-'); |
|||
|
|||
if(supported(shorthand) && properties.indexOf(shorthand) === -1) { |
|||
properties.push(shorthand); |
|||
} |
|||
} |
|||
} |
|||
}, |
|||
supported = function(property) { |
|||
return StyleFix.camelCase(property) in dummy; |
|||
} |
|||
|
|||
// Some browsers have numerical indices for the properties, some don't
|
|||
if(style.length > 0) { |
|||
for(var i=0; i<style.length; i++) { |
|||
iterate(style[i]) |
|||
} |
|||
} |
|||
else { |
|||
for(var property in style) { |
|||
iterate(StyleFix.deCamelCase(property)); |
|||
} |
|||
} |
|||
|
|||
// Find most frequently used prefix
|
|||
var highest = {uses:0}; |
|||
for(var prefix in prefixes) { |
|||
var uses = prefixes[prefix]; |
|||
|
|||
if(highest.uses < uses) { |
|||
highest = {prefix: prefix, uses: uses}; |
|||
} |
|||
} |
|||
|
|||
self.prefix = '-' + highest.prefix + '-'; |
|||
self.Prefix = StyleFix.camelCase(self.prefix); |
|||
|
|||
self.properties = []; |
|||
|
|||
// Get properties ONLY supported with a prefix
|
|||
for(var i=0; i<properties.length; i++) { |
|||
var property = properties[i]; |
|||
|
|||
if(property.indexOf(self.prefix) === 0) { // we might have multiple prefixes, like Opera
|
|||
var unprefixed = property.slice(self.prefix.length); |
|||
|
|||
if(!supported(unprefixed)) { |
|||
self.properties.push(unprefixed); |
|||
} |
|||
} |
|||
} |
|||
|
|||
// IE fix
|
|||
if(self.Prefix == 'Ms' |
|||
&& !('transform' in dummy) |
|||
&& !('MsTransform' in dummy) |
|||
&& ('msTransform' in dummy)) { |
|||
self.properties.push('transform', 'transform-origin'); |
|||
} |
|||
|
|||
self.properties.sort(); |
|||
})(); |
|||
|
|||
/************************************** |
|||
* Values |
|||
**************************************/ |
|||
(function() { |
|||
// Values that might need prefixing
|
|||
var functions = { |
|||
'linear-gradient': { |
|||
property: 'backgroundImage', |
|||
params: 'red, teal' |
|||
}, |
|||
'calc': { |
|||
property: 'width', |
|||
params: '1px + 5%' |
|||
}, |
|||
'element': { |
|||
property: 'backgroundImage', |
|||
params: '#foo' |
|||
}, |
|||
'cross-fade': { |
|||
property: 'backgroundImage', |
|||
params: 'url(a.png), url(b.png), 50%' |
|||
} |
|||
}; |
|||
|
|||
|
|||
functions['repeating-linear-gradient'] = |
|||
functions['repeating-radial-gradient'] = |
|||
functions['radial-gradient'] = |
|||
functions['linear-gradient']; |
|||
|
|||
var keywords = { |
|||
'initial': 'color', |
|||
'zoom-in': 'cursor', |
|||
'zoom-out': 'cursor', |
|||
'box': 'display', |
|||
'flexbox': 'display', |
|||
'inline-flexbox': 'display', |
|||
'flex': 'display', |
|||
'inline-flex': 'display' |
|||
}; |
|||
|
|||
self.functions = []; |
|||
self.keywords = []; |
|||
|
|||
var style = document.createElement('div').style; |
|||
|
|||
function supported(value, property) { |
|||
style[property] = ''; |
|||
style[property] = value; |
|||
|
|||
return !!style[property]; |
|||
} |
|||
|
|||
for (var func in functions) { |
|||
var test = functions[func], |
|||
property = test.property, |
|||
value = func + '(' + test.params + ')'; |
|||
|
|||
if (!supported(value, property) |
|||
&& supported(self.prefix + value, property)) { |
|||
// It's supported, but with a prefix
|
|||
self.functions.push(func); |
|||
} |
|||
} |
|||
|
|||
for (var keyword in keywords) { |
|||
var property = keywords[keyword]; |
|||
|
|||
if (!supported(keyword, property) |
|||
&& supported(self.prefix + keyword, property)) { |
|||
// It's supported, but with a prefix
|
|||
self.keywords.push(keyword); |
|||
} |
|||
} |
|||
|
|||
})(); |
|||
|
|||
/************************************** |
|||
* Selectors and @-rules |
|||
**************************************/ |
|||
(function() { |
|||
|
|||
var |
|||
selectors = { |
|||
':read-only': null, |
|||
':read-write': null, |
|||
':any-link': null, |
|||
'::selection': null |
|||
}, |
|||
|
|||
atrules = { |
|||
'keyframes': 'name', |
|||
'viewport': null, |
|||
'document': 'regexp(".")' |
|||
}; |
|||
|
|||
self.selectors = []; |
|||
self.atrules = []; |
|||
|
|||
var style = root.appendChild(document.createElement('style')); |
|||
|
|||
function supported(selector) { |
|||
style.textContent = selector + '{}'; // Safari 4 has issues with style.innerHTML
|
|||
|
|||
return !!style.sheet.cssRules.length; |
|||
} |
|||
|
|||
for(var selector in selectors) { |
|||
var test = selector + (selectors[selector]? '(' + selectors[selector] + ')' : ''); |
|||
|
|||
if(!supported(test) && supported(self.prefixSelector(test))) { |
|||
self.selectors.push(selector); |
|||
} |
|||
} |
|||
|
|||
for(var atrule in atrules) { |
|||
var test = atrule + ' ' + (atrules[atrule] || ''); |
|||
|
|||
if(!supported('@' + test) && supported('@' + self.prefix + test)) { |
|||
self.atrules.push(atrule); |
|||
} |
|||
} |
|||
|
|||
root.removeChild(style); |
|||
|
|||
})(); |
|||
|
|||
// Properties that accept properties as their value
|
|||
self.valueProperties = [ |
|||
'transition', |
|||
'transition-property' |
|||
] |
|||
|
|||
// Add class for current prefix
|
|||
root.className += ' ' + self.prefix; |
|||
|
|||
StyleFix.register(self.prefixCSS); |
|||
|
|||
|
|||
})(document.documentElement); |
@ -0,0 +1,20 @@ |
|||
# 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') |
@ -0,0 +1,117 @@ |
|||
# 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 cssutils |
|||
import re |
|||
from rules import rules as tr_rules |
|||
from rules import prefixRegex |
|||
|
|||
|
|||
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 unicode(cssText) |
|||
return unicode(cssText) + '\n' |
|||
|
|||
|
|||
def process(string, debug = False, minify = False, filt = ['webkit', 'moz', 'o', 'ms'], **prefs): |
|||
loglevel = 'DEBUG' if debug else 'ERROR' |
|||
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'] |
@ -0,0 +1,271 @@ |
|||
# 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, |
|||
} |
@ -0,0 +1,385 @@ |
|||
#!/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__ |
@ -0,0 +1,584 @@ |
|||
#!/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) |
@ -0,0 +1,608 @@ |
|||
#!/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) |
@ -0,0 +1,44 @@ |
|||
"""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() |
@ -0,0 +1,68 @@ |
|||
"""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) |
@ -0,0 +1,16 @@ |
|||
#!/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 |
@ -0,0 +1,80 @@ |
|||
"""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 * |
@ -0,0 +1,184 @@ |
|||
# -*- 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), |
|||
} |
@ -0,0 +1,159 @@ |
|||
"""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)) |
@ -0,0 +1,87 @@ |
|||
"""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) |
@ -0,0 +1,184 @@ |
|||
"""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) |
@ -0,0 +1,396 @@ |
|||
"""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) |
@ -0,0 +1,302 @@ |
|||
"""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) |
@ -0,0 +1,295 @@ |
|||
"""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) |
|||
|
@ -0,0 +1,436 @@ |
|||
"""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) |
@ -0,0 +1,122 @@ |
|||
"""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))) |
@ -0,0 +1,304 @@ |
|||
"""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) |
@ -0,0 +1,53 @@ |
|||
"""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 |
@ -0,0 +1,697 @@ |
|||
"""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!") |
@ -0,0 +1,234 @@ |
|||
"""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) |
@ -0,0 +1,804 @@ |
|||
"""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) |
@ -0,0 +1,209 @@ |
|||
"""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)) |
|||
|
@ -0,0 +1,330 @@ |
|||
"""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.") |
@ -0,0 +1,198 @@ |
|||
"""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) |