Update bundled version of cssutils. Fixes #3162 (Calibre does not support complete font-face standard)

This commit is contained in:
Kovid Goyal 2009-08-15 10:29:27 -06:00
parent 976b3139db
commit b1c8d3d9a9
25 changed files with 483 additions and 336 deletions

View File

@ -88,7 +88,7 @@ _magick_error = None
try: try:
_magick = ctypes.CDLL(_lib) _magick = ctypes.CDLL(_lib)
except Exception, err: except Exception, err:
global _magick_error #global _magick_error
_magick_error = str(err) _magick_error = str(err)
_initialized = False _initialized = False

View File

@ -70,11 +70,11 @@ Usage may be::
__all__ = ['css', 'stylesheets', 'CSSParser', 'CSSSerializer'] __all__ = ['css', 'stylesheets', 'CSSParser', 'CSSSerializer']
__docformat__ = 'restructuredtext' __docformat__ = 'restructuredtext'
__author__ = 'Christof Hoeke with contributions by Walter Doerwald' __author__ = 'Christof Hoeke with contributions by Walter Doerwald'
__date__ = '$LastChangedDate:: 2009-05-09 13:59:54 -0700 #$:' __date__ = '$LastChangedDate:: 2009-08-01 16:10:11 -0600 #$:'
VERSION = '0.9.6a5' VERSION = '0.9.6b3'
__version__ = '%s $Id: __init__.py 1747 2009-05-09 20:59:54Z cthedot $' % VERSION __version__ = '%s $Id: __init__.py 1832 2009-08-01 22:10:11Z cthedot $' % VERSION
import codec import codec
import xml.dom import xml.dom
@ -165,6 +165,23 @@ def parse(*a, **k):
return parseFile(*a, **k) return parseFile(*a, **k)
parse.__doc__ = CSSParser.parse.__doc__ parse.__doc__ = CSSParser.parse.__doc__
def parseStyle(cssText, encoding='utf-8'):
"""Parse given `cssText` which is assumed to be the content of
a HTML style attribute.
:param cssText:
CSS string to parse
:param encoding:
It will be used to decode `cssText` if given as a (byte)
string.
:returns:
:class:`~cssutils.css.CSSStyleDeclaration`
"""
if isinstance(cssText, str):
cssText = cssText.decode(encoding)
style = css.CSSStyleDeclaration()
style.cssText = cssText
return style
# set "ser", default serializer # set "ser", default serializer
def setSerializer(serializer): def setSerializer(serializer):
@ -172,7 +189,6 @@ def setSerializer(serializer):
global ser global ser
ser = serializer ser = serializer
def getUrls(sheet): def getUrls(sheet):
"""Retrieve all ``url(urlstring)`` values (in e.g. """Retrieve all ``url(urlstring)`` values (in e.g.
:class:`cssutils.css.CSSImportRule` or :class:`cssutils.css.CSSValue` :class:`cssutils.css.CSSImportRule` or :class:`cssutils.css.CSSValue`
@ -284,5 +300,6 @@ def resolveImports(sheet, target=None):
target.add(rule) target.add(rule)
return target return target
if __name__ == '__main__': if __name__ == '__main__':
print __doc__ print __doc__

View File

@ -1,8 +1,9 @@
"""Default URL reading functions""" """Default URL reading functions"""
__all__ = ['_defaultFetcher', '_readUrl'] __all__ = ['_defaultFetcher']
__docformat__ = 'restructuredtext' __docformat__ = 'restructuredtext'
__version__ = '$Id: tokenize2.py 1547 2008-12-10 20:42:26Z cthedot $' __version__ = '$Id: tokenize2.py 1547 2008-12-10 20:42:26Z cthedot $'
from cssutils import VERSION
import encutils import encutils
import errorhandler import errorhandler
import urllib2 import urllib2
@ -16,8 +17,11 @@ def _defaultFetcher(url):
Returns ``(encoding, string)`` or ``None`` Returns ``(encoding, string)`` or ``None``
""" """
try: request = urllib2.Request(url)
res = urllib2.urlopen(url) request.add_header('User-agent',
'cssutils %s (http://www.cthedot.de/cssutils/)' % VERSION)
try:
res = urllib2.urlopen(request)
except OSError, e: except OSError, e:
# e.g if file URL and not found # e.g if file URL and not found
log.warn(e, error=OSError) log.warn(e, error=OSError)

View File

@ -1,5 +1,5 @@
"""GAE specific URL reading functions""" """GAE specific URL reading functions"""
__all__ = ['_defaultFetcher', '_readUrl'] __all__ = ['_defaultFetcher']
__docformat__ = 'restructuredtext' __docformat__ = 'restructuredtext'
__version__ = '$Id: tokenize2.py 1547 2008-12-10 20:42:26Z cthedot $' __version__ = '$Id: tokenize2.py 1547 2008-12-10 20:42:26Z cthedot $'

View File

@ -1,8 +1,11 @@
"""CSSFontFaceRule implements DOM Level 2 CSS CSSFontFaceRule. """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'] __all__ = ['CSSFontFaceRule']
__docformat__ = 'restructuredtext' __docformat__ = 'restructuredtext'
__version__ = '$Id: cssfontfacerule.py 1638 2009-01-13 20:39:33Z cthedot $' __version__ = '$Id: cssfontfacerule.py 1818 2009-07-30 21:39:00Z cthedot $'
from cssstyledeclaration import CSSStyleDeclaration from cssstyledeclaration import CSSStyleDeclaration
import cssrule import cssrule
@ -21,6 +24,11 @@ class CSSFontFaceRule(cssrule.CSSRule):
: FONT_FACE_SYM S* : FONT_FACE_SYM S*
'{' S* declaration [ ';' S* declaration ]* '}' 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, def __init__(self, style=None, parentRule=None,
parentStyleSheet=None, readonly=False): parentStyleSheet=None, readonly=False):
@ -28,25 +36,26 @@ class CSSFontFaceRule(cssrule.CSSRule):
If readonly allows setting of properties in constructor only. If readonly allows setting of properties in constructor only.
:param style: :param style:
CSSStyleDeclaration for this CSSStyleRule CSSStyleDeclaration used to hold any font descriptions
for this CSSFontFaceRule
""" """
super(CSSFontFaceRule, self).__init__(parentRule=parentRule, super(CSSFontFaceRule, self).__init__(parentRule=parentRule,
parentStyleSheet=parentStyleSheet) parentStyleSheet=parentStyleSheet)
self._atkeyword = u'@font-face' self._atkeyword = u'@font-face'
self._style = CSSStyleDeclaration(parentRule=self)
if style: if style:
self.style = style self.style = style
else:
self._style = CSSStyleDeclaration(parentRule=self)
self._readonly = readonly self._readonly = readonly
def __repr__(self): def __repr__(self):
return "cssutils.css.%s(style=%r)" % ( return "cssutils.css.%s(style=%r)" % (
self.__class__.__name__, self.style.cssText) self.__class__.__name__, self.style.cssText)
def __str__(self): def __str__(self):
return "<cssutils.css.%s object style=%r at 0x%x>" % ( return "<cssutils.css.%s object style=%r valid=%r at 0x%x>" % (
self.__class__.__name__, self.style.cssText, id(self)) self.__class__.__name__, self.style.cssText, self.valid,
id(self))
def _getCssText(self): def _getCssText(self):
"""Return serialized property cssText.""" """Return serialized property cssText."""
@ -112,15 +121,22 @@ class CSSFontFaceRule(cssrule.CSSRule):
self._log.error(u'CSSFontFaceRule: Trailing content found.', self._log.error(u'CSSFontFaceRule: Trailing content found.',
token=nonetoken) token=nonetoken)
newstyle = CSSStyleDeclaration() teststyle = CSSStyleDeclaration(parentRule=self)
if 'EOF' == typ: if 'EOF' == typ:
# add again as style needs it # add again as style needs it
styletokens.append(braceorEOFtoken) styletokens.append(braceorEOFtoken)
newstyle.cssText = styletokens # may raise:
teststyle.cssText = styletokens
if wellformed: if wellformed:
self.style = newstyle # contains probably comments only upto {
self._setSeq(newseq) # contains (probably comments) upto { only self._setSeq(newseq)
# known as correct from before
cssutils.log.enabled = False
self.style.cssText = styletokens
cssutils.log.enabled = True
cssText = property(_getCssText, _setCssText, cssText = property(_getCssText, _setCssText,
doc="(DOM) The parsable textual representation of this rule.") doc="(DOM) The parsable textual representation of this rule.")
@ -132,9 +148,10 @@ class CSSFontFaceRule(cssrule.CSSRule):
""" """
self._checkReadonly() self._checkReadonly()
if isinstance(style, basestring): if isinstance(style, basestring):
self._style = CSSStyleDeclaration(parentRule=self, cssText=style) self._style.cssText = style
else: else:
self._style._seq = style.seq self._style = style
self._style.parentRule = self
style = property(lambda self: self._style, _setStyle, style = property(lambda self: self._style, _setStyle,
doc="(DOM) The declaration-block of this rule set, " doc="(DOM) The declaration-block of this rule set, "
@ -144,5 +161,20 @@ class CSSFontFaceRule(cssrule.CSSRule):
doc="The type of this rule, as defined by a CSSRule " doc="The type of this rule, as defined by a CSSRule "
"type constant.") "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='CSSFontFace is valid if properties '
'`font-family` and `src` are set and all properties are '
'valid.')
# constant but needed: # constant but needed:
wellformed = property(lambda self: True) wellformed = property(lambda self: True)

View File

