You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
804 lines
32 KiB
804 lines
32 KiB
"""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)
|
|
|