diff --git a/src/calibre/utils/PythonMagickWand.py b/src/calibre/utils/PythonMagickWand.py index abbc46bc9d..f650d6dc4e 100644 --- a/src/calibre/utils/PythonMagickWand.py +++ b/src/calibre/utils/PythonMagickWand.py @@ -88,7 +88,7 @@ _magick_error = None try: _magick = ctypes.CDLL(_lib) except Exception, err: - global _magick_error + #global _magick_error _magick_error = str(err) _initialized = False diff --git a/src/cssutils/__init__.py b/src/cssutils/__init__.py index 467d17238b..6a107c3c69 100644 --- a/src/cssutils/__init__.py +++ b/src/cssutils/__init__.py @@ -70,11 +70,11 @@ Usage may be:: __all__ = ['css', 'stylesheets', 'CSSParser', 'CSSSerializer'] __docformat__ = 'restructuredtext' __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 xml.dom @@ -165,6 +165,23 @@ def parse(*a, **k): return parseFile(*a, **k) 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 def setSerializer(serializer): @@ -172,7 +189,6 @@ def setSerializer(serializer): global ser ser = serializer - def getUrls(sheet): """Retrieve all ``url(urlstring)`` values (in e.g. :class:`cssutils.css.CSSImportRule` or :class:`cssutils.css.CSSValue` @@ -284,5 +300,6 @@ def resolveImports(sheet, target=None): target.add(rule) return target + if __name__ == '__main__': print __doc__ diff --git a/src/cssutils/_fetch.py b/src/cssutils/_fetch.py index 75b3e525fb..af18bc5640 100644 --- a/src/cssutils/_fetch.py +++ b/src/cssutils/_fetch.py @@ -1,8 +1,9 @@ """Default URL reading functions""" -__all__ = ['_defaultFetcher', '_readUrl'] +__all__ = ['_defaultFetcher'] __docformat__ = 'restructuredtext' __version__ = '$Id: tokenize2.py 1547 2008-12-10 20:42:26Z cthedot $' +from cssutils import VERSION import encutils import errorhandler import urllib2 @@ -16,8 +17,11 @@ def _defaultFetcher(url): Returns ``(encoding, string)`` or ``None`` """ - try: - res = urllib2.urlopen(url) + request = urllib2.Request(url) + request.add_header('User-agent', + 'cssutils %s (http://www.cthedot.de/cssutils/)' % VERSION) + try: + res = urllib2.urlopen(request) except OSError, e: # e.g if file URL and not found log.warn(e, error=OSError) diff --git a/src/cssutils/_fetchgae.py b/src/cssutils/_fetchgae.py index 4250e8bc8c..7760ac6c6b 100644 --- a/src/cssutils/_fetchgae.py +++ b/src/cssutils/_fetchgae.py @@ -1,5 +1,5 @@ """GAE specific URL reading functions""" -__all__ = ['_defaultFetcher', '_readUrl'] +__all__ = ['_defaultFetcher'] __docformat__ = 'restructuredtext' __version__ = '$Id: tokenize2.py 1547 2008-12-10 20:42:26Z cthedot $' diff --git a/src/cssutils/css/cssfontfacerule.py b/src/cssutils/css/cssfontfacerule.py index 76bdc8aeb0..4252bf30a0 100644 --- a/src/cssutils/css/cssfontfacerule.py +++ b/src/cssutils/css/cssfontfacerule.py @@ -1,8 +1,11 @@ """CSSFontFaceRule implements DOM Level 2 CSS CSSFontFaceRule. + +From cssutils 0.9.6 additions from CSS Fonts Module Level 3 are +added http://www.w3.org/TR/css3-fonts/. """ __all__ = ['CSSFontFaceRule'] __docformat__ = 'restructuredtext' -__version__ = '$Id: 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 import cssrule @@ -21,6 +24,11 @@ class CSSFontFaceRule(cssrule.CSSRule): : FONT_FACE_SYM S* '{' S* declaration [ ';' S* declaration ]* '}' S* ; + + cssutils uses a :class:`~cssutils.css.CSSStyleDeclaration` to + represent the font descriptions. For validation a specific profile + is used though were some properties have other valid values than + when used in e.g. a :class:`~cssutils.css.CSSStyleRule`. """ def __init__(self, style=None, parentRule=None, parentStyleSheet=None, readonly=False): @@ -28,25 +36,26 @@ class CSSFontFaceRule(cssrule.CSSRule): If readonly allows setting of properties in constructor only. :param style: - CSSStyleDeclaration for this CSSStyleRule + CSSStyleDeclaration used to hold any font descriptions + for this CSSFontFaceRule """ super(CSSFontFaceRule, self).__init__(parentRule=parentRule, parentStyleSheet=parentStyleSheet) self._atkeyword = u'@font-face' + self._style = CSSStyleDeclaration(parentRule=self) if style: self.style = style - else: - self._style = CSSStyleDeclaration(parentRule=self) self._readonly = readonly - + def __repr__(self): return "cssutils.css.%s(style=%r)" % ( self.__class__.__name__, self.style.cssText) def __str__(self): - return "" % ( - self.__class__.__name__, self.style.cssText, id(self)) + return "" % ( + self.__class__.__name__, self.style.cssText, self.valid, + id(self)) def _getCssText(self): """Return serialized property cssText.""" @@ -112,15 +121,22 @@ class CSSFontFaceRule(cssrule.CSSRule): self._log.error(u'CSSFontFaceRule: Trailing content found.', token=nonetoken) - newstyle = CSSStyleDeclaration() + teststyle = CSSStyleDeclaration(parentRule=self) if 'EOF' == typ: # add again as style needs it styletokens.append(braceorEOFtoken) - newstyle.cssText = styletokens + # may raise: + teststyle.cssText = styletokens if wellformed: - self.style = newstyle - self._setSeq(newseq) # contains (probably comments) upto { only + # contains probably comments only upto { + self._setSeq(newseq) + + # known as correct from before + cssutils.log.enabled = False + self.style.cssText = styletokens + cssutils.log.enabled = True + cssText = property(_getCssText, _setCssText, doc="(DOM) The parsable textual representation of this rule.") @@ -132,9 +148,10 @@ class CSSFontFaceRule(cssrule.CSSRule): """ self._checkReadonly() if isinstance(style, basestring): - self._style = CSSStyleDeclaration(parentRule=self, cssText=style) + self._style.cssText = style else: - self._style._seq = style.seq + self._style = style + self._style.parentRule = self style = property(lambda self: self._style, _setStyle, 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 " "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: wellformed = property(lambda self: True) diff --git a/src/cssutils/css/cssimportrule.py b/src/cssutils/css/cssimportrule.py index d77a2f195f..e0e1d5e832 100644 --- a/src/cssutils/css/cssimportrule.py +++ b/src/cssutils/css/cssimportrule.py @@ -1,10 +1,8 @@ """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'] __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 cssutils @@ -320,7 +318,8 @@ class CSSImportRule(cssrule.CSSRule): parentHref = self.parentStyleSheet.href if parentHref is None: # 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) # all possible exceptions are ignored (styleSheet is None then) diff --git a/src/cssutils/css/cssmediarule.py b/src/cssutils/css/cssmediarule.py index d4b82af600..34a2c0de06 100644 --- a/src/cssutils/css/cssmediarule.py +++ b/src/cssutils/css/cssmediarule.py @@ -1,7 +1,7 @@ """CSSMediaRule implements DOM Level 2 CSS CSSMediaRule.""" __all__ = ['CSSMediaRule'] __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 cssutils @@ -34,15 +34,11 @@ class CSSMediaRule(cssrule.CSSRule): readonly=readonly) self.name = name self.cssRules = cssutils.css.cssrulelist.CSSRuleList() - self.cssRules.append = self.insertRule - self.cssRules.extend = self.insertRule - self.cssRules.__delitem__ == self.deleteRule - self._readonly = readonly def __iter__(self): """Generator iterating over these rule's cssRules.""" - for rule in self.cssRules: + for rule in self._cssRules: yield rule def __repr__(self): @@ -53,6 +49,20 @@ class CSSMediaRule(cssrule.CSSRule): return "" % ( 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): """Return serialized property cssText.""" return cssutils.ser.do_CSSMediaRule(self) @@ -204,9 +214,9 @@ class CSSMediaRule(cssrule.CSSRule): self._media.mediaText = newmedia.mediaText self.name = name self._setSeq(nameseq) - del self.cssRules[:] + del self._cssRules[:] for r in newcssrules: - self.cssRules.append(r) + self._cssRules.append(r) cssText = property(_getCssText, _setCssText, doc="(DOM) The parsable textual representation of this rule.") @@ -245,12 +255,12 @@ class CSSMediaRule(cssrule.CSSRule): self._checkReadonly() try: - self.cssRules[index]._parentRule = None # detach - del self.cssRules[index] # remove from @media + self._cssRules[index]._parentRule = None # detach + del self._cssRules[index] # remove from @media except IndexError: raise xml.dom.IndexSizeErr( 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): """Add `rule` to end of this mediarule. @@ -300,11 +310,11 @@ class CSSMediaRule(cssrule.CSSRule): # check position if index is None: - index = len(self.cssRules) - elif index < 0 or index > self.cssRules.length: + index = len(self._cssRules) + elif index < 0 or index > self._cssRules.length: raise xml.dom.IndexSizeErr( u'CSSMediaRule: Invalid index %s for CSSRuleList with a length of %s.' % ( - index, self.cssRules.length)) + index, self._cssRules.length)) # parse if isinstance(rule, basestring): @@ -315,6 +325,13 @@ class CSSMediaRule(cssrule.CSSRule): self._log.error(u'CSSMediaRule: Invalid Rule: %s' % rule) return 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): self._log.error(u'CSSMediaRule: Not a CSSRule: %s' % rule) return @@ -332,7 +349,7 @@ class CSSMediaRule(cssrule.CSSRule): error=xml.dom.HierarchyRequestErr) return - self.cssRules.insert(index, rule) + self._cssRules.insert(index, rule) rule._parentRule = self rule._parentStyleSheet = self.parentStyleSheet return index diff --git a/src/cssutils/css/csspagerule.py b/src/cssutils/css/csspagerule.py index 04cde02a05..b3e60be922 100644 --- a/src/cssutils/css/csspagerule.py +++ b/src/cssutils/css/csspagerule.py @@ -1,8 +1,7 @@ -"""CSSPageRule implements DOM Level 2 CSS CSSPageRule. -""" +"""CSSPageRule implements DOM Level 2 CSS CSSPageRule.""" __all__ = ['CSSPageRule'] __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 selectorlist import SelectorList @@ -45,11 +44,12 @@ class CSSPageRule(cssrule.CSSRule): tempseq.append(self.selectorText, 'selectorText') else: self._selectorText = self._tempSeq() + + self._style = CSSStyleDeclaration(parentRule=self) if style: self.style = style tempseq.append(self.style, 'style') - else: - self._style = CSSStyleDeclaration(parentRule=self) + self._setSeq(tempseq) self._readonly = readonly @@ -192,7 +192,7 @@ class CSSPageRule(cssrule.CSSRule): wellformed, newselectorseq = self.__parseSelectorText(selectortokens) - newstyle = CSSStyleDeclaration() + teststyle = CSSStyleDeclaration(parentRule=self) val, typ = self._tokenvalue(braceorEOFtoken), self._type(braceorEOFtoken) if val != u'}' and typ != 'EOF': wellformed = False @@ -203,13 +203,15 @@ class CSSPageRule(cssrule.CSSRule): if 'EOF' == typ: # add again as style needs it styletokens.append(braceorEOFtoken) - newstyle.cssText = styletokens + teststyle.cssText = styletokens if wellformed: - self._selectorText = newselectorseq # already parsed - self.style = newstyle - self._setSeq(newselectorseq) # contains upto style only - + # known as correct from before + cssutils.log.enabled = False + self._selectorText = newselectorseq # TODO: TEST and REFS + self.style.cssText = styletokens + cssutils.log.enabled = True + cssText = property(_getCssText, _setCssText, doc="(DOM) The parsable textual representation of this rule.") @@ -239,7 +241,7 @@ class CSSPageRule(cssrule.CSSRule): # may raise SYNTAX_ERR wellformed, newseq = self.__parseSelectorText(selectorText) - if wellformed and newseq: + if wellformed: self._selectorText = newseq selectorText = property(_getSelectorText, _setSelectorText, @@ -251,19 +253,16 @@ class CSSPageRule(cssrule.CSSRule): a CSSStyleDeclaration or string """ self._checkReadonly() - if isinstance(style, basestring): self._style.cssText = style else: - # cssText would be serialized with optional preferences - # so use seq! - self._style._seq = style.seq + self._style = style + self._style.parentRule = self style = property(lambda self: self._style, _setStyle, doc="(DOM) The declaration-block of this rule set, " "a :class:`~cssutils.css.CSSStyleDeclaration`.") - type = property(lambda self: self.PAGE_RULE, doc="The type of this rule, as defined by a CSSRule " "type constant.") diff --git a/src/cssutils/css/cssrule.py b/src/cssutils/css/cssrule.py index e3eb7296c7..1d21b14ea8 100644 --- a/src/cssutils/css/cssrule.py +++ b/src/cssutils/css/cssrule.py @@ -1,7 +1,7 @@ """CSSRule implements DOM Level 2 CSS CSSRule.""" __all__ = ['CSSRule'] __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 xml.dom @@ -35,6 +35,7 @@ class CSSRule(cssutils.util.Base2): def __init__(self, parentRule=None, parentStyleSheet=None, readonly=False): """Set common attributes for all rules.""" super(CSSRule, self).__init__() + self._parent = parentRule self._parentRule = parentRule self._parentStyleSheet = parentStyleSheet self._setSeq(self._tempSeq()) @@ -78,6 +79,10 @@ class CSSRule(cssutils.util.Base2): "rule. This reflects the current state of the rule " "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, doc="If this rule is contained inside " "another rule (e.g. a style rule inside " diff --git a/src/cssutils/css/cssrulelist.py b/src/cssutils/css/cssrulelist.py index 43adfe1415..a20f19cf47 100644 --- a/src/cssutils/css/cssrulelist.py +++ b/src/cssutils/css/cssrulelist.py @@ -1,9 +1,8 @@ """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'] __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): """The CSSRuleList object represents an (ordered) list of statements. diff --git a/src/cssutils/css/cssstyledeclaration.py b/src/cssutils/css/cssstyledeclaration.py index ae106341bb..1386c68a62 100644 --- a/src/cssutils/css/cssstyledeclaration.py +++ b/src/cssutils/css/cssstyledeclaration.py @@ -51,7 +51,7 @@ TODO: """ __all__ = ['CSSStyleDeclaration', 'Property'] __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 property import Property @@ -127,6 +127,11 @@ class CSSStyleDeclaration(CSS2Properties, cssutils.util.Base2): for name in self.__nnames(): yield self.getProperty(name) return properties() + + def keys(self): + """Analoguous to standard dict returns property names which are set in + this declaration.""" + return list(self.__nnames()) def __getitem__(self, CSSName): """Retrieve the value of property ``CSSName`` from this declaration. @@ -247,6 +252,13 @@ class CSSStyleDeclaration(CSS2Properties, cssutils.util.Base2): """ self.removeProperty(CSSName) + def children(self): + """Generator yielding any known child in this declaration including + *all* properties, comments or CSSUnknownrules. + """ + for item in self._seq: + yield item.value + def _getCssText(self): """Return serialized property cssText.""" return cssutils.ser.do_css_CSSStyleDeclaration(self) @@ -275,10 +287,10 @@ class CSSStyleDeclaration(CSS2Properties, cssutils.util.Base2): semicolon=True) if self._tokenvalue(tokens[-1]) == u';': tokens.pop() - property = Property() + property = Property(parent=self) property.cssText = tokens if property.wellformed: - seq.append(property, 'Property') + seq.append(property, 'Property') else: self._log.error(u'CSSStyleDeclaration: Syntax Error in Property: %s' % self._valuestr(tokens)) @@ -301,12 +313,13 @@ class CSSStyleDeclaration(CSS2Properties, cssutils.util.Base2): productions={'IDENT': ident},#, 'CHAR': char}, default=unexpected) # wellformed set by parse - # post conditions + for item in newseq: + item.value._parent = self + # do not check wellformed as invalid things are removed anyway - #if wellformed: self._setSeq(newseq) - + cssText = property(_getCssText, _setCssText, doc="(DOM) A parsable textual representation of the declaration\ block excluding the surrounding curly braces.") @@ -322,13 +335,12 @@ class CSSStyleDeclaration(CSS2Properties, cssutils.util.Base2): """ return cssutils.ser.do_css_CSSStyleDeclaration(self, separator) - def _getParentRule(self): - return self._parentRule - def _setParentRule(self, parentRule): self._parentRule = parentRule - - parentRule = property(_getParentRule, _setParentRule, + for x in self.children(): + x.parent = self + + parentRule = property(lambda self: self._parentRule, _setParentRule, doc="(DOM) The CSS rule that contains this declaration block or " "None if this CSSStyleDeclaration is not attached to a CSSRule.") @@ -581,6 +593,7 @@ class CSSStyleDeclaration(CSS2Properties, cssutils.util.Base2): property.priority = newp.priority break else: + newp.parent = self self.seq._readonly = False self.seq.append(newp, 'Property') self.seq._readonly = True diff --git a/src/cssutils/css/cssstylerule.py b/src/cssutils/css/cssstylerule.py index 661e719743..51991b8384 100644 --- a/src/cssutils/css/cssstylerule.py +++ b/src/cssutils/css/cssstylerule.py @@ -1,7 +1,7 @@ """CSSStyleRule implements DOM Level 2 CSS CSSStyleRule.""" __all__ = ['CSSStyleRule'] __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 selectorlist import SelectorList @@ -107,6 +107,8 @@ class CSSStyleRule(cssrule.CSSRule): else: wellformed = True + testselectorlist, teststyle = None, None + bracetoken = selectortokens.pop() if self._tokenvalue(bracetoken) != u'{': wellformed = False @@ -117,11 +119,11 @@ class CSSStyleRule(cssrule.CSSRule): wellformed = False self._log.error(u'CSSStyleRule: No selector found: %r.' % self._valuestr(cssText), bracetoken) - newselectorlist = SelectorList(selectorText=(selectortokens, + + testselectorlist = SelectorList(selectorText=(selectortokens, namespaces), parentRule=self) - newstyle = CSSStyleDeclaration() if not styletokens: wellformed = False self._log.error( @@ -139,11 +141,14 @@ class CSSStyleRule(cssrule.CSSRule): if 'EOF' == typ: # add again as style needs it styletokens.append(braceorEOFtoken) - newstyle.cssText = styletokens + teststyle = CSSStyleDeclaration(styletokens, parentRule=self) - if wellformed: - self._selectorList = newselectorlist - self.style = newstyle + if wellformed and testselectorlist and teststyle: + # known as correct from before + cssutils.log.enabled = False + self.style.cssText = styletokens + self.selectorList.selectorText=(selectortokens, namespaces) + cssutils.log.enabled = True cssText = property(_getCssText, _setCssText, doc="(DOM) The parsable textual representation of this rule.") @@ -164,11 +169,12 @@ class CSSStyleRule(cssrule.CSSRule): def _setSelectorList(self, selectorList): """ - :param selectorList: selectorList, only content is used, not the actual - object + :param selectorList: A SelectorList which replaces the current + selectorList object """ self._checkReadonly() - self.selectorText = selectorList.selectorText + selectorList._parentRule = self + self._selectorList = selectorList selectorList = property(lambda self: self._selectorList, _setSelectorList, doc="The SelectorList of this rule.") @@ -200,16 +206,15 @@ class CSSStyleRule(cssrule.CSSRule): def _setStyle(self, style): """ - :param style: CSSStyleDeclaration or string, only the cssText of a - declaration is used, not the actual object + :param style: A string or CSSStyleDeclaration which replaces the + current style object. """ self._checkReadonly() if isinstance(style, basestring): self._style.cssText = style else: - # cssText would be serialized with optional preferences - # so use _seq! - self._style._seq = style._seq + style._parentRule = self + self._style = style style = property(lambda self: self._style, _setStyle, doc="(DOM) The declaration-block of this rule set.") diff --git a/src/cssutils/css/cssstylesheet.py b/src/cssutils/css/cssstylesheet.py index bfc5c0f5d2..ad0798473e 100644 --- a/src/cssutils/css/cssstylesheet.py +++ b/src/cssutils/css/cssstylesheet.py @@ -9,7 +9,7 @@ TODO: """ __all__ = ['CSSStyleSheet'] __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.util import _Namespaces, _SimpleNamespaces, _readUrl @@ -42,8 +42,6 @@ class CSSStyleSheet(cssutils.stylesheets.StyleSheet): self._ownerRule = ownerRule self.cssRules = cssutils.css.CSSRuleList() - self.cssRules.append = self.insertRule - self.cssRules.extend = self.insertRule self._namespaces = _Namespaces(parentStyleSheet=self, log=self._log) self._readonly = readonly @@ -53,7 +51,7 @@ class CSSStyleSheet(cssutils.stylesheets.StyleSheet): def __iter__(self): "Generator which iterates over cssRules." - for rule in self.cssRules: + for rule in self._cssRules: yield rule def __repr__(self): @@ -78,7 +76,7 @@ class CSSStyleSheet(cssutils.stylesheets.StyleSheet): def _cleanNamespaces(self): "Remove all namespace rules with same namespaceURI but last one set." - rules = self.cssRules + rules = self._cssRules namespaceitems = self.namespaces.items() i = 0 while i < len(rules): @@ -101,6 +99,19 @@ class CSSStyleSheet(cssutils.stylesheets.StyleSheet): 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, + "All Rules in this style sheet, a " + ":class:`~cssutils.css.CSSRuleList`.") + def _getCssText(self): "Textual representation of the stylesheet (a byte string)." return cssutils.ser.do_CSSStyleSheet(self) @@ -260,7 +271,7 @@ class CSSStyleSheet(cssutils.stylesheets.StyleSheet): default=ruleset) if wellformed: - del self.cssRules[:] + del self._cssRules[:] for rule in newseq: self.insertRule(rule, _clean=False) self._cleanNamespaces() @@ -277,7 +288,7 @@ class CSSStyleSheet(cssutils.stylesheets.StyleSheet): except AttributeError: try: # explicit @charset - selfAsParentEncoding = self.cssRules[0].encoding + selfAsParentEncoding = self._cssRules[0].encoding except (IndexError, AttributeError): # default not UTF-8 but None! selfAsParentEncoding = None @@ -312,6 +323,14 @@ class CSSStyleSheet(cssutils.stylesheets.StyleSheet): """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`. @@ -319,7 +338,7 @@ class CSSStyleSheet(cssutils.stylesheets.StyleSheet): default encoding of utf-8. """ try: - rule = self.cssRules[0] + rule = self._cssRules[0] except IndexError: rule = None if rule and rule.CHARSET_RULE == rule.type: @@ -330,14 +349,6 @@ class CSSStyleSheet(cssutils.stylesheets.StyleSheet): elif encoding: 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, "(cssutils) Reflect encoding of an @charset rule or 'utf-8' " "(default) if set to ``None``") @@ -371,11 +382,11 @@ class CSSStyleSheet(cssutils.stylesheets.StyleSheet): self._checkReadonly() try: - rule = self.cssRules[index] + rule = self._cssRules[index] except IndexError: raise xml.dom.IndexSizeErr( u'CSSStyleSheet: %s is not a valid index in the rulelist of length %i' % ( - index, self.cssRules.length)) + index, self._cssRules.length)) else: if rule.type == rule.NAMESPACE_RULE: # check all namespacerules if used @@ -388,7 +399,7 @@ class CSSStyleSheet(cssutils.stylesheets.StyleSheet): return 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): """ @@ -427,11 +438,11 @@ class CSSStyleSheet(cssutils.stylesheets.StyleSheet): # check position if index is None: - index = len(self.cssRules) - elif index < 0 or index > self.cssRules.length: + 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 length of %s.' % ( - index, self.cssRules.length)) + index, self._cssRules.length)) return 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 # @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): + 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 + rule = self._cssRules[0].cssText + rule else: newrulescount, newruleindex = 1, 0 @@ -484,29 +495,29 @@ class CSSStyleSheet(cssutils.stylesheets.StyleSheet): 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 + 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._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) + 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: + 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) + self._cssRules.insert(index, rule) # @import elif rule.type == rule.IMPORT_RULE: @@ -514,27 +525,27 @@ class CSSStyleSheet(cssutils.stylesheets.StyleSheet): # 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)): + for i, r in enumerate(reversed(self._cssRules)): if r.type == rule.type: - index = len(self.cssRules) - i + index = len(self._cssRules) - i break else: # 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): index = 1 else: index = 0 else: # after @charset - if index == 0 and self.cssRules and\ - self.cssRules[0].type == rule.CHARSET_RULE: + 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, @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, r.STYLE_RULE, r.FONT_FACE_RULE): self._log.error( @@ -542,27 +553,27 @@ class CSSStyleSheet(cssutils.stylesheets.StyleSheet): index, error=xml.dom.HierarchyRequestErr) return - self.cssRules.insert(index, rule) + self._cssRules.insert(index, rule) # @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)): + for i, r in enumerate(reversed(self._cssRules)): if r.type == rule.type: - index = len(self.cssRules) - i + index = len(self._cssRules) - i break else: # 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, 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:]: + 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.' % @@ -570,7 +581,7 @@ class CSSStyleSheet(cssutils.stylesheets.StyleSheet): error=xml.dom.HierarchyRequestErr) return # 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, r.FONT_FACE_RULE): self._log.error( @@ -582,7 +593,7 @@ class CSSStyleSheet(cssutils.stylesheets.StyleSheet): if not (rule.prefix in self.namespaces and self.namespaces[rule.prefix] == rule.namespaceURI): # no doublettes - self.cssRules.insert(index, rule) + self._cssRules.insert(index, rule) if _clean: self._cleanNamespaces() @@ -590,17 +601,17 @@ class CSSStyleSheet(cssutils.stylesheets.StyleSheet): else: if inOrder: # simply add to end as no specific order - self.cssRules.append(rule) - index = len(self.cssRules) - 1 + self._cssRules.append(rule) + index = len(self._cssRules) - 1 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): self._log.error( u'CSSStylesheet: Cannot insert rule here, found @charset, @import or @namespace before index %s.' % index, error=xml.dom.HierarchyRequestErr) return - self.cssRules.insert(index, rule) + self._cssRules.insert(index, rule) # post settings, TODO: for other rules which contain @rules rule._parentStyleSheet = self diff --git a/src/cssutils/css/cssvalue.py b/src/cssutils/css/cssvalue.py index 9b5a8a1aef..07589f5531 100644 --- a/src/cssutils/css/cssvalue.py +++ b/src/cssutils/css/cssvalue.py @@ -7,7 +7,7 @@ """ __all__ = ['CSSValue', 'CSSPrimitiveValue', 'CSSValueList', 'RGBColor'] __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 * import cssutils @@ -81,6 +81,7 @@ class CSSValue(cssutils.util._NewBase): [ NUMBER S* | PERCENTAGE S* | LENGTH S* | EMS S* | EXS S* | ANGLE S* | TIME S* | FREQ S* ] | STRING S* | IDENT S* | URI S* | hexcolor | function + | UNICODE-RANGE S* ; function : FUNCTION S* expr ')' S* @@ -108,7 +109,7 @@ class CSSValue(cssutils.util._NewBase): # used as operator is , / or S nextSor = u',/' - + term = Choice(Sequence(PreDef.unary(), Choice(PreDef.number(nextSor=nextSor), PreDef.percentage(nextSor=nextSor), @@ -117,22 +118,25 @@ class CSSValue(cssutils.util._NewBase): PreDef.ident(nextSor=nextSor), PreDef.uri(nextSor=nextSor), PreDef.hexcolor(nextSor=nextSor), + PreDef.unicode_range(nextSor=nextSor), # special case IE only 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(', - u'alpha('), + u'alpha(') or + v.startswith(u'progid:DXImageTransform.Microsoft.') ), nextSor=nextSor, toSeq=lambda t, tokens: (ExpressionValue.name, ExpressionValue(cssutils.helper.pushtoken(t, - tokens)))), + tokens))) + ), PreDef.function(nextSor=nextSor, toSeq=lambda t, tokens: ('FUNCTION', CSSFunction(cssutils.helper.pushtoken(t, tokens))))) - operator = Choice(PreDef.S(optional=False, mayEnd=True), - PreDef.CHAR('comma', ',', toSeq=lambda t, tokens: ('operator', t[1])), - PreDef.CHAR('slash', '/', toSeq=lambda t, tokens: ('operator', t[1])), + operator = Choice(PreDef.S(), + PreDef.char('comma', ',', toSeq=lambda t, tokens: ('operator', t[1])), + PreDef.char('slash', '/', toSeq=lambda t, tokens: ('operator', t[1])), optional=True) # CSSValue PRODUCTIONS valueprods = Sequence(term, @@ -154,19 +158,22 @@ class CSSValue(cssutils.util._NewBase): item = seq[i] if item.type == self._prods.S: 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) - 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 + if firstvalue: + # list of IDENTs is handled as STRING! + if firstvalue[1] == self._prods.IDENT: + firstvalue = firstvalue[0], 'STRING' + elif item.value == u'/': - # counts as a single one + # / separated items count as one 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 i += 1 try: @@ -187,12 +194,12 @@ class CSSValue(cssutils.util._NewBase): if not firstvalue: firstvalue = (item.value, item.type) count += 1 - + else: newseq.appendItem(item) i += 1 - + if not firstvalue: self._log.error( u'CSSValue: Unknown syntax or no value: %r.' % @@ -201,7 +208,7 @@ class CSSValue(cssutils.util._NewBase): # ok and set self._setSeq(newseq) self.wellformed = wellformed - + if hasattr(self, '_value'): # only in case of CSSPrimitiveValue, else remove! del self._value @@ -228,6 +235,8 @@ class CSSValue(cssutils.util._NewBase): return cssutils.helper.string(item.value) elif self._prods.URI == item.type: return cssutils.helper.uri(item.value) + elif self._prods.FUNCTION == item.type: + return item.value.cssText else: return item.value @@ -253,7 +262,8 @@ class CSSValue(cssutils.util._NewBase): self._prods.NUMBER, self._prods.PERCENTAGE, self._prods.STRING, - self._prods.URI): + self._prods.URI, + self._prods.UNICODE_RANGE): if nexttocommalist: # wait until complete commalist.append(itemValue(item)) @@ -353,6 +363,7 @@ class CSSPrimitiveValue(CSSValue): CSS_RGBCOLOR = 25 # NOT OFFICIAL: CSS_RGBACOLOR = 26 + CSS_UNICODE_RANGE = 27 _floattypes = (CSS_NUMBER, CSS_PERCENTAGE, CSS_EMS, CSS_EXS, CSS_PX, CSS_CM, CSS_MM, CSS_IN, CSS_PT, CSS_PC, @@ -426,9 +437,10 @@ class CSSPrimitiveValue(CSSValue): 'CSS_MS', 'CSS_S', 'CSS_HZ', 'CSS_KHZ', 'CSS_DIMENSION', - 'CSS_STRING', 'CSS_URI', 'CSS_IDENT', + 'CSS_STRING', 'CSS_URI', 'CSS_IDENT', 'CSS_ATTR', 'CSS_COUNTER', 'CSS_RECT', 'CSS_RGBCOLOR', 'CSS_RGBACOLOR', + 'CSS_UNICODE_RANGE' ] _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.PERCENTAGE: 'CSS_PERCENTAGE', __types.STRING: 'CSS_STRING', + __types.UNICODE_RANGE: 'CSS_UNICODE_RANGE', __types.URI: 'CSS_URI', __types.IDENT: 'CSS_IDENT', __types.HASH: 'CSS_RGBCOLOR', @@ -474,7 +487,6 @@ class CSSPrimitiveValue(CSSValue): def __set_primitiveType(self): """primitiveType is readonly but is set lazy if accessed""" # TODO: check unary and font-family STRING a, b, "c" - val, type_ = self._value # try get by type_ pt = self.__unitbytype.get(type_, 'CSS_UNKNOWN') @@ -827,7 +839,7 @@ class CSSFunction(CSSPrimitiveValue): types.PERCENTAGE, types.STRING), toSeq=lambda t, tokens: (t[0], CSSPrimitiveValue(t[1]))) - + funcProds = Sequence(Prod(name='FUNC', match=lambda t, v: t == types.FUNCTION, toSeq=lambda t, tokens: (t[0], cssutils.helper.normalize(t[1]))), @@ -969,23 +981,31 @@ class RGBColor(CSSPrimitiveValue): class ExpressionValue(CSSFunction): """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)' def _productiondefinition(self): """Return defintion used for parsing.""" types = self._prods # rename! + + def toSeq(t, tokens): + "Do not normalize function name!" + return t[0], t[1] + funcProds = Sequence(Prod(name='expression', 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', match=lambda t, v: t == self._prods.FUNCTION, toSeq=lambda t, tokens: (CSSFunction.name, CSSFunction(cssutils.helper.pushtoken(t, - tokens)))), + tokens))) + ), Prod(name='part', 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)), PreDef.funcEnd(stop=True)) return funcProds diff --git a/src/cssutils/css/property.py b/src/cssutils/css/property.py index c096fa767d..3961132060 100644 --- a/src/cssutils/css/property.py +++ b/src/cssutils/css/property.py @@ -1,7 +1,7 @@ """Property is a single CSS property in a CSSStyleDeclaration.""" __all__ = ['Property'] __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 cssvalue import CSSValue @@ -44,7 +44,7 @@ class Property(cssutils.util.Base): """ def __init__(self, name=None, value=None, priority=u'', - _mediaQuery=False, _parent=None): + _mediaQuery=False, parent=None, parentStyle=None): """ :param name: a property name string (will be normalized) @@ -55,16 +55,17 @@ class Property(cssutils.util.Base): u'!important' or u'important' :param _mediaQuery: if ``True`` value is optional (used by MediaQuery) - :param _parent: + :param parent: the parent object, normally a :class:`cssutils.css.CSSStyleDeclaration` + :param parentStyle: + DEPRECATED: Use ``parent`` instead """ super(Property, self).__init__() - self.seqs = [[], None, []] self.wellformed = False self._mediaQuery = _mediaQuery - self._parent = _parent + self.parent = parent self.__nametoken = None self._name = u'' @@ -363,13 +364,18 @@ class Property(cssutils.util.Base): literalpriority = property(lambda self: self._literalpriority, doc="Readonly literal (not normalized) priority of this property") - def validate(self, profiles=None): - """Validate value against `profiles`. + def _setParent(self, parent): + self._parent = parent + + parent = property(lambda self: self._parent, _setParent, + doc="The Parent Node (normally a CSSStyledeclaration) of this " + "Property") + + def validate(self): + """Validate value against `profiles` which are checked dynamically. + properties in e.g. @font-face rules are checked against + ``cssutils.profile.CSS3_FONT_FACE`` only. - :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: - INVALID (so the property is known but not valid) @@ -424,6 +430,16 @@ class Property(cssutils.util.Base): """ 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 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 " "in the properties context.") + + + @Deprecated('Use ``parent`` attribute instead.') + def _getParentStyle(self): + return self._parent + + parentStyle = property(_getParentStyle, _setParent, + doc="DEPRECATED: Use ``parent`` instead") + diff --git a/src/cssutils/css3productions.py b/src/cssutils/css3productions.py deleted file mode 100644 index bfd02fa8ac..0000000000 --- a/src/cssutils/css3productions.py +++ /dev/null @@ -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]) diff --git a/src/cssutils/cssproductions.py b/src/cssutils/cssproductions.py index 90155539a0..c0b0296f6b 100644 --- a/src/cssutils/cssproductions.py +++ b/src/cssutils/cssproductions.py @@ -12,12 +12,12 @@ open issues """ __all__ = ['CSSProductions', 'MACROS', 'PRODUCTIONS'] __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 MACROS = { '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}|\\[^\n\r\f0-9a-f]', 'nmstart': r'[_a-zA-Z]|{nonascii}|{escape}', @@ -71,6 +71,7 @@ PRODUCTIONS = [ ('S', r'{s}+'), # 1st in list of general productions ('URI', r'{U}{R}{L}\({w}({string}|{url}*){w}\)'), ('FUNCTION', r'{ident}\('), + ('UNICODE-RANGE', r'{U}\+[0-9A-Fa-f?]{1,6}(\-[0-9A-Fa-f]{1,6})?'), ('IDENT', r'{ident}'), ('STRING', r'{string}'), ('INVALID', r'{invalid}'), # from CSS2.1 @@ -80,8 +81,9 @@ PRODUCTIONS = [ ('NUMBER', r'{num}'), # valid ony at start so not checked everytime #('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 - #('UNICODE-RANGE', r'[0-9A-F?]{1,6}(\-[0-9A-F]{1,6})?'), #??? ('CDO', r'\<\!\-\-'), ('CDC', r'\-\-\>'), ('INCLUDES', '\~\='), @@ -89,8 +91,6 @@ PRODUCTIONS = [ ('PREFIXMATCH', r'\^\='), ('SUFFIXMATCH', r'\$\='), ('SUBSTRINGMATCH', r'\*\='), - # checked specially if fullsheet is parsed - ('COMMENT', r'{comment}'), #r'\/\*[^*]*\*+([^/][^*]*\*+)*\/'), ('CHAR', r'[^"\']') # MUST always be last ] @@ -110,3 +110,9 @@ class CSSProductions(object): for i, t in enumerate(PRODUCTIONS): setattr(CSSProductions, t[0].replace('-', '_'), t[0]) + + +# may be enabled by settings.set +_DXImageTransform = ('FUNCTION', + r'progid\:DXImageTransform\.Microsoft\..+\(' + ) diff --git a/src/cssutils/errorhandler.py b/src/cssutils/errorhandler.py index 2d1814b6c8..d3f891e76e 100644 --- a/src/cssutils/errorhandler.py +++ b/src/cssutils/errorhandler.py @@ -16,7 +16,7 @@ log """ __all__ = ['ErrorHandler'] __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 import logging @@ -41,6 +41,9 @@ class _ErrorHandler(object): - False: Errors will be written to the log, this is the default behaviour when parsing """ + # may be disabled during setting of known valid items + self.enabled = True + if log: self._log = log else: @@ -74,26 +77,27 @@ class _ErrorHandler(object): handles all calls logs or raises exception """ - line, col = None, None - if token: - if isinstance(token, tuple): - value, line, col = token[1], token[2], token[3] + if self.enabled: + line, col = None, None + if token: + if isinstance(token, tuple): + value, line, col = token[1], token[2], token[3] + else: + value, line, col = token.value, token.line, token.col + msg = u'%s [%s:%s: %s]' % ( + msg, line, col, value) + + if error and self.raiseExceptions and not neverraise: + if isinstance(error, urllib2.HTTPError) or isinstance(error, urllib2.URLError): + raise + elif issubclass(error, xml.dom.DOMException): + error.line = line + error.col = col + # raise error(msg, line, col) + # else: + raise error(msg) 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: - self._logcall(msg) + self._logcall(msg) def setLog(self, log): """set log of errorhandler's log""" diff --git a/src/cssutils/helper.py b/src/cssutils/helper.py index 912d65d5e9..543e3a3bca 100644 --- a/src/cssutils/helper.py +++ b/src/cssutils/helper.py @@ -3,7 +3,10 @@ __docformat__ = 'restructuredtext' __version__ = '$Id: errorhandler.py 1234 2008-05-22 20:26:12Z cthedot $' +import os import re +import sys +import urllib class Deprecated(object): """This is a decorator which can be used to mark functions @@ -48,6 +51,10 @@ def normalize(x): else: return x +def path2url(path): + """Return file URL of `path`""" + return u'file:' + urllib.pathname2url(os.path.abspath(path)) + def pushtoken(token, tokens): """Return new generator starting with token followed by all tokens in ``tokens``""" @@ -107,24 +114,24 @@ def urivalue(uri): else: return uri -def normalnumber(num): - """ - Return normalized number as string. - """ - sign = '' - if num.startswith('-'): - sign = '-' - num = num[1:] - elif num.startswith('+'): - num = num[1:] - - if float(num) == 0.0: - return '0' - else: - if num.find('.') == -1: - return sign + str(int(num)) - else: - a, b = num.split('.') - if not a: - a = '0' - return '%s%s.%s' % (sign, int(a), b) +#def normalnumber(num): +# """ +# Return normalized number as string. +# """ +# sign = '' +# if num.startswith('-'): +# sign = '-' +# num = num[1:] +# elif num.startswith('+'): +# num = num[1:] +# +# if float(num) == 0.0: +# return '0' +# else: +# if num.find('.') == -1: +# return sign + str(int(num)) +# else: +# a, b = num.split('.') +# if not a: +# a = '0' +# return '%s%s.%s' % (sign, int(a), b) diff --git a/src/cssutils/parse.py b/src/cssutils/parse.py index 17c63be7d9..4330f153f7 100644 --- a/src/cssutils/parse.py +++ b/src/cssutils/parse.py @@ -2,9 +2,9 @@ """A validating CSSParser""" __all__ = ['CSSParser'] __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 cssutils import os @@ -124,7 +124,8 @@ class CSSParser(object): """ if not href: # 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(), encoding=encoding, # read returns a str diff --git a/src/cssutils/prodparser.py b/src/cssutils/prodparser.py index 0222be97c6..ec1af95ece 100644 --- a/src/cssutils/prodparser.py +++ b/src/cssutils/prodparser.py @@ -533,105 +533,98 @@ class PreDef(object): types = cssutils.cssproductions.CSSProductions @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): "any CHAR" return Prod(name=name, match=lambda t, v: v == char, toSeq=toSeq, - toStore=toStore, stop=stop, nextSor=nextSor) @staticmethod - def comma(toStore=None): - return PreDef.CHAR(u'comma', u',', toStore=toStore) + def comma(): + return PreDef.char(u'comma', u',') @staticmethod - def dimension(toStore=None, nextSor=False): + def dimension(nextSor=False): return Prod(name=u'dimension', match=lambda t, v: t == PreDef.types.DIMENSION, - toStore=toStore, toSeq=lambda t, tokens: (t[0], cssutils.helper.normalize(t[1])), nextSor=nextSor) @staticmethod - def function(toSeq=None, toStore=None, nextSor=False): + def function(toSeq=None, nextSor=False): return Prod(name=u'function', match=lambda t, v: t == PreDef.types.FUNCTION, toSeq=toSeq, - toStore=toStore, nextSor=nextSor) @staticmethod - def funcEnd(toStore=None, stop=False, nextSor=False): + def funcEnd(stop=False): ")" - return PreDef.CHAR(u'end FUNC ")"', u')', - toStore=toStore, - stop=stop, - nextSor=nextSor) + return PreDef.char(u'end FUNC ")"', u')', + stop=stop) @staticmethod - def ident(toStore=None, nextSor=False): + def ident(nextSor=False): return Prod(name=u'ident', match=lambda t, v: t == PreDef.types.IDENT, - toStore=toStore, nextSor=nextSor) @staticmethod - def number(toStore=None, nextSor=False): + def number(nextSor=False): return Prod(name=u'number', match=lambda t, v: t == PreDef.types.NUMBER, - toStore=toStore, nextSor=nextSor) @staticmethod - def string(toStore=None, nextSor=False): + def string(nextSor=False): "string delimiters are removed by default" return Prod(name=u'string', match=lambda t, v: t == PreDef.types.STRING, - toStore=toStore, toSeq=lambda t, tokens: (t[0], cssutils.helper.stringvalue(t[1])), nextSor=nextSor) @staticmethod - def percentage(toStore=None, nextSor=False): + def percentage(nextSor=False): return Prod(name=u'percentage', match=lambda t, v: t == PreDef.types.PERCENTAGE, - toStore=toStore, nextSor=nextSor) @staticmethod - def S(name=u'whitespace', optional=True, toSeq=None, toStore=None, nextSor=False, - mayEnd=False): - return Prod(name=name, + def S(): + return Prod(name=u'whitespace', match=lambda t, v: t == PreDef.types.S, - optional=optional, - toSeq=toSeq, - toStore=toStore, - mayEnd=mayEnd) + mayEnd=True) @staticmethod - def unary(optional=True, toStore=None): + def unary(): "+ or -" return Prod(name=u'unary +-', match=lambda t, v: v in (u'+', u'-'), - optional=optional, - toStore=toStore) + optional=True) @staticmethod - def uri(toStore=None, nextSor=False): + def uri(nextSor=False): "'url(' and ')' are removed and URI is stripped" return Prod(name=u'URI', match=lambda t, v: t == PreDef.types.URI, - toStore=toStore, toSeq=lambda t, tokens: (t[0], cssutils.helper.urivalue(t[1])), nextSor=nextSor) @staticmethod - def hexcolor(toStore=None, toSeq=None, nextSor=False): + def hexcolor(nextSor=False): + "#123456" return Prod(name='HEX color', - match=lambda t, v: t == PreDef.types.HASH and - len(v) == 4 or len(v) == 7, - toStore=toStore, - toSeq=toSeq, + match=lambda t, v: t == PreDef.types.HASH and ( + len(v) == 4 or len(v) == 7), + nextSor=nextSor + ) + + @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 ) diff --git a/src/cssutils/profiles.py b/src/cssutils/profiles.py index 78fb43468d..70e2ab3bfc 100644 --- a/src/cssutils/profiles.py +++ b/src/cssutils/profiles.py @@ -43,8 +43,10 @@ class Profiles(object): macros. """ 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_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' _TOKEN_MACROS = { @@ -58,6 +60,7 @@ class Profiles(object): 'int': r'[-]?\d+', 'nmchar': r'[\w-]|{nonascii}|{escape}', 'num': r'[-]?\d+|[-]?\d*\.\d+', + 'positivenum': r'\d+|[-]?\d*\.\d+', 'number': r'{num}', 'string': r'{string1}|{string2}', 'string1': r'"(\\\"|[^\"])*"', @@ -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}\)', 'integer': r'{int}', 'length': r'0|{num}(em|ex|px|in|cm|mm|pt|pc)', + 'positivelength': r'0|{positivenum}(em|ex|px|in|cm|mm|pt|pc)', 'angle': r'0|{num}(deg|grad|rad)', 'time': r'0|{num}m?s', 'frequency': r'0|{num}k?Hz', @@ -97,6 +101,16 @@ class Profiles(object): self.addProfile(self.CSS3_COLOR, properties[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, properties[self.CSS3_PAGED_MEDIA], macros[self.CSS3_PAGED_MEDIA]) @@ -132,7 +146,7 @@ class Profiles(object): def _getDefaultProfiles(self): "If not explicitly set same as Profiles.profiles but in reverse order." if not self._defaultProfiles: - return self.profiles#list(reversed(self.profiles)) + return self.profiles else: return self._defaultProfiles @@ -338,12 +352,12 @@ macros[Profiles.CSS_LEVEL_2] = { 'shape': r'rect\(({w}({length}|auto}){w},){3}{w}({length}|auto){w}\)', 'counter': r'counter\({w}{identifier}{w}(?:,{w}{list-style-type}{w})?\)', 'identifier': r'{ident}', - 'family-name': r'{string}|{identifier}', + 'family-name': r'{string}|{identifier}({w}{identifier})*', 'generic-family': r'serif|sans-serif|cursive|fantasy|monospace', 'absolute-size': r'(x?x-)?(small|large)|medium', 'relative-size': r'smaller|larger', '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-variant': r'normal|small-caps|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'] } properties[Profiles.CSS3_BOX] = { - 'overflow': '{overflow}\s?{overflow}?|inherit', + 'overflow': '{overflow}{w}{overflow}?|inherit', 'overflow-x': '{overflow}|inherit', 'overflow-y': '{overflow}|inherit' } @@ -515,6 +529,28 @@ properties[Profiles.CSS3_COLOR] = { 'opacity': r'{num}|inherit' } +# CSS Fonts Module Level 3 http://www.w3.org/TR/css3-fonts/ +macros[Profiles.CSS3_FONTS] = { + 'family-name': r'{string}|{ident}', # 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 macros[Profiles.CSS3_PAGED_MEDIA] = { 'pagesize': 'a5|a4|a3|b5|b4|letter|legal|ledger', diff --git a/src/cssutils/settings.py b/src/cssutils/settings.py new file mode 100644 index 0000000000..755d4eb6ba --- /dev/null +++ b/src/cssutils/settings.py @@ -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) + \ No newline at end of file diff --git a/src/cssutils/tokenize2.py b/src/cssutils/tokenize2.py index 0abd7c7677..4785ee97a5 100644 --- a/src/cssutils/tokenize2.py +++ b/src/cssutils/tokenize2.py @@ -4,7 +4,7 @@ """ __all__ = ['Tokenizer', 'CSSProductions'] __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 helper import normalize @@ -147,7 +147,8 @@ class Tokenizer(object): break 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 # but do not _normalize (?) value = self.unicodesub(_repl, found) @@ -166,7 +167,6 @@ class Tokenizer(object): name = self._atkeywords.get(_normalize(found), 'ATKEYWORD') value = found # should not contain unicode escape (?) - yield (name, value, line, col) text = text[len(found):] nls = found.count(self._linesep) diff --git a/src/cssutils/util.py b/src/cssutils/util.py index 6f8ba3bc5e..21bacb1abb 100644 --- a/src/cssutils/util.py +++ b/src/cssutils/util.py @@ -2,7 +2,7 @@ """ __all__ = [] __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 itertools import ifilter @@ -663,6 +663,9 @@ class _Namespaces(object): self.parentStyleSheet = parentStyleSheet self._log = log + def __repr__(self): + return "%r" % self.namespaces + def __contains__(self, prefix): return prefix in self.namespaces