@ -1,10 +1,8 @@
"""CSSImportRule implements DOM Level 2 CSS CSSImportRule plus the """CSSImportRule implements DOM Level 2 CSS CSSImportRule plus the
``name`` property from http://www.w3.org/TR/css3-cascade/#cascading. ``name`` property from http://www.w3.org/TR/css3-cascade/#cascading."""
"""
__all__ = ['CSSImportRule'] __all__ = ['CSSImportRule']
__docformat__ = 'restructuredtext' __docformat__ = 'restructuredtext'
__version__ = '$Id: cssimportrule.py 1638 2009-01-13 20:39:33Z cthedot $' __version__ = '$Id: cssimportrule.py 1824 2009-08-01 21:00:34Z cthedot $'
import cssrule import cssrule
import cssutils import cssutils
@ -320,7 +318,8 @@ class CSSImportRule(cssrule.CSSRule):
parentHref = self.parentStyleSheet.href parentHref = self.parentStyleSheet.href
if parentHref is None: if parentHref is None:
# use cwd instead # use cwd instead
parentHref = u'file:' + urllib.pathname2url(os.getcwd()) + '/' #parentHref = u'file:' + urllib.pathname2url(os.getcwd()) + '/'
parentHref = cssutils.helper.path2url(os.getcwd()) + '/'
href = urlparse.urljoin(parentHref, self.href) href = urlparse.urljoin(parentHref, self.href)
# all possible exceptions are ignored (styleSheet is None then) # all possible exceptions are ignored (styleSheet is None then)

View File

@ -1,7 +1,7 @@
"""CSSMediaRule implements DOM Level 2 CSS CSSMediaRule.""" """CSSMediaRule implements DOM Level 2 CSS CSSMediaRule."""
__all__ = ['CSSMediaRule'] __all__ = ['CSSMediaRule']
__docformat__ = 'restructuredtext' __docformat__ = 'restructuredtext'
__version__ = '$Id: cssmediarule.py 1743 2009-05-09 20:33:15Z cthedot $' __version__ = '$Id: cssmediarule.py 1820 2009-08-01 20:53:08Z cthedot $'
import cssrule import cssrule
import cssutils import cssutils
@ -34,15 +34,11 @@ class CSSMediaRule(cssrule.CSSRule):
readonly=readonly) readonly=readonly)
self.name = name self.name = name
self.cssRules = cssutils.css.cssrulelist.CSSRuleList() self.cssRules = cssutils.css.cssrulelist.CSSRuleList()
self.cssRules.append = self.insertRule
self.cssRules.extend = self.insertRule
self.cssRules.__delitem__ == self.deleteRule
self._readonly = readonly self._readonly = readonly
def __iter__(self): def __iter__(self):
"""Generator iterating over these rule's cssRules.""" """Generator iterating over these rule's cssRules."""
for rule in self.cssRules: for rule in self._cssRules:
yield rule yield rule
def __repr__(self): def __repr__(self):
@ -53,6 +49,20 @@ class CSSMediaRule(cssrule.CSSRule):
return "<cssutils.css.%s object mediaText=%r at 0x%x>" % ( return "<cssutils.css.%s object mediaText=%r at 0x%x>" % (
self.__class__.__name__, self.media.mediaText, id(self)) self.__class__.__name__, self.media.mediaText, id(self))
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.parentStyleSheet
rule._parentRule = self
self._cssRules = cssRules
cssRules = property(lambda self: self._cssRules, _setCssRules,
"All Rules in this style sheet, a "
":class:`~cssutils.css.CSSRuleList`.")
def _getCssText(self): def _getCssText(self):
"""Return serialized property cssText.""" """Return serialized property cssText."""
return cssutils.ser.do_CSSMediaRule(self) return cssutils.ser.do_CSSMediaRule(self)
@ -204,9 +214,9 @@ class CSSMediaRule(cssrule.CSSRule):
self._media.mediaText = newmedia.mediaText self._media.mediaText = newmedia.mediaText
self.name = name self.name = name
self._setSeq(nameseq) self._setSeq(nameseq)
del self.cssRules[:] del self._cssRules[:]
for r in newcssrules: for r in newcssrules:
self.cssRules.append(r) self._cssRules.append(r)
cssText = property(_getCssText, _setCssText, cssText = property(_getCssText, _setCssText,
doc="(DOM) The parsable textual representation of this rule.") doc="(DOM) The parsable textual representation of this rule.")
@ -245,12 +255,12 @@ class CSSMediaRule(cssrule.CSSRule):
self._checkReadonly() self._checkReadonly()
try: try:
self.cssRules[index]._parentRule = None # detach self._cssRules[index]._parentRule = None # detach
del self.cssRules[index] # remove from @media del self._cssRules[index] # remove from @media
except IndexError: except IndexError:
raise xml.dom.IndexSizeErr( raise xml.dom.IndexSizeErr(
u'CSSMediaRule: %s is not a valid index in the rulelist of length %i' % ( u'CSSMediaRule: %s is not a valid index in the rulelist of length %i' % (
index, self.cssRules.length)) index, self._cssRules.length))
def add(self, rule): def add(self, rule):
"""Add `rule` to end of this mediarule. """Add `rule` to end of this mediarule.
@ -300,11 +310,11 @@ class CSSMediaRule(cssrule.CSSRule):
# check position # check position
if index is None: if index is None:
index = len(self.cssRules) index = len(self._cssRules)
elif index < 0 or index > self.cssRules.length: elif index < 0 or index > self._cssRules.length:
raise xml.dom.IndexSizeErr( raise xml.dom.IndexSizeErr(
u'CSSMediaRule: Invalid index %s for CSSRuleList with a length of %s.' % ( u'CSSMediaRule: Invalid index %s for CSSRuleList with a length of %s.' % (
index, self.cssRules.length)) index, self._cssRules.length))
# parse # parse
if isinstance(rule, basestring): if isinstance(rule, basestring):
@ -315,6 +325,13 @@ class CSSMediaRule(cssrule.CSSRule):
self._log.error(u'CSSMediaRule: Invalid Rule: %s' % rule) self._log.error(u'CSSMediaRule: Invalid Rule: %s' % rule)
return return
rule = tempsheet.cssRules[0] 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 index
elif not isinstance(rule, cssutils.css.CSSRule): elif not isinstance(rule, cssutils.css.CSSRule):
self._log.error(u'CSSMediaRule: Not a CSSRule: %s' % rule) self._log.error(u'CSSMediaRule: Not a CSSRule: %s' % rule)
return return
@ -332,7 +349,7 @@ class CSSMediaRule(cssrule.CSSRule):
error=xml.dom.HierarchyRequestErr) error=xml.dom.HierarchyRequestErr)
return return
self.cssRules.insert(index, rule) self._cssRules.insert(index, rule)
rule._parentRule = self rule._parentRule = self
rule._parentStyleSheet = self.parentStyleSheet rule._parentStyleSheet = self.parentStyleSheet
return index return index

View File

@ -1,8 +1,7 @@
"""CSSPageRule implements DOM Level 2 CSS CSSPageRule. """CSSPageRule implements DOM Level 2 CSS CSSPageRule."""
"""
__all__ = ['CSSPageRule'] __all__ = ['CSSPageRule']
__docformat__ = 'restructuredtext' __docformat__ = 'restructuredtext'
__version__ = '$Id: csspagerule.py 1658 2009-02-07 18:24:40Z cthedot $' __version__ = '$Id: csspagerule.py 1824 2009-08-01 21:00:34Z cthedot $'
from cssstyledeclaration import CSSStyleDeclaration from cssstyledeclaration import CSSStyleDeclaration
from selectorlist import SelectorList from selectorlist import SelectorList
@ -45,11 +44,12 @@ class CSSPageRule(cssrule.CSSRule):
tempseq.append(self.selectorText, 'selectorText') tempseq.append(self.selectorText, 'selectorText')
else: else:
self._selectorText = self._tempSeq() self._selectorText = self._tempSeq()
self._style = CSSStyleDeclaration(parentRule=self)
if style: if style:
self.style = style self.style = style
tempseq.append(self.style, 'style') tempseq.append(self.style, 'style')
else:
self._style = CSSStyleDeclaration(parentRule=self)
self._setSeq(tempseq) self._setSeq(tempseq)
self._readonly = readonly self._readonly = readonly
@ -192,7 +192,7 @@ class CSSPageRule(cssrule.CSSRule):
wellformed, newselectorseq = self.__parseSelectorText(selectortokens) wellformed, newselectorseq = self.__parseSelectorText(selectortokens)
newstyle = CSSStyleDeclaration() teststyle = CSSStyleDeclaration(parentRule=self)
val, typ = self._tokenvalue(braceorEOFtoken), self._type(braceorEOFtoken) val, typ = self._tokenvalue(braceorEOFtoken), self._type(braceorEOFtoken)
if val != u'}' and typ != 'EOF': if val != u'}' and typ != 'EOF':
wellformed = False wellformed = False
@ -203,13 +203,15 @@ class CSSPageRule(cssrule.CSSRule):
if 'EOF' == typ: if 'EOF' == typ:
# add again as style needs it # add again as style needs it
styletokens.append(braceorEOFtoken) styletokens.append(braceorEOFtoken)
newstyle.cssText = styletokens teststyle.cssText = styletokens
if wellformed: if wellformed:
self._selectorText = newselectorseq # already parsed # known as correct from before
self.style = newstyle cssutils.log.enabled = False
self._setSeq(newselectorseq) # contains upto style only self._selectorText = newselectorseq # TODO: TEST and REFS
self.style.cssText = styletokens
cssutils.log.enabled = True
cssText = property(_getCssText, _setCssText, cssText = property(_getCssText, _setCssText,
doc="(DOM) The parsable textual representation of this rule.") doc="(DOM) The parsable textual representation of this rule.")
@ -239,7 +241,7 @@ class CSSPageRule(cssrule.CSSRule):
# may raise SYNTAX_ERR # may raise SYNTAX_ERR
wellformed, newseq = self.__parseSelectorText(selectorText) wellformed, newseq = self.__parseSelectorText(selectorText)
if wellformed and newseq: if wellformed:
self._selectorText = newseq self._selectorText = newseq
selectorText = property(_getSelectorText, _setSelectorText, selectorText = property(_getSelectorText, _setSelectorText,
@ -251,19 +253,16 @@ class CSSPageRule(cssrule.CSSRule):
a CSSStyleDeclaration or string a CSSStyleDeclaration or string
""" """
self._checkReadonly() self._checkReadonly()
if isinstance(style, basestring): if isinstance(style, basestring):
self._style.cssText = style self._style.cssText = style
else: else:
# cssText would be serialized with optional preferences self._style = style
# so use seq! self._style.parentRule = self
self._style._seq = style.seq
style = property(lambda self: self._style, _setStyle, style = property(lambda self: self._style, _setStyle,
doc="(DOM) The declaration-block of this rule set, " doc="(DOM) The declaration-block of this rule set, "
"a :class:`~cssutils.css.CSSStyleDeclaration`.") "a :class:`~cssutils.css.CSSStyleDeclaration`.")
type = property(lambda self: self.PAGE_RULE, type = property(lambda self: self.PAGE_RULE,
doc="The type of this rule, as defined by a CSSRule " doc="The type of this rule, as defined by a CSSRule "
"type constant.") "type constant.")

View File

@ -1,7 +1,7 @@
"""CSSRule implements DOM Level 2 CSS CSSRule.""" """CSSRule implements DOM Level 2 CSS CSSRule."""
__all__ = ['CSSRule'] __all__ = ['CSSRule']
__docformat__ = 'restructuredtext' __docformat__ = 'restructuredtext'
__version__ = '$Id: cssrule.py 1638 2009-01-13 20:39:33Z cthedot $' __version__ = '$Id: cssrule.py 1808 2009-07-29 13:09:36Z cthedot $'
import cssutils import cssutils
import xml.dom import xml.dom
@ -35,6 +35,7 @@ class CSSRule(cssutils.util.Base2):
def __init__(self, parentRule=None, parentStyleSheet=None, readonly=False): def __init__(self, parentRule=None, parentStyleSheet=None, readonly=False):
"""Set common attributes for all rules.""" """Set common attributes for all rules."""
super(CSSRule, self).__init__() super(CSSRule, self).__init__()
self._parent = parentRule
self._parentRule = parentRule self._parentRule = parentRule
self._parentStyleSheet = parentStyleSheet self._parentStyleSheet = parentStyleSheet
self._setSeq(self._tempSeq()) self._setSeq(self._tempSeq())
@ -78,6 +79,10 @@ class CSSRule(cssutils.util.Base2):
"rule. This reflects the current state of the rule " "rule. This reflects the current state of the rule "
"and not its initial value.") "and not its initial value.")
parent = property(lambda self: self._parent,
doc="The Parent Node of this CSSRule (currently if a "
"CSSStyleDeclaration only!) or None.")
parentRule = property(lambda self: self._parentRule, parentRule = property(lambda self: self._parentRule,
doc="If this rule is contained inside " doc="If this rule is contained inside "
"another rule (e.g. a style rule inside " "another rule (e.g. a style rule inside "

View File

@ -1,9 +1,8 @@
"""CSSRuleList implements DOM Level 2 CSS CSSRuleList. """CSSRuleList implements DOM Level 2 CSS CSSRuleList.
Partly also http://dev.w3.org/csswg/cssom/#the-cssrulelist Partly also http://dev.w3.org/csswg/cssom/#the-cssrulelist."""
"""
__all__ = ['CSSRuleList'] __all__ = ['CSSRuleList']
__docformat__ = 'restructuredtext' __docformat__ = 'restructuredtext'
__version__ = '$Id: cssrulelist.py 1641 2009-01-13 21:05:37Z cthedot $' __version__ = '$Id: cssrulelist.py 1824 2009-08-01 21:00:34Z cthedot $'
class CSSRuleList(list): class CSSRuleList(list):
"""The CSSRuleList object represents an (ordered) list of statements. """The CSSRuleList object represents an (ordered) list of statements.

View File

@ -51,7 +51,7 @@ TODO:
""" """
__all__ = ['CSSStyleDeclaration', 'Property'] __all__ = ['CSSStyleDeclaration', 'Property']
__docformat__ = 'restructuredtext' __docformat__ = 'restructuredtext'
__version__ = '$Id: cssstyledeclaration.py 1710 2009-04-18 15:46:20Z cthedot $' __version__ = '$Id: cssstyledeclaration.py 1819 2009-08-01 20:52:43Z cthedot $'
from cssproperties import CSS2Properties from cssproperties import CSS2Properties
from property import Property from property import Property
@ -127,6 +127,11 @@ class CSSStyleDeclaration(CSS2Properties, cssutils.util.Base2):
for name in self.__nnames(): for name in self.__nnames():
yield self.getProperty(name) yield self.getProperty(name)
return properties() 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): def __getitem__(self, CSSName):
"""Retrieve the value of property ``CSSName`` from this declaration. """Retrieve the value of property ``CSSName`` from this declaration.
@ -247,6 +252,13 @@ class CSSStyleDeclaration(CSS2Properties, cssutils.util.Base2):
""" """
self.removeProperty(CSSName) 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): def _getCssText(self):
"""Return serialized property cssText.""" """Return serialized property cssText."""
return cssutils.ser.do_css_CSSStyleDeclaration(self) return cssutils.ser.do_css_CSSStyleDeclaration(self)
@ -275,10 +287,10 @@ class CSSStyleDeclaration(CSS2Properties, cssutils.util.Base2):
semicolon=True) semicolon=True)
if self._tokenvalue(tokens[-1]) == u';': if self._tokenvalue(tokens[-1]) == u';':
tokens.pop() tokens.pop()
property = Property() property = Property(parent=self)
property.cssText = tokens property.cssText = tokens
if property.wellformed: if property.wellformed:
seq.append(property, 'Property') seq.append(property, 'Property')
else: else:
self._log.error(u'CSSStyleDeclaration: Syntax Error in Property: %s' self._log.error(u'CSSStyleDeclaration: Syntax Error in Property: %s'
% self._valuestr(tokens)) % self._valuestr(tokens))
@ -301,12 +313,13 @@ class CSSStyleDeclaration(CSS2Properties, cssutils.util.Base2):
productions={'IDENT': ident},#, 'CHAR': char}, productions={'IDENT': ident},#, 'CHAR': char},
default=unexpected) default=unexpected)
# wellformed set by parse # wellformed set by parse
# post conditions
for item in newseq:
item.value._parent = self
# do not check wellformed as invalid things are removed anyway # do not check wellformed as invalid things are removed anyway
#if wellformed:
self._setSeq(newseq) self._setSeq(newseq)
cssText = property(_getCssText, _setCssText, cssText = property(_getCssText, _setCssText,
doc="(DOM) A parsable textual representation of the declaration\ doc="(DOM) A parsable textual representation of the declaration\
block excluding the surrounding curly braces.") block excluding the surrounding curly braces.")
@ -322,13 +335,12 @@ class CSSStyleDeclaration(CSS2Properties, cssutils.util.Base2):
""" """
return cssutils.ser.do_css_CSSStyleDeclaration(self, separator) return cssutils.ser.do_css_CSSStyleDeclaration(self, separator)
def _getParentRule(self):
return self._parentRule
def _setParentRule(self, parentRule): def _setParentRule(self, parentRule):
self._parentRule = parentRule self._parentRule = parentRule
for x in self.children():
parentRule = property(_getParentRule, _setParentRule, x.parent = self
parentRule = property(lambda self: self._parentRule, _setParentRule,
doc="(DOM) The CSS rule that contains this declaration block or " doc="(DOM) The CSS rule that contains this declaration block or "
"None if this CSSStyleDeclaration is not attached to a CSSRule.") "None if this CSSStyleDeclaration is not attached to a CSSRule.")
@ -581,6 +593,7 @@ class CSSStyleDeclaration(CSS2Properties, cssutils.util.Base2):
property.priority = newp.priority property.priority = newp.priority
break break
else: else:
newp.parent = self
self.seq._readonly = False self.seq._readonly = False
self.seq.append(newp, 'Property') self.seq.append(newp, 'Property')
self.seq._readonly = True self.seq._readonly = True

View File

@ -1,7 +1,7 @@
"""CSSStyleRule implements DOM Level 2 CSS CSSStyleRule.""" """CSSStyleRule implements DOM Level 2 CSS CSSStyleRule."""
__all__ = ['CSSStyleRule'] __all__ = ['CSSStyleRule']
__docformat__ = 'restructuredtext' __docformat__ = 'restructuredtext'
__version__ = '$Id: cssstylerule.py 1638 2009-01-13 20:39:33Z cthedot $' __version__ = '$Id: cssstylerule.py 1815 2009-07-29 16:51:58Z cthedot $'
from cssstyledeclaration import CSSStyleDeclaration from cssstyledeclaration import CSSStyleDeclaration
from selectorlist import SelectorList from selectorlist import SelectorList
@ -107,6 +107,8 @@ class CSSStyleRule(cssrule.CSSRule):
else: else:
wellformed = True wellformed = True
testselectorlist, teststyle = None, None
bracetoken = selectortokens.pop() bracetoken = selectortokens.pop()
if self._tokenvalue(bracetoken) != u'{': if self._tokenvalue(bracetoken) != u'{':
wellformed = False wellformed = False
@ -117,11 +119,11 @@ class CSSStyleRule(cssrule.CSSRule):
wellformed = False wellformed = False
self._log.error(u'CSSStyleRule: No selector found: %r.' % self._log.error(u'CSSStyleRule: No selector found: %r.' %
self._valuestr(cssText), bracetoken) self._valuestr(cssText), bracetoken)
newselectorlist = SelectorList(selectorText=(selectortokens,
testselectorlist = SelectorList(selectorText=(selectortokens,
namespaces), namespaces),
parentRule=self) parentRule=self)
newstyle = CSSStyleDeclaration()
if not styletokens: if not styletokens:
wellformed = False wellformed = False
self._log.error( self._log.error(
@ -139,11 +141,14 @@ class CSSStyleRule(cssrule.CSSRule):
if 'EOF' == typ: if 'EOF' == typ:
# add again as style needs it # add again as style needs it
styletokens.append(braceorEOFtoken) styletokens.append(braceorEOFtoken)
newstyle.cssText = styletokens teststyle = CSSStyleDeclaration(styletokens, parentRule=self)
if wellformed: if wellformed and testselectorlist and teststyle:
self._selectorList = newselectorlist # known as correct from before
self.style = newstyle cssutils.log.enabled = False
self.style.cssText = styletokens
self.selectorList.selectorText=(selectortokens, namespaces)
cssutils.log.enabled = True
cssText = property(_getCssText, _setCssText, cssText = property(_getCssText, _setCssText,
doc="(DOM) The parsable textual representation of this rule.") doc="(DOM) The parsable textual representation of this rule.")
@ -164,11 +169,12 @@ class CSSStyleRule(cssrule.CSSRule):
def _setSelectorList(self, selectorList): def _setSelectorList(self, selectorList):
""" """
:param selectorList: selectorList, only content is used, not the actual :param selectorList: A SelectorList which replaces the current
object selectorList object
""" """
self._checkReadonly() self._checkReadonly()
self.selectorText = selectorList.selectorText selectorList._parentRule = self
self._selectorList = selectorList
selectorList = property(lambda self: self._selectorList, _setSelectorList, selectorList = property(lambda self: self._selectorList, _setSelectorList,
doc="The SelectorList of this rule.") doc="The SelectorList of this rule.")
@ -200,16 +206,15 @@ class CSSStyleRule(cssrule.CSSRule):
def _setStyle(self, style): def _setStyle(self, style):
""" """
:param style: CSSStyleDeclaration or string, only the cssText of a :param style: A string or CSSStyleDeclaration which replaces the
declaration is used, not the actual object current style object.
""" """
self._checkReadonly() self._checkReadonly()
if isinstance(style, basestring): if isinstance(style, basestring):
self._style.cssText = style self._style.cssText = style
else: else:
# cssText would be serialized with optional preferences style._parentRule = self
# so use _seq! self._style = style
self._style._seq = style._seq
style = property(lambda self: self._style, _setStyle, style = property(lambda self: self._style, _setStyle,
doc="(DOM) The declaration-block of this rule set.") doc="(DOM) The declaration-block of this rule set.")

View File

@ -9,7 +9,7 @@ TODO:
""" """
__all__ = ['CSSStyleSheet'] __all__ = ['CSSStyleSheet']
__docformat__ = 'restructuredtext' __docformat__ = 'restructuredtext'
__version__ = '$Id: cssstylesheet.py 1641 2009-01-13 21:05:37Z cthedot $' __version__ = '$Id: cssstylesheet.py 1820 2009-08-01 20:53:08Z cthedot $'
from cssutils.helper import Deprecated from cssutils.helper import Deprecated
from cssutils.util import _Namespaces, _SimpleNamespaces, _readUrl from cssutils.util import _Namespaces, _SimpleNamespaces, _readUrl
@ -42,8 +42,6 @@ class CSSStyleSheet(cssutils.stylesheets.StyleSheet):
self._ownerRule = ownerRule self._ownerRule = ownerRule
self.cssRules = cssutils.css.CSSRuleList() self.cssRules = cssutils.css.CSSRuleList()
self.cssRules.append = self.insertRule
self.cssRules.extend = self.insertRule
self._namespaces = _Namespaces(parentStyleSheet=self, log=self._log) self._namespaces = _Namespaces(parentStyleSheet=self, log=self._log)
self._readonly = readonly self._readonly = readonly
@ -53,7 +51,7 @@ class CSSStyleSheet(cssutils.stylesheets.StyleSheet):
def __iter__(self): def __iter__(self):
"Generator which iterates over cssRules." "Generator which iterates over cssRules."
for rule in self.cssRules: for rule in self._cssRules:
yield rule yield rule
def __repr__(self): def __repr__(self):
@ -78,7 +76,7 @@ class CSSStyleSheet(cssutils.stylesheets.StyleSheet):
def _cleanNamespaces(self): def _cleanNamespaces(self):
"Remove all namespace rules with same namespaceURI but last one set." "Remove all namespace rules with same namespaceURI but last one set."
rules = self.cssRules rules = self._cssRules
namespaceitems = self.namespaces.items() namespaceitems = self.namespaces.items()
i = 0 i = 0
while i < len(rules): while i < len(rules):
@ -101,6 +99,19 @@ class CSSStyleSheet(cssutils.stylesheets.StyleSheet):
useduris.update(r2.selectorList._getUsedUris()) useduris.update(r2.selectorList._getUsedUris())
return useduris 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,
"All Rules in this style sheet, a "
":class:`~cssutils.css.CSSRuleList`.")
def _getCssText(self): def _getCssText(self):
"Textual representation of the stylesheet (a byte string)." "Textual representation of the stylesheet (a byte string)."
return cssutils.ser.do_CSSStyleSheet(self) return cssutils.ser.do_CSSStyleSheet(self)
@ -260,7 +271,7 @@ class CSSStyleSheet(cssutils.stylesheets.StyleSheet):
default=ruleset) default=ruleset)
if wellformed: if wellformed:
del self.cssRules[:] del self._cssRules[:]
for rule in newseq: for rule in newseq:
self.insertRule(rule, _clean=False) self.insertRule(rule, _clean=False)
self._cleanNamespaces() self._cleanNamespaces()
@ -277,7 +288,7 @@ class CSSStyleSheet(cssutils.stylesheets.StyleSheet):
except AttributeError: except AttributeError:
try: try:
# explicit @charset # explicit @charset
selfAsParentEncoding = self.cssRules[0].encoding selfAsParentEncoding = self._cssRules[0].encoding
except (IndexError, AttributeError): except (IndexError, AttributeError):
# default not UTF-8 but None! # default not UTF-8 but None!
selfAsParentEncoding = None selfAsParentEncoding = None
@ -312,6 +323,14 @@ class CSSStyleSheet(cssutils.stylesheets.StyleSheet):
"""Set @import URL loader, if None the default is used.""" """Set @import URL loader, if None the default is used."""
self._fetcher = fetcher 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): def _setEncoding(self, encoding):
"""Set `encoding` of charset rule if present in sheet or insert a new """Set `encoding` of charset rule if present in sheet or insert a new
:class:`~cssutils.css.CSSCharsetRule` with given `encoding`. :class:`~cssutils.css.CSSCharsetRule` with given `encoding`.
@ -319,7 +338,7 @@ class CSSStyleSheet(cssutils.stylesheets.StyleSheet):
default encoding of utf-8. default encoding of utf-8.
""" """
try: try:
rule = self.cssRules[0] rule = self._cssRules[0]
except IndexError: except IndexError:
rule = None rule = None
if rule and rule.CHARSET_RULE == rule.type: if rule and rule.CHARSET_RULE == rule.type:
@ -330,14 +349,6 @@ class CSSStyleSheet(cssutils.stylesheets.StyleSheet):
elif encoding: elif encoding:
self.insertRule(cssutils.css.CSSCharsetRule(encoding=encoding), 0) self.insertRule(cssutils.css.CSSCharsetRule(encoding=encoding), 0)
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'
encoding = property(_getEncoding, _setEncoding, encoding = property(_getEncoding, _setEncoding,
"(cssutils) Reflect encoding of an @charset rule or 'utf-8' " "(cssutils) Reflect encoding of an @charset rule or 'utf-8' "
"(default) if set to ``None``") "(default) if set to ``None``")
@ -371,11 +382,11 @@ class CSSStyleSheet(cssutils.stylesheets.StyleSheet):
self._checkReadonly() self._checkReadonly()
try: try:
rule = self.cssRules[index] rule = self._cssRules[index]
except IndexError: except IndexError:
raise xml.dom.IndexSizeErr( raise xml.dom.IndexSizeErr(
u'CSSStyleSheet: %s is not a valid index in the rulelist of length %i' % ( u'CSSStyleSheet: %s is not a valid index in the rulelist of length %i' % (
index, self.cssRules.length)) index, self._cssRules.length))
else: else:
if rule.type == rule.NAMESPACE_RULE: if rule.type == rule.NAMESPACE_RULE:
# check all namespacerules if used # check all namespacerules if used
@ -388,7 +399,7 @@ class CSSStyleSheet(cssutils.stylesheets.StyleSheet):
return return
rule._parentStyleSheet = None # detach rule._parentStyleSheet = None # detach
del self.cssRules[index] # delete from StyleSheet del self._cssRules[index] # delete from StyleSheet
def insertRule(self, rule, index=None, inOrder=False, _clean=True): def insertRule(self, rule, index=None, inOrder=False, _clean=True):
""" """
@ -427,11 +438,11 @@ class CSSStyleSheet(cssutils.stylesheets.StyleSheet):
# check position # check position
if index is None: if index is None:
index = len(self.cssRules) index = len(self._cssRules)
elif index < 0 or index > self.cssRules.length: elif index < 0 or index > self._cssRules.length:
raise xml.dom.IndexSizeErr( raise xml.dom.IndexSizeErr(
u'CSSStyleSheet: Invalid index %s for CSSRuleList with a length of %s.' % ( u'CSSStyleSheet: Invalid index %s for CSSRuleList with a length of %s.' % (
index, self.cssRules.length)) index, self._cssRules.length))
return return
if isinstance(rule, basestring): if isinstance(rule, basestring):
@ -447,11 +458,11 @@ class CSSStyleSheet(cssutils.stylesheets.StyleSheet):
# prepend encoding if in this sheet to be able to use it in # prepend encoding if in this sheet to be able to use it in
# @import rules encoding resolution # @import rules encoding resolution
# do not add if new rule startswith "@charset" (which is exact!) # do not add if new rule startswith "@charset" (which is exact!)
if not rule.startswith(u'@charset') and (self.cssRules and if not rule.startswith(u'@charset') and (self._cssRules and
self.cssRules[0].type == self.cssRules[0].CHARSET_RULE): self._cssRules[0].type == self._cssRules[0].CHARSET_RULE):
# rule 0 is @charset! # rule 0 is @charset!
newrulescount, newruleindex = 2, 1 newrulescount, newruleindex = 2, 1
rule = self.cssRules[0].cssText + rule rule = self._cssRules[0].cssText + rule
else: else:
newrulescount, newruleindex = 1, 0 newrulescount, newruleindex = 1, 0
@ -484,29 +495,29 @@ class CSSStyleSheet(cssutils.stylesheets.StyleSheet):
if inOrder: if inOrder:
index = 0 index = 0
# always first and only # always first and only
if (self.cssRules and self.cssRules[0].type == rule.CHARSET_RULE): if (self._cssRules and self._cssRules[0].type == rule.CHARSET_RULE):
self.cssRules[0].encoding = rule.encoding self._cssRules[0].encoding = rule.encoding
else: else:
self.cssRules.insert(0, rule) self._cssRules.insert(0, rule)
elif index != 0 or (self.cssRules and elif index != 0 or (self._cssRules and
self.cssRules[0].type == rule.CHARSET_RULE): self._cssRules[0].type == rule.CHARSET_RULE):
self._log.error( self._log.error(
u'CSSStylesheet: @charset only allowed once at the beginning of a stylesheet.', u'CSSStylesheet: @charset only allowed once at the beginning of a stylesheet.',
error=xml.dom.HierarchyRequestErr) error=xml.dom.HierarchyRequestErr)
return return
else: else:
self.cssRules.insert(index, rule) self._cssRules.insert(index, rule)
# @unknown or comment # @unknown or comment
elif rule.type in (rule.UNKNOWN_RULE, rule.COMMENT) and not inOrder: elif rule.type in (rule.UNKNOWN_RULE, rule.COMMENT) and not inOrder:
if index == 0 and self.cssRules and\ if index == 0 and self._cssRules and\
self.cssRules[0].type == rule.CHARSET_RULE: self._cssRules[0].type == rule.CHARSET_RULE:
self._log.error( self._log.error(
u'CSSStylesheet: @charset must be the first rule.', u'CSSStylesheet: @charset must be the first rule.',
error=xml.dom.HierarchyRequestErr) error=xml.dom.HierarchyRequestErr)
return return
else: else:
self.cssRules.insert(index, rule) self._cssRules.insert(index, rule)
# @import # @import
elif rule.type == rule.IMPORT_RULE: elif rule.type == rule.IMPORT_RULE:
@ -514,27 +525,27 @@ class CSSStyleSheet(cssutils.stylesheets.StyleSheet):
# automatic order # automatic order
if rule.type in (r.type for r in self): if rule.type in (r.type for r in self):
# find last of this type # find last of this type
for i, r in enumerate(reversed(self.cssRules)): for i, r in enumerate(reversed(self._cssRules)):
if r.type == rule.type: if r.type == rule.type:
index = len(self.cssRules) - i index = len(self._cssRules) - i
break break
else: else:
# find first point to insert # find first point to insert
if self.cssRules and self.cssRules[0].type in (rule.CHARSET_RULE, if self._cssRules and self._cssRules[0].type in (rule.CHARSET_RULE,
rule.COMMENT): rule.COMMENT):
index = 1 index = 1
else: else:
index = 0 index = 0
else: else:
# after @charset # after @charset
if index == 0 and self.cssRules and\ if index == 0 and self._cssRules and\
self.cssRules[0].type == rule.CHARSET_RULE: self._cssRules[0].type == rule.CHARSET_RULE:
self._log.error( self._log.error(
u'CSSStylesheet: Found @charset at index 0.', u'CSSStylesheet: Found @charset at index 0.',
error=xml.dom.HierarchyRequestErr) error=xml.dom.HierarchyRequestErr)
return return
# before @namespace, @page, @font-face, @media and stylerule # before @namespace, @page, @font-face, @media and stylerule
for r in self.cssRules[:index]: for r in self._cssRules[:index]:
if r.type in (r.NAMESPACE_RULE, r.MEDIA_RULE, r.PAGE_RULE, if r.type in (r.NAMESPACE_RULE, r.MEDIA_RULE, r.PAGE_RULE,
r.STYLE_RULE, r.FONT_FACE_RULE): r.STYLE_RULE, r.FONT_FACE_RULE):
self._log.error( self._log.error(
@ -542,27 +553,27 @@ class CSSStyleSheet(cssutils.stylesheets.StyleSheet):
index, index,
error=xml.dom.HierarchyRequestErr) error=xml.dom.HierarchyRequestErr)
return return
self.cssRules.insert(index, rule) self._cssRules.insert(index, rule)
# @namespace # @namespace
elif rule.type == rule.NAMESPACE_RULE: elif rule.type == rule.NAMESPACE_RULE:
if inOrder: if inOrder:
if rule.type in (r.type for r in self): if rule.type in (r.type for r in self):
# find last of this type # find last of this type
for i, r in enumerate(reversed(self.cssRules)): for i, r in enumerate(reversed(self._cssRules)):
if r.type == rule.type: if r.type == rule.type:
index = len(self.cssRules) - i index = len(self._cssRules) - i
break break
else: else:
# find first point to insert # find first point to insert
for i, r in enumerate(self.cssRules): for i, r in enumerate(self._cssRules):
if r.type in (r.MEDIA_RULE, r.PAGE_RULE, r.STYLE_RULE, if r.type in (r.MEDIA_RULE, r.PAGE_RULE, r.STYLE_RULE,
r.FONT_FACE_RULE, r.UNKNOWN_RULE, r.COMMENT): r.FONT_FACE_RULE, r.UNKNOWN_RULE, r.COMMENT):
index = i # before these index = i # before these
break break
else: else:
# after @charset and @import # after @charset and @import
for r in self.cssRules[index:]: for r in self._cssRules[index:]:
if r.type in (r.CHARSET_RULE, r.IMPORT_RULE): if r.type in (r.CHARSET_RULE, r.IMPORT_RULE):
self._log.error( self._log.error(
u'CSSStylesheet: Cannot insert @namespace here, found @charset or @import after index %s.' % u'CSSStylesheet: Cannot insert @namespace here, found @charset or @import after index %s.' %
@ -570,7 +581,7 @@ class CSSStyleSheet(cssutils.stylesheets.StyleSheet):
error=xml.dom.HierarchyRequestErr) error=xml.dom.HierarchyRequestErr)
return return
# before @media and stylerule # before @media and stylerule
for r in self.cssRules[:index]: for r in self._cssRules[:index]:
if r.type in (r.MEDIA_RULE, r.PAGE_RULE, r.STYLE_RULE, if r.type in (r.MEDIA_RULE, r.PAGE_RULE, r.STYLE_RULE,
r.FONT_FACE_RULE): r.FONT_FACE_RULE):
self._log.error( self._log.error(
@ -582,7 +593,7 @@ class CSSStyleSheet(cssutils.stylesheets.StyleSheet):
if not (rule.prefix in self.namespaces and if not (rule.prefix in self.namespaces and
self.namespaces[rule.prefix] == rule.namespaceURI): self.namespaces[rule.prefix] == rule.namespaceURI):
# no doublettes # no doublettes
self.cssRules.insert(index, rule) self._cssRules.insert(index, rule)
if _clean: if _clean:
self._cleanNamespaces() self._cleanNamespaces()
@ -590,17 +601,17 @@ class CSSStyleSheet(cssutils.stylesheets.StyleSheet):
else: else:
if inOrder: if inOrder:
# simply add to end as no specific order # simply add to end as no specific order
self.cssRules.append(rule) self._cssRules.append(rule)
index = len(self.cssRules) - 1 index = len(self._cssRules) - 1
else: else:
for r in self.cssRules[index:]: for r in self._cssRules[index:]:
if r.type in (r.CHARSET_RULE, r.IMPORT_RULE, r.NAMESPACE_RULE): if r.type in (r.CHARSET_RULE, r.IMPORT_RULE, r.NAMESPACE_RULE):
self._log.error( self._log.error(
u'CSSStylesheet: Cannot insert rule here, found @charset, @import or @namespace before index %s.' % u'CSSStylesheet: Cannot insert rule here, found @charset, @import or @namespace before index %s.' %
index, index,
error=xml.dom.HierarchyRequestErr) error=xml.dom.HierarchyRequestErr)
return return
self.cssRules.insert(index, rule) self._cssRules.insert(index, rule)
# post settings, TODO: for other rules which contain @rules # post settings, TODO: for other rules which contain @rules
rule._parentStyleSheet = self rule._parentStyleSheet = self

View File

@ -7,7 +7,7 @@
""" """
__all__ = ['CSSValue', 'CSSPrimitiveValue', 'CSSValueList', 'RGBColor'] __all__ = ['CSSValue', 'CSSPrimitiveValue', 'CSSValueList', 'RGBColor']
__docformat__ = 'restructuredtext' __docformat__ = 'restructuredtext'
__version__ = '$Id: cssvalue.py 1684 2009-03-01 18:26:21Z cthedot $' __version__ = '$Id: cssvalue.py 1834 2009-08-02 12:20:21Z cthedot $'
from cssutils.prodparser import * from cssutils.prodparser import *
import cssutils import cssutils
@ -81,6 +81,7 @@ class CSSValue(cssutils.util._NewBase):
[ NUMBER S* | PERCENTAGE S* | LENGTH S* | EMS S* | EXS S* | ANGLE S* | [ NUMBER S* | PERCENTAGE S* | LENGTH S* | EMS S* | EXS S* | ANGLE S* |
TIME S* | FREQ S* ] TIME S* | FREQ S* ]
| STRING S* | IDENT S* | URI S* | hexcolor | function | STRING S* | IDENT S* | URI S* | hexcolor | function
| UNICODE-RANGE S*
; ;
function function
: FUNCTION S* expr ')' S* : FUNCTION S* expr ')' S*
@ -108,7 +109,7 @@ class CSSValue(cssutils.util._NewBase):
# used as operator is , / or S # used as operator is , / or S
nextSor = u',/' nextSor = u',/'
term = Choice(Sequence(PreDef.unary(), term = Choice(Sequence(PreDef.unary(),
Choice(PreDef.number(nextSor=nextSor), Choice(PreDef.number(nextSor=nextSor),
PreDef.percentage(nextSor=nextSor), PreDef.percentage(nextSor=nextSor),
@ -117,22 +118,25 @@ class CSSValue(cssutils.util._NewBase):
PreDef.ident(nextSor=nextSor), PreDef.ident(nextSor=nextSor),
PreDef.uri(nextSor=nextSor), PreDef.uri(nextSor=nextSor),
PreDef.hexcolor(nextSor=nextSor), PreDef.hexcolor(nextSor=nextSor),
PreDef.unicode_range(nextSor=nextSor),
# special case IE only expression # special case IE only expression
Prod(name='expression', Prod(name='expression',
match=lambda t, v: t == self._prods.FUNCTION and match=lambda t, v: t == self._prods.FUNCTION and (
cssutils.helper.normalize(v) in (u'expression(', cssutils.helper.normalize(v) in (u'expression(',
u'alpha('), u'alpha(') or
v.startswith(u'progid:DXImageTransform.Microsoft.') ),
nextSor=nextSor, nextSor=nextSor,
toSeq=lambda t, tokens: (ExpressionValue.name, toSeq=lambda t, tokens: (ExpressionValue.name,
ExpressionValue(cssutils.helper.pushtoken(t, ExpressionValue(cssutils.helper.pushtoken(t,
tokens)))), tokens)))
),
PreDef.function(nextSor=nextSor, PreDef.function(nextSor=nextSor,
toSeq=lambda t, tokens: ('FUNCTION', toSeq=lambda t, tokens: ('FUNCTION',
CSSFunction(cssutils.helper.pushtoken(t, CSSFunction(cssutils.helper.pushtoken(t,
tokens))))) tokens)))))
operator = Choice(PreDef.S(optional=False, mayEnd=True), operator = Choice(PreDef.S(),
PreDef.CHAR('comma', ',', toSeq=lambda t, tokens: ('operator', t[1])), PreDef.char('comma', ',', toSeq=lambda t, tokens: ('operator', t[1])),
PreDef.CHAR('slash', '/', toSeq=lambda t, tokens: ('operator', t[1])), PreDef.char('slash', '/', toSeq=lambda t, tokens: ('operator', t[1])),
optional=True) optional=True)
# CSSValue PRODUCTIONS # CSSValue PRODUCTIONS
valueprods = Sequence(term, valueprods = Sequence(term,
@ -154,19 +158,22 @@ class CSSValue(cssutils.util._NewBase):
item = seq[i] item = seq[i]
if item.type == self._prods.S: if item.type == self._prods.S:
pass pass
elif item.value == u',' and item.type not in (self._prods.URI, self._prods.STRING):
# counts as a single one elif (item.value, item.type) == (u',', 'operator'):
# , separared counts as a single STRING for now
# URI or STRING value might be a single CHAR too!
newseq.appendItem(item) newseq.appendItem(item)
if firstvalue:
# may be IDENT or STRING but with , it is always STRING
firstvalue = firstvalue[0], 'STRING'
# each comma separated list counts as a single one only
count -= 1 count -= 1
if firstvalue:
# list of IDENTs is handled as STRING!
if firstvalue[1] == self._prods.IDENT:
firstvalue = firstvalue[0], 'STRING'
elif item.value == u'/': elif item.value == u'/':
# counts as a single one # / separated items count as one
newseq.appendItem(item) newseq.appendItem(item)
elif item.value == u'+' or item.value == u'-': elif item.value == u'-' or item.value == u'+':
# combine +- and following number or other # combine +- and following number or other
i += 1 i += 1
try: try:
@ -187,12 +194,12 @@ class CSSValue(cssutils.util._NewBase):
if not firstvalue: if not firstvalue:
firstvalue = (item.value, item.type) firstvalue = (item.value, item.type)
count += 1 count += 1
else: else:
newseq.appendItem(item) newseq.appendItem(item)
i += 1 i += 1
if not firstvalue: if not firstvalue:
self._log.error( self._log.error(
u'CSSValue: Unknown syntax or no value: %r.' % u'CSSValue: Unknown syntax or no value: %r.' %
@ -201,7 +208,7 @@ class CSSValue(cssutils.util._NewBase):
# ok and set # ok and set
self._setSeq(newseq) self._setSeq(newseq)
self.wellformed = wellformed self.wellformed = wellformed
if hasattr(self, '_value'): if hasattr(self, '_value'):
# only in case of CSSPrimitiveValue, else remove! # only in case of CSSPrimitiveValue, else remove!
del self._value del self._value
@ -228,6 +235,8 @@ class CSSValue(cssutils.util._NewBase):
return cssutils.helper.string(item.value) return cssutils.helper.string(item.value)
elif self._prods.URI == item.type: elif self._prods.URI == item.type:
return cssutils.helper.uri(item.value) return cssutils.helper.uri(item.value)
elif self._prods.FUNCTION == item.type:
return item.value.cssText
else: else:
return item.value return item.value
@ -253,7 +262,8 @@ class CSSValue(cssutils.util._NewBase):
self._prods.NUMBER, self._prods.NUMBER,
self._prods.PERCENTAGE, self._prods.PERCENTAGE,
self._prods.STRING, self._prods.STRING,
self._prods.URI): self._prods.URI,
self._prods.UNICODE_RANGE):
if nexttocommalist: if nexttocommalist:
# wait until complete # wait until complete
commalist.append(itemValue(item)) commalist.append(itemValue(item))
@ -353,6 +363,7 @@ class CSSPrimitiveValue(CSSValue):
CSS_RGBCOLOR = 25 CSS_RGBCOLOR = 25
# NOT OFFICIAL: # NOT OFFICIAL:
CSS_RGBACOLOR = 26 CSS_RGBACOLOR = 26
CSS_UNICODE_RANGE = 27
_floattypes = (CSS_NUMBER, CSS_PERCENTAGE, CSS_EMS, CSS_EXS, _floattypes = (CSS_NUMBER, CSS_PERCENTAGE, CSS_EMS, CSS_EXS,
CSS_PX, CSS_CM, CSS_MM, CSS_IN, CSS_PT, CSS_PC, CSS_PX, CSS_CM, CSS_MM, CSS_IN, CSS_PT, CSS_PC,
@ -426,9 +437,10 @@ class CSSPrimitiveValue(CSSValue):
'CSS_MS', 'CSS_S', 'CSS_MS', 'CSS_S',
'CSS_HZ', 'CSS_KHZ', 'CSS_HZ', 'CSS_KHZ',
'CSS_DIMENSION', 'CSS_DIMENSION',
'CSS_STRING', 'CSS_URI', 'CSS_IDENT', 'CSS_STRING', 'CSS_URI', 'CSS_IDENT',
'CSS_ATTR', 'CSS_COUNTER', 'CSS_RECT', 'CSS_ATTR', 'CSS_COUNTER', 'CSS_RECT',
'CSS_RGBCOLOR', 'CSS_RGBACOLOR', 'CSS_RGBCOLOR', 'CSS_RGBACOLOR',
'CSS_UNICODE_RANGE'
] ]
_reNumDim = re.compile(ur'([+-]?\d*\.\d+|[+-]?\d+)(.*)$', re.I| re.U|re.X) _reNumDim = re.compile(ur'([+-]?\d*\.\d+|[+-]?\d+)(.*)$', re.I| re.U|re.X)
@ -464,6 +476,7 @@ class CSSPrimitiveValue(CSSValue):
__types.NUMBER: 'CSS_NUMBER', __types.NUMBER: 'CSS_NUMBER',
__types.PERCENTAGE: 'CSS_PERCENTAGE', __types.PERCENTAGE: 'CSS_PERCENTAGE',
__types.STRING: 'CSS_STRING', __types.STRING: 'CSS_STRING',
__types.UNICODE_RANGE: 'CSS_UNICODE_RANGE',
__types.URI: 'CSS_URI', __types.URI: 'CSS_URI',
__types.IDENT: 'CSS_IDENT', __types.IDENT: 'CSS_IDENT',
__types.HASH: 'CSS_RGBCOLOR', __types.HASH: 'CSS_RGBCOLOR',
@ -474,7 +487,6 @@ class CSSPrimitiveValue(CSSValue):
def __set_primitiveType(self): def __set_primitiveType(self):
"""primitiveType is readonly but is set lazy if accessed""" """primitiveType is readonly but is set lazy if accessed"""
# TODO: check unary and font-family STRING a, b, "c" # TODO: check unary and font-family STRING a, b, "c"
val, type_ = self._value val, type_ = self._value
# try get by type_ # try get by type_
pt = self.__unitbytype.get(type_, 'CSS_UNKNOWN') pt = self.__unitbytype.get(type_, 'CSS_UNKNOWN')
@ -827,7 +839,7 @@ class CSSFunction(CSSPrimitiveValue):
types.PERCENTAGE, types.PERCENTAGE,
types.STRING), types.STRING),
toSeq=lambda t, tokens: (t[0], CSSPrimitiveValue(t[1]))) toSeq=lambda t, tokens: (t[0], CSSPrimitiveValue(t[1])))
funcProds = Sequence(Prod(name='FUNC', funcProds = Sequence(Prod(name='FUNC',
match=lambda t, v: t == types.FUNCTION, match=lambda t, v: t == types.FUNCTION,
toSeq=lambda t, tokens: (t[0], cssutils.helper.normalize(t[1]))), toSeq=lambda t, tokens: (t[0], cssutils.helper.normalize(t[1]))),
@ -969,23 +981,31 @@ class RGBColor(CSSPrimitiveValue):
class ExpressionValue(CSSFunction): class ExpressionValue(CSSFunction):
"""Special IE only CSSFunction which may contain *anything*. """Special IE only CSSFunction which may contain *anything*.
Used for expressions and ``alpha(opacity=100)`` currently""" Used for expressions and ``alpha(opacity=100)`` currently."""
name = u'Expression (IE only)' name = u'Expression (IE only)'
def _productiondefinition(self): def _productiondefinition(self):
"""Return defintion used for parsing.""" """Return defintion used for parsing."""
types = self._prods # rename! types = self._prods # rename!
def toSeq(t, tokens):
"Do not normalize function name!"
return t[0], t[1]
funcProds = Sequence(Prod(name='expression', funcProds = Sequence(Prod(name='expression',
match=lambda t, v: t == types.FUNCTION, match=lambda t, v: t == types.FUNCTION,
toSeq=lambda t, tokens: (t[0], cssutils.helper.normalize(t[1]))), toSeq=toSeq
),
Sequence(Choice(Prod(name='nested function', Sequence(Choice(Prod(name='nested function',
match=lambda t, v: t == self._prods.FUNCTION, match=lambda t, v: t == self._prods.FUNCTION,
toSeq=lambda t, tokens: (CSSFunction.name, toSeq=lambda t, tokens: (CSSFunction.name,
CSSFunction(cssutils.helper.pushtoken(t, CSSFunction(cssutils.helper.pushtoken(t,
tokens)))), tokens)))
),
Prod(name='part', Prod(name='part',
match=lambda t, v: v != u')', match=lambda t, v: v != u')',
toSeq=lambda t, tokens: (t[0], t[1])), ), toSeq=lambda t, tokens: (t[0], t[1])),
),
minmax=lambda: (0, None)), minmax=lambda: (0, None)),
PreDef.funcEnd(stop=True)) PreDef.funcEnd(stop=True))
return funcProds return funcProds

View File

@ -1,7 +1,7 @@
"""Property is a single CSS property in a CSSStyleDeclaration.""" """Property is a single CSS property in a CSSStyleDeclaration."""
__all__ = ['Property'] __all__ = ['Property']
__docformat__ = 'restructuredtext' __docformat__ = 'restructuredtext'
__version__ = '$Id: property.py 1685 2009-03-01 18:26:48Z cthedot $' __version__ = '$Id: property.py 1811 2009-07-29 13:11:15Z cthedot $'
from cssutils.helper import Deprecated from cssutils.helper import Deprecated
from cssvalue import CSSValue from cssvalue import CSSValue
@ -44,7 +44,7 @@ class Property(cssutils.util.Base):
""" """
def __init__(self, name=None, value=None, priority=u'', def __init__(self, name=None, value=None, priority=u'',
_mediaQuery=False, _parent=None): _mediaQuery=False, parent=None, parentStyle=None):
""" """
:param name: :param name:
a property name string (will be normalized) a property name string (will be normalized)
@ -55,16 +55,17 @@ class Property(cssutils.util.Base):
u'!important' or u'important' u'!important' or u'important'
:param _mediaQuery: :param _mediaQuery:
if ``True`` value is optional (used by MediaQuery) if ``True`` value is optional (used by MediaQuery)
:param _parent: :param parent:
the parent object, normally a the parent object, normally a
:class:`cssutils.css.CSSStyleDeclaration` :class:`cssutils.css.CSSStyleDeclaration`
:param parentStyle:
DEPRECATED: Use ``parent`` instead
""" """
super(Property, self).__init__() super(Property, self).__init__()
self.seqs = [[], None, []] self.seqs = [[], None, []]
self.wellformed = False self.wellformed = False
self._mediaQuery = _mediaQuery self._mediaQuery = _mediaQuery
self._parent = _parent self.parent = parent
self.__nametoken = None self.__nametoken = None
self._name = u'' self._name = u''
@ -363,13 +364,18 @@ class Property(cssutils.util.Base):
literalpriority = property(lambda self: self._literalpriority, literalpriority = property(lambda self: self._literalpriority,
doc="Readonly literal (not normalized) priority of this property") doc="Readonly literal (not normalized) priority of this property")
def validate(self, profiles=None): def _setParent(self, parent):
"""Validate value against `profiles`. self._parent = parent
parent = property(lambda self: self._parent, _setParent,
doc="The Parent Node (normally a CSSStyledeclaration) of this "
"Property")
def validate(self):
"""Validate value against `profiles` which are checked dynamically.
properties in e.g. @font-face rules are checked against
``cssutils.profile.CSS3_FONT_FACE`` only.
:param profiles:
A list of profile names used for validating. If no `profiles`
is given ``cssutils.profile.defaultProfiles`` is used
For each of the following cases a message is reported: For each of the following cases a message is reported:
- INVALID (so the property is known but not valid) - INVALID (so the property is known but not valid)
@ -424,6 +430,16 @@ class Property(cssutils.util.Base):
""" """
valid = False valid = False
profiles = None
try:
# if @font-face use that profile
rule = self.parent.parentRule
if rule.type == rule.FONT_FACE_RULE:
profiles = [cssutils.profile.CSS3_FONT_FACE]
#TODO: same for @page
except AttributeError:
pass
if self.name and self.value: if self.name and self.value:
if self.name in cssutils.profile.knownNames: if self.name in cssutils.profile.knownNames:
@ -467,3 +483,12 @@ class Property(cssutils.util.Base):
valid = property(validate, doc="Check if value of this property is valid " valid = property(validate, doc="Check if value of this property is valid "
"in the properties context.") "in the properties context.")
@Deprecated('Use ``parent`` attribute instead.')
def _getParentStyle(self):
return self._parent
parentStyle = property(_getParentStyle, _setParent,
doc="DEPRECATED: Use ``parent`` instead")

View File

@ -1,62 +0,0 @@
"""productions for CSS 3
CSS3_MACROS and CSS3_PRODUCTIONS are from http://www.w3.org/TR/css3-syntax
"""
__all__ = ['CSSProductions', 'MACROS', 'PRODUCTIONS']
__docformat__ = 'restructuredtext'
__version__ = '$Id: css3productions.py 1116 2008-03-05 13:52:23Z cthedot $'
# a complete list of css3 macros
MACROS = {
'ident': r'[-]?{nmstart}{nmchar}*',
'name': r'{nmchar}+',
'nmstart': r'[_a-zA-Z]|{nonascii}|{escape}',
'nonascii': r'[^\0-\177]',
'unicode': r'\\[0-9a-f]{1,6}{wc}?',
'escape': r'{unicode}|\\[ -~\200-\777]',
# 'escape': r'{unicode}|\\[ -~\200-\4177777]',
'nmchar': r'[-_a-zA-Z0-9]|{nonascii}|{escape}',
# CHANGED TO SPEC: added "-?"
'num': r'-?[0-9]*\.[0-9]+|[0-9]+', #r'[-]?\d+|[-]?\d*\.\d+',
'string': r'''\'({stringchar}|\")*\'|\"({stringchar}|\')*\"''',
'stringchar': r'{urlchar}| |\\{nl}',
'urlchar': r'[\x09\x21\x23-\x26\x27-\x7E]|{nonascii}|{escape}',
# what if \r\n, \n matches first?
'nl': r'\n|\r\n|\r|\f',
'w': r'{wc}*',
'wc': r'\t|\r|\n|\f|\x20'
}
# The following productions are the complete list of tokens in CSS3, the productions are **ordered**:
PRODUCTIONS = [
('BOM', r'\xFEFF'),
('URI', r'url\({w}({string}|{urlchar}*){w}\)'),
('FUNCTION', r'{ident}\('),
('ATKEYWORD', r'\@{ident}'),
('IDENT', r'{ident}'),
('STRING', r'{string}'),
('HASH', r'\#{name}'),
('PERCENTAGE', r'{num}\%'),
('DIMENSION', r'{num}{ident}'),
('NUMBER', r'{num}'),
#???
('UNICODE-RANGE', ur'[0-9A-F?]{1,6}(\-[0-9A-F]{1,6})?'),
('CDO', r'\<\!\-\-'),
('CDC', r'\-\-\>'),
('S', r'{wc}+'),
('INCLUDES', '\~\='),
('DASHMATCH', r'\|\='),
('PREFIXMATCH', r'\^\='),
('SUFFIXMATCH', r'\$\='),
('SUBSTRINGMATCH', r'\*\='),
('COMMENT', r'\/\*[^*]*\*+([^/][^*]*\*+)*\/'),
('CHAR', r'[^"\']'),
]
class CSSProductions(object):
"has attributes for all PRODUCTIONS"
pass
for i, t in enumerate(PRODUCTIONS):
setattr(CSSProductions, t[0].replace('-', '_'), t[0])

View File

@ -12,12 +12,12 @@ open issues
""" """
__all__ = ['CSSProductions', 'MACROS', 'PRODUCTIONS'] __all__ = ['CSSProductions', 'MACROS', 'PRODUCTIONS']
__docformat__ = 'restructuredtext' __docformat__ = 'restructuredtext'
__version__ = '$Id: cssproductions.py 1738 2009-05-02 13:03:28Z cthedot $' __version__ = '$Id: cssproductions.py 1835 2009-08-02 16:47:27Z cthedot $'
# a complete list of css3 macros # a complete list of css3 macros
MACROS = { MACROS = {
'nonascii': r'[^\0-\177]', 'nonascii': r'[^\0-\177]',
'unicode': r'\\[0-9a-f]{1,6}(?:{nl}|{s})?', 'unicode': r'\\[0-9A-Fa-f]{1,6}(?:{nl}|{s})?',
#'escape': r'{unicode}|\\[ -~\200-\777]', #'escape': r'{unicode}|\\[ -~\200-\777]',
'escape': r'{unicode}|\\[^\n\r\f0-9a-f]', 'escape': r'{unicode}|\\[^\n\r\f0-9a-f]',
'nmstart': r'[_a-zA-Z]|{nonascii}|{escape}', 'nmstart': r'[_a-zA-Z]|{nonascii}|{escape}',
@ -71,6 +71,7 @@ PRODUCTIONS = [
('S', r'{s}+'), # 1st in list of general productions ('S', r'{s}+'), # 1st in list of general productions
('URI', r'{U}{R}{L}\({w}({string}|{url}*){w}\)'), ('URI', r'{U}{R}{L}\({w}({string}|{url}*){w}\)'),
('FUNCTION', r'{ident}\('), ('FUNCTION', r'{ident}\('),
('UNICODE-RANGE', r'{U}\+[0-9A-Fa-f?]{1,6}(\-[0-9A-Fa-f]{1,6})?'),
('IDENT', r'{ident}'), ('IDENT', r'{ident}'),
('STRING', r'{string}'), ('STRING', r'{string}'),
('INVALID', r'{invalid}'), # from CSS2.1 ('INVALID', r'{invalid}'), # from CSS2.1
@ -80,8 +81,9 @@ PRODUCTIONS = [
('NUMBER', r'{num}'), ('NUMBER', r'{num}'),
# valid ony at start so not checked everytime # valid ony at start so not checked everytime
#('CHARSET_SYM', r'@charset '), # from Errata includes ending space! #('CHARSET_SYM', r'@charset '), # from Errata includes ending space!
# checked specially if fullsheet is parsed
('COMMENT', r'{comment}'), #r'\/\*[^*]*\*+([^/][^*]*\*+)*\/'),
('ATKEYWORD', r'@{ident}'), # other keywords are done in the tokenizer ('ATKEYWORD', r'@{ident}'), # other keywords are done in the tokenizer
#('UNICODE-RANGE', r'[0-9A-F?]{1,6}(\-[0-9A-F]{1,6})?'), #???
('CDO', r'\<\!\-\-'), ('CDO', r'\<\!\-\-'),
('CDC', r'\-\-\>'), ('CDC', r'\-\-\>'),
('INCLUDES', '\~\='), ('INCLUDES', '\~\='),
@ -89,8 +91,6 @@ PRODUCTIONS = [
('PREFIXMATCH', r'\^\='), ('PREFIXMATCH', r'\^\='),
('SUFFIXMATCH', r'\$\='), ('SUFFIXMATCH', r'\$\='),
('SUBSTRINGMATCH', r'\*\='), ('SUBSTRINGMATCH', r'\*\='),
# checked specially if fullsheet is parsed
('COMMENT', r'{comment}'), #r'\/\*[^*]*\*+([^/][^*]*\*+)*\/'),
('CHAR', r'[^"\']') # MUST always be last ('CHAR', r'[^"\']') # MUST always be last
] ]
@ -110,3 +110,9 @@ class CSSProductions(object):
for i, t in enumerate(PRODUCTIONS): for i, t in enumerate(PRODUCTIONS):
setattr(CSSProductions, t[0].replace('-', '_'), t[0]) setattr(CSSProductions, t[0].replace('-', '_'), t[0])
# may be enabled by settings.set
_DXImageTransform = ('FUNCTION',
r'progid\:DXImageTransform\.Microsoft\..+\('
)

View File

@ -16,7 +16,7 @@ log
""" """
__all__ = ['ErrorHandler'] __all__ = ['ErrorHandler']
__docformat__ = 'restructuredtext' __docformat__ = 'restructuredtext'
__version__ = '$Id: errorhandler.py 1728 2009-05-01 20:35:25Z cthedot $' __version__ = '$Id: errorhandler.py 1812 2009-07-29 13:11:49Z cthedot $'
from helper import Deprecated from helper import Deprecated
import logging import logging
@ -41,6 +41,9 @@ class _ErrorHandler(object):
- False: Errors will be written to the log, this is the - False: Errors will be written to the log, this is the
default behaviour when parsing default behaviour when parsing
""" """
# may be disabled during setting of known valid items
self.enabled = True
if log: if log:
self._log = log self._log = log
else: else:
@ -74,26 +77,27 @@ class _ErrorHandler(object):
handles all calls handles all calls
logs or raises exception logs or raises exception
""" """
line, col = None, None if self.enabled:
if token: line, col = None, None
if isinstance(token, tuple): if token:
value, line, col = token[1], token[2], token[3] if isinstance(token, tuple):
value, line, col = token[1], token[2], token[3]
else:
value, line, col = token.value, token.line, token.col
msg = u'%s [%s:%s: %s]' % (
msg, line, col, value)
if error and self.raiseExceptions and not neverraise:
if isinstance(error, urllib2.HTTPError) or isinstance(error, urllib2.URLError):
raise
elif issubclass(error, xml.dom.DOMException):
error.line = line
error.col = col
# raise error(msg, line, col)
# else:
raise error(msg)
else: else:
value, line, col = token.value, token.line, token.col self._logcall(msg)
msg = u'%s [%s:%s: %s]' % (
msg, line, col, value)
if error and self.raiseExceptions and not neverraise:
if isinstance(error, urllib2.HTTPError) or isinstance(error, urllib2.URLError):
raise
elif issubclass(error, xml.dom.DOMException):
error.line = line
error.col = col
# raise error(msg, line, col)
# else:
raise error(msg)
else:
self._logcall(msg)
def setLog(self, log): def setLog(self, log):
"""set log of errorhandler's log""" """set log of errorhandler's log"""

View File

@ -3,7 +3,10 @@
__docformat__ = 'restructuredtext' __docformat__ = 'restructuredtext'
__version__ = '$Id: errorhandler.py 1234 2008-05-22 20:26:12Z cthedot $' __version__ = '$Id: errorhandler.py 1234 2008-05-22 20:26:12Z cthedot $'
import os
import re import re
import sys
import urllib
class Deprecated(object): class Deprecated(object):
"""This is a decorator which can be used to mark functions """This is a decorator which can be used to mark functions
@ -48,6 +51,10 @@ def normalize(x):
else: else:
return x return x
def path2url(path):
"""Return file URL of `path`"""
return u'file:' + urllib.pathname2url(os.path.abspath(path))
def pushtoken(token, tokens): def pushtoken(token, tokens):
"""Return new generator starting with token followed by all tokens in """Return new generator starting with token followed by all tokens in
``tokens``""" ``tokens``"""
@ -107,24 +114,24 @@ def urivalue(uri):
else: else:
return uri return uri
def normalnumber(num): #def normalnumber(num):
""" # """
Return normalized number as string. # Return normalized number as string.
""" # """
sign = '' # sign = ''
if num.startswith('-'): # if num.startswith('-'):
sign = '-' # sign = '-'
num = num[1:] # num = num[1:]
elif num.startswith('+'): # elif num.startswith('+'):
num = num[1:] # num = num[1:]
#
if float(num) == 0.0: # if float(num) == 0.0:
return '0' # return '0'
else: # else:
if num.find('.') == -1: # if num.find('.') == -1:
return sign + str(int(num)) # return sign + str(int(num))
else: # else:
a, b = num.split('.') # a, b = num.split('.')
if not a: # if not a:
a = '0' # a = '0'
return '%s%s.%s' % (sign, int(a), b) # return '%s%s.%s' % (sign, int(a), b)

View File

@ -2,9 +2,9 @@
"""A validating CSSParser""" """A validating CSSParser"""
__all__ = ['CSSParser'] __all__ = ['CSSParser']
__docformat__ = 'restructuredtext' __docformat__ = 'restructuredtext'
__version__ = '$Id: parse.py 1656 2009-02-03 20:31:06Z cthedot $' __version__ = '$Id: parse.py 1754 2009-05-30 14:50:13Z cthedot $'
from helper import Deprecated from helper import Deprecated, path2url
import codecs import codecs
import cssutils import cssutils
import os import os
@ -124,7 +124,8 @@ class CSSParser(object):
""" """
if not href: if not href:
# prepend // for file URL, urllib does not do this? # prepend // for file URL, urllib does not do this?
href = u'file:' + urllib.pathname2url(os.path.abspath(filename)) #href = u'file:' + urllib.pathname2url(os.path.abspath(filename))
href = path2url(filename)
return self.parseString(open(filename, 'rb').read(), return self.parseString(open(filename, 'rb').read(),
encoding=encoding, # read returns a str encoding=encoding, # read returns a str

View File

@ -533,105 +533,98 @@ class PreDef(object):
types = cssutils.cssproductions.CSSProductions types = cssutils.cssproductions.CSSProductions
@staticmethod @staticmethod
def CHAR(name='char', char=u',', toSeq=None, toStore=None, stop=False, def char(name='char', char=u',', toSeq=None, stop=False,
nextSor=False): nextSor=False):
"any CHAR" "any CHAR"
return Prod(name=name, match=lambda t, v: v == char, return Prod(name=name, match=lambda t, v: v == char,
toSeq=toSeq, toSeq=toSeq,
toStore=toStore,
stop=stop, stop=stop,
nextSor=nextSor) nextSor=nextSor)
@staticmethod @staticmethod
def comma(toStore=None): def comma():
return PreDef.CHAR(u'comma', u',', toStore=toStore) return PreDef.char(u'comma', u',')
@staticmethod @staticmethod
def dimension(toStore=None, nextSor=False): def dimension(nextSor=False):
return Prod(name=u'dimension', return Prod(name=u'dimension',
match=lambda t, v: t == PreDef.types.DIMENSION, match=lambda t, v: t == PreDef.types.DIMENSION,
toStore=toStore,
toSeq=lambda t, tokens: (t[0], cssutils.helper.normalize(t[1])), toSeq=lambda t, tokens: (t[0], cssutils.helper.normalize(t[1])),
nextSor=nextSor) nextSor=nextSor)
@staticmethod @staticmethod
def function(toSeq=None, toStore=None, nextSor=False): def function(toSeq=None, nextSor=False):
return Prod(name=u'function', return Prod(name=u'function',
match=lambda t, v: t == PreDef.types.FUNCTION, match=lambda t, v: t == PreDef.types.FUNCTION,
toSeq=toSeq, toSeq=toSeq,
toStore=toStore,
nextSor=nextSor) nextSor=nextSor)
@staticmethod @staticmethod
def funcEnd(toStore=None, stop=False, nextSor=False): def funcEnd(stop=False):
")" ")"
return PreDef.CHAR(u'end FUNC ")"', u')', return PreDef.char(u'end FUNC ")"', u')',
toStore=toStore, stop=stop)
stop=stop,
nextSor=nextSor)
@staticmethod @staticmethod
def ident(toStore=None, nextSor=False): def ident(nextSor=False):
return Prod(name=u'ident', return Prod(name=u'ident',
match=lambda t, v: t == PreDef.types.IDENT, match=lambda t, v: t == PreDef.types.IDENT,
toStore=toStore,
nextSor=nextSor) nextSor=nextSor)
@staticmethod @staticmethod
def number(toStore=None, nextSor=False): def number(nextSor=False):
return Prod(name=u'number', return Prod(name=u'number',
match=lambda t, v: t == PreDef.types.NUMBER, match=lambda t, v: t == PreDef.types.NUMBER,
toStore=toStore,
nextSor=nextSor) nextSor=nextSor)
@staticmethod @staticmethod
def string(toStore=None, nextSor=False): def string(nextSor=False):
"string delimiters are removed by default" "string delimiters are removed by default"
return Prod(name=u'string', return Prod(name=u'string',
match=lambda t, v: t == PreDef.types.STRING, match=lambda t, v: t == PreDef.types.STRING,
toStore=toStore,
toSeq=lambda t, tokens: (t[0], cssutils.helper.stringvalue(t[1])), toSeq=lambda t, tokens: (t[0], cssutils.helper.stringvalue(t[1])),
nextSor=nextSor) nextSor=nextSor)
@staticmethod @staticmethod
def percentage(toStore=None, nextSor=False): def percentage(nextSor=False):
return Prod(name=u'percentage', return Prod(name=u'percentage',
match=lambda t, v: t == PreDef.types.PERCENTAGE, match=lambda t, v: t == PreDef.types.PERCENTAGE,
toStore=toStore,
nextSor=nextSor) nextSor=nextSor)
@staticmethod @staticmethod
def S(name=u'whitespace', optional=True, toSeq=None, toStore=None, nextSor=False, def S():
mayEnd=False): return Prod(name=u'whitespace',
return Prod(name=name,
match=lambda t, v: t == PreDef.types.S, match=lambda t, v: t == PreDef.types.S,
optional=optional, mayEnd=True)
toSeq=toSeq,
toStore=toStore,
mayEnd=mayEnd)
@staticmethod @staticmethod
def unary(optional=True, toStore=None): def unary():
"+ or -" "+ or -"
return Prod(name=u'unary +-', match=lambda t, v: v in (u'+', u'-'), return Prod(name=u'unary +-', match=lambda t, v: v in (u'+', u'-'),
optional=optional, optional=True)
toStore=toStore)
@staticmethod @staticmethod
def uri(toStore=None, nextSor=False): def uri(nextSor=False):
"'url(' and ')' are removed and URI is stripped" "'url(' and ')' are removed and URI is stripped"
return Prod(name=u'URI', return Prod(name=u'URI',
match=lambda t, v: t == PreDef.types.URI, match=lambda t, v: t == PreDef.types.URI,
toStore=toStore,
toSeq=lambda t, tokens: (t[0], cssutils.helper.urivalue(t[1])), toSeq=lambda t, tokens: (t[0], cssutils.helper.urivalue(t[1])),
nextSor=nextSor) nextSor=nextSor)
@staticmethod @staticmethod
def hexcolor(toStore=None, toSeq=None, nextSor=False): def hexcolor(nextSor=False):
"#123456"
return Prod(name='HEX color', return Prod(name='HEX color',
match=lambda t, v: t == PreDef.types.HASH and match=lambda t, v: t == PreDef.types.HASH and (
len(v) == 4 or len(v) == 7, len(v) == 4 or len(v) == 7),
toStore=toStore, nextSor=nextSor
toSeq=toSeq, )
@staticmethod
def unicode_range(nextSor=False):
"u+123456-abc normalized to lower `u`"
return Prod(name='unicode-range',
match=lambda t, v: t == PreDef.types.UNICODE_RANGE,
toSeq=lambda t, tokens: (t[0], t[1].lower()),
nextSor=nextSor nextSor=nextSor
) )

View File

@ -43,8 +43,10 @@ class Profiles(object):
macros. macros.
""" """
CSS_LEVEL_2 = 'CSS Level 2.1' CSS_LEVEL_2 = 'CSS Level 2.1'
CSS3_COLOR = CSS_COLOR_LEVEL_3 = 'CSS Color Module Level 3'
CSS3_BOX = CSS_BOX_LEVEL_3 = 'CSS Box Module Level 3' CSS3_BOX = CSS_BOX_LEVEL_3 = 'CSS Box Module Level 3'
CSS3_COLOR = CSS_COLOR_LEVEL_3 = 'CSS Color Module Level 3'
CSS3_FONTS = 'CSS Fonts Module Level 3'
CSS3_FONT_FACE = 'CSS Fonts Module Level 3 @font-face properties'
CSS3_PAGED_MEDIA = 'CSS3 Paged Media Module' CSS3_PAGED_MEDIA = 'CSS3 Paged Media Module'
_TOKEN_MACROS = { _TOKEN_MACROS = {
@ -58,6 +60,7 @@ class Profiles(object):
'int': r'[-]?\d+', 'int': r'[-]?\d+',
'nmchar': r'[\w-]|{nonascii}|{escape}', 'nmchar': r'[\w-]|{nonascii}|{escape}',
'num': r'[-]?\d+|[-]?\d*\.\d+', 'num': r'[-]?\d+|[-]?\d*\.\d+',
'positivenum': r'\d+|[-]?\d*\.\d+',
'number': r'{num}', 'number': r'{num}',
'string': r'{string1}|{string2}', 'string': r'{string1}|{string2}',
'string1': r'"(\\\"|[^\"])*"', 'string1': r'"(\\\"|[^\"])*"',
@ -75,6 +78,7 @@ class Profiles(object):
#'color': r'(maroon|red|orange|yellow|olive|purple|fuchsia|white|lime|green|navy|blue|aqua|teal|black|silver|gray|ActiveBorder|ActiveCaption|AppWorkspace|Background|ButtonFace|ButtonHighlight|ButtonShadow|ButtonText|CaptionText|GrayText|Highlight|HighlightText|InactiveBorder|InactiveCaption|InactiveCaptionText|InfoBackground|InfoText|Menu|MenuText|Scrollbar|ThreeDDarkShadow|ThreeDFace|ThreeDHighlight|ThreeDLightShadow|ThreeDShadow|Window|WindowFrame|WindowText)|#[0-9a-f]{3}|#[0-9a-f]{6}|rgb\({w}{int}{w},{w}{int}{w},{w}{int}{w}\)|rgb\({w}{num}%{w},{w}{num}%{w},{w}{num}%{w}\)', #'color': r'(maroon|red|orange|yellow|olive|purple|fuchsia|white|lime|green|navy|blue|aqua|teal|black|silver|gray|ActiveBorder|ActiveCaption|AppWorkspace|Background|ButtonFace|ButtonHighlight|ButtonShadow|ButtonText|CaptionText|GrayText|Highlight|HighlightText|InactiveBorder|InactiveCaption|InactiveCaptionText|InfoBackground|InfoText|Menu|MenuText|Scrollbar|ThreeDDarkShadow|ThreeDFace|ThreeDHighlight|ThreeDLightShadow|ThreeDShadow|Window|WindowFrame|WindowText)|#[0-9a-f]{3}|#[0-9a-f]{6}|rgb\({w}{int}{w},{w}{int}{w},{w}{int}{w}\)|rgb\({w}{num}%{w},{w}{num}%{w},{w}{num}%{w}\)',
'integer': r'{int}', 'integer': r'{int}',
'length': r'0|{num}(em|ex|px|in|cm|mm|pt|pc)', 'length': r'0|{num}(em|ex|px|in|cm|mm|pt|pc)',
'positivelength': r'0|{positivenum}(em|ex|px|in|cm|mm|pt|pc)',
'angle': r'0|{num}(deg|grad|rad)', 'angle': r'0|{num}(deg|grad|rad)',
'time': r'0|{num}m?s', 'time': r'0|{num}m?s',
'frequency': r'0|{num}k?Hz', 'frequency': r'0|{num}k?Hz',
@ -97,6 +101,16 @@ class Profiles(object):
self.addProfile(self.CSS3_COLOR, self.addProfile(self.CSS3_COLOR,
properties[self.CSS3_COLOR], properties[self.CSS3_COLOR],
macros[self.CSS3_COLOR]) macros[self.CSS3_COLOR])
self.addProfile(self.CSS3_FONTS,
properties[self.CSS3_FONTS],
macros[self.CSS3_FONTS])
# new object for font-face only?
self.addProfile(self.CSS3_FONT_FACE,
properties[self.CSS3_FONT_FACE],
macros[self.CSS3_FONTS]) # same
self.addProfile(self.CSS3_PAGED_MEDIA, self.addProfile(self.CSS3_PAGED_MEDIA,
properties[self.CSS3_PAGED_MEDIA], properties[self.CSS3_PAGED_MEDIA],
macros[self.CSS3_PAGED_MEDIA]) macros[self.CSS3_PAGED_MEDIA])
@ -132,7 +146,7 @@ class Profiles(object):
def _getDefaultProfiles(self): def _getDefaultProfiles(self):
"If not explicitly set same as Profiles.profiles but in reverse order." "If not explicitly set same as Profiles.profiles but in reverse order."
if not self._defaultProfiles: if not self._defaultProfiles:
return self.profiles#list(reversed(self.profiles)) return self.profiles
else: else:
return self._defaultProfiles return self._defaultProfiles
@ -338,12 +352,12 @@ macros[Profiles.CSS_LEVEL_2] = {
'shape': r'rect\(({w}({length}|auto}){w},){3}{w}({length}|auto){w}\)', 'shape': r'rect\(({w}({length}|auto}){w},){3}{w}({length}|auto){w}\)',
'counter': r'counter\({w}{identifier}{w}(?:,{w}{list-style-type}{w})?\)', 'counter': r'counter\({w}{identifier}{w}(?:,{w}{list-style-type}{w})?\)',
'identifier': r'{ident}', 'identifier': r'{ident}',
'family-name': r'{string}|{identifier}', 'family-name': r'{string}|{identifier}({w}{identifier})*',
'generic-family': r'serif|sans-serif|cursive|fantasy|monospace', 'generic-family': r'serif|sans-serif|cursive|fantasy|monospace',
'absolute-size': r'(x?x-)?(small|large)|medium', 'absolute-size': r'(x?x-)?(small|large)|medium',
'relative-size': r'smaller|larger', 'relative-size': r'smaller|larger',
'font-family': r'(({family-name}|{generic-family}){w},{w})*({family-name}|{generic-family})|inherit', 'font-family': r'(({family-name}|{generic-family}){w},{w})*({family-name}|{generic-family})|inherit',
'font-size': r'{absolute-size}|{relative-size}|{length}|{percentage}|inherit', 'font-size': r'{absolute-size}|{relative-size}|{positivelength}|{percentage}|inherit',
'font-style': r'normal|italic|oblique|inherit', 'font-style': r'normal|italic|oblique|inherit',
'font-variant': r'normal|small-caps|inherit', 'font-variant': r'normal|small-caps|inherit',
'font-weight': r'normal|bold|bolder|lighter|[1-9]00|inherit', 'font-weight': r'normal|bold|bolder|lighter|[1-9]00|inherit',
@ -495,7 +509,7 @@ macros[Profiles.CSS3_BOX] = {
'overflow': macros[Profiles.CSS_LEVEL_2]['overflow'] 'overflow': macros[Profiles.CSS_LEVEL_2]['overflow']
} }
properties[Profiles.CSS3_BOX] = { properties[Profiles.CSS3_BOX] = {
'overflow': '{overflow}\s?{overflow}?|inherit', 'overflow': '{overflow}{w}{overflow}?|inherit',
'overflow-x': '{overflow}|inherit', 'overflow-x': '{overflow}|inherit',
'overflow-y': '{overflow}|inherit' 'overflow-y': '{overflow}|inherit'
} }
@ -515,6 +529,28 @@ properties[Profiles.CSS3_COLOR] = {
'opacity': r'{num}|inherit' 'opacity': r'{num}|inherit'
} }
# CSS Fonts Module Level 3 http://www.w3.org/TR/css3-fonts/
macros[Profiles.CSS3_FONTS] = {
'family-name': r'{string}|{ident}', # but STRING is effectively an IDENT???
'font-face-name': 'local\({w}{ident}{w}\)',
'font-stretch-names': r'(ultra-condensed|extra-condensed|condensed|semi-condensed|semi-expanded|expanded|extra-expanded|ultra-expanded)',
'unicode-range': r'[uU]\+[0-9A-Fa-f?]{1,6}(\-[0-9A-Fa-f]{1,6})?'
}
properties[Profiles.CSS3_FONTS] = {
'font-size-adjust': r'{number}|none|inherit',
'font-stretch': r'normal|wider|narrower|{font-stretch-names}|inherit'
}
properties[Profiles.CSS3_FONT_FACE] = {
'font-family': '{family-name}',
'font-stretch': r'{font-stretch-names}',
'font-style': r'normal|italic|oblique',
'font-weight': r'normal|bold|[1-9]00',
'src': r'({uri}{w}(format\({w}{string}{w}(\,{w}{string}{w})*\))?|{font-face-name})({w},{w}({uri}{w}(format\({w}{string}{w}(\,{w}{string}{w})*\))?|{font-face-name}))*',
'unicode-range': '{unicode-range}({w},{w}{unicode-range})*'
}
# CSS3 Paged Media # CSS3 Paged Media
macros[Profiles.CSS3_PAGED_MEDIA] = { macros[Profiles.CSS3_PAGED_MEDIA] = {
'pagesize': 'a5|a4|a3|b5|b4|letter|legal|ledger', 'pagesize': 'a5|a4|a3|b5|b4|letter|legal|ledger',

13
src/cssutils/settings.py Normal file
View File

@ -0,0 +1,13 @@
"""Experimental settings for special stuff."""
def set(key, value):
"""Call to enable special settings:
('DXImageTransform.Microsoft', True)
enable support for parsing special MS only filter values
"""
if key == 'DXImageTransform.Microsoft' and value == True:
import cssproductions
cssproductions.PRODUCTIONS.insert(1, cssproductions._DXImageTransform)

View File

@ -4,7 +4,7 @@
""" """
__all__ = ['Tokenizer', 'CSSProductions'] __all__ = ['Tokenizer', 'CSSProductions']
__docformat__ = 'restructuredtext' __docformat__ = 'restructuredtext'
__version__ = '$Id: tokenize2.py 1547 2008-12-10 20:42:26Z cthedot $' __version__ = '$Id: tokenize2.py 1834 2009-08-02 12:20:21Z cthedot $'
from cssproductions import * from cssproductions import *
from helper import normalize from helper import normalize
@ -147,7 +147,8 @@ class Tokenizer(object):
break break
if name in ('DIMENSION', 'IDENT', 'STRING', 'URI', if name in ('DIMENSION', 'IDENT', 'STRING', 'URI',
'HASH', 'COMMENT', 'FUNCTION', 'INVALID'): 'HASH', 'COMMENT', 'FUNCTION', 'INVALID',
'UNICODE-RANGE'):
# may contain unicode escape, replace with normal char # may contain unicode escape, replace with normal char
# but do not _normalize (?) # but do not _normalize (?)
value = self.unicodesub(_repl, found) value = self.unicodesub(_repl, found)
@ -166,7 +167,6 @@ class Tokenizer(object):
name = self._atkeywords.get(_normalize(found), 'ATKEYWORD') name = self._atkeywords.get(_normalize(found), 'ATKEYWORD')
value = found # should not contain unicode escape (?) value = found # should not contain unicode escape (?)
yield (name, value, line, col) yield (name, value, line, col)
text = text[len(found):] text = text[len(found):]
nls = found.count(self._linesep) nls = found.count(self._linesep)

View File

@ -2,7 +2,7 @@
""" """
__all__ = [] __all__ = []
__docformat__ = 'restructuredtext' __docformat__ = 'restructuredtext'
__version__ = '$Id: util.py 1743 2009-05-09 20:33:15Z cthedot $' __version__ = '$Id: util.py 1781 2009-07-19 12:30:49Z cthedot $'
from helper import normalize from helper import normalize
from itertools import ifilter from itertools import ifilter
@ -663,6 +663,9 @@ class _Namespaces(object):
self.parentStyleSheet = parentStyleSheet self.parentStyleSheet = parentStyleSheet
self._log = log self._log = log
def __repr__(self):
return "%r" % self.namespaces
def __contains__(self, prefix): def __contains__(self, prefix):
return prefix in self.namespaces return prefix in self.namespaces