@ -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) |