From 1d5aaaf83063055f32f2921e53681f5cf31bbc9b Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Wed, 18 Feb 2009 09:23:50 -0800 Subject: [PATCH] Update cssutils to 0.9.6a1. Fixes #1869 (TypeError: 'NoneType' object is not iterable / [ERROR] CSSColor: No match in choice: ('HASH', u'#FFBCCB67B', 20, 19)) --- src/cssutils/__init__.py | 87 +- src/cssutils/_fetch.py | 40 + src/cssutils/_fetchgae.py | 68 + src/cssutils/codec.py | 13 +- src/cssutils/css/__init__.py | 6 +- src/cssutils/css/csscharsetrule.py | 105 +- src/cssutils/css/csscomment.py | 72 +- src/cssutils/css/cssfontfacerule.py | 91 +- src/cssutils/css/cssimportrule.py | 129 +- src/cssutils/css/cssmediarule.py | 150 +- src/cssutils/css/cssnamespacerule.py | 104 +- src/cssutils/css/csspagerule.py | 186 ++- src/cssutils/css/cssproperties.py | 283 +--- src/cssutils/css/cssrule.py | 123 +- src/cssutils/css/cssrulelist.py | 26 +- src/cssutils/css/cssstyledeclaration.py | 369 +++-- src/cssutils/css/cssstylerule.py | 113 +- src/cssutils/css/cssstylesheet.py | 233 ++- src/cssutils/css/cssunknownrule.py | 81 +- src/cssutils/css/cssvalue.py | 1492 ++++++++------------ src/cssutils/css/property.py | 262 ++-- src/cssutils/css/selector.py | 136 +- src/cssutils/css/selectorlist.py | 209 ++- src/cssutils/cssproductions.py | 47 +- src/cssutils/errorhandler.py | 19 +- src/cssutils/helper.py | 84 +- src/cssutils/parse.py | 111 +- src/cssutils/prodparser.py | 595 +++++--- src/cssutils/profiles.py | 255 +++- src/cssutils/script.py | 66 +- src/cssutils/serialize.py | 263 ++-- src/cssutils/stylesheets/__init__.py | 11 +- src/cssutils/stylesheets/medialist.py | 166 +-- src/cssutils/stylesheets/mediaquery.py | 112 +- src/cssutils/stylesheets/stylesheet.py | 55 +- src/cssutils/stylesheets/stylesheetlist.py | 23 +- src/cssutils/tokenize2.py | 12 +- src/cssutils/util.py | 252 ++-- 38 files changed, 3003 insertions(+), 3446 deletions(-) create mode 100644 src/cssutils/_fetch.py create mode 100644 src/cssutils/_fetchgae.py diff --git a/src/cssutils/__init__.py b/src/cssutils/__init__.py index 901d6acf75..846ab3b397 100644 --- a/src/cssutils/__init__.py +++ b/src/cssutils/__init__.py @@ -1,7 +1,7 @@ #!/usr/bin/env python """cssutils - CSS Cascading Style Sheets library for Python - Copyright (C) 2004-2008 Christof Hoeke + Copyright (C) 2004-2009 Christof Hoeke cssutils is free software: you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by @@ -63,18 +63,18 @@ Usage may be:: >>> sheet = parser.parseString(u'a { color: red}') >>> print sheet.cssText a { - color: red - } + color: red + } """ __all__ = ['css', 'stylesheets', 'CSSParser', 'CSSSerializer'] __docformat__ = 'restructuredtext' __author__ = 'Christof Hoeke with contributions by Walter Doerwald' -__date__ = '$LastChangedDate:: 2008-08-11 11:11:23 -0700 #$:' +__date__ = '$LastChangedDate:: 2009-02-16 12:05:02 -0800 #$:' -VERSION = '0.9.5.1' +VERSION = '0.9.6a1' -__version__ = '%s $Id: __init__.py 1426 2008-08-11 18:11:23Z cthedot $' % VERSION +__version__ = '%s $Id: __init__.py 1669 2009-02-16 20:05:02Z cthedot $' % VERSION import codec import xml.dom @@ -84,20 +84,19 @@ from helper import Deprecated import errorhandler log = errorhandler.ErrorHandler() -import util import css import stylesheets +import util from parse import CSSParser from serialize import CSSSerializer ser = CSSSerializer() -# used by Selector defining namespace prefix '*' +# used by Selector defining namespace prefix '*' _ANYNS = -1 class DOMImplementationCSS(object): - """ - This interface allows the DOM user to create a CSSStyleSheet + """This interface allows the DOM user to create a CSSStyleSheet outside the context of a document. There is no way to associate the new CSSStyleSheet with a document in DOM Level 2. @@ -166,21 +165,21 @@ parse.__doc__ = CSSParser.parse.__doc__ # set "ser", default serializer def setSerializer(serializer): - """ - sets the global serializer used by all class in cssutils - """ + """Set the global serializer used by all class in cssutils.""" global ser ser = serializer def getUrls(sheet): - """ - Utility function to get all ``url(urlstring)`` values in - ``CSSImportRules`` and ``CSSStyleDeclaration`` objects (properties) - of given CSSStyleSheet ``sheet``. + """Retrieve all ``url(urlstring)`` values (in e.g. + :class:`cssutils.css.CSSImportRule` or :class:`cssutils.css.CSSValue` + objects of given `sheet`. - This function is a generator. The url values exclude ``url(`` and ``)`` - and surrounding single or double quotes. + :param sheet: + :class:`cssutils.css.CSSStyleSheet` object whose URLs are yielded + + This function is a generator. The generated URL values exclude ``url(`` and + ``)`` and surrounding single or double quotes. """ for importrule in (r for r in sheet if r.type == r.IMPORT_RULE): yield importrule.href @@ -211,16 +210,17 @@ def getUrls(sheet): u = getUrl(v) if u is not None: yield u - -def replaceUrls(sheet, replacer): - """ - Utility function to replace all ``url(urlstring)`` values in - ``CSSImportRules`` and ``CSSStyleDeclaration`` objects (properties) - of given CSSStyleSheet ``sheet``. - ``replacer`` must be a function which is called with a single - argument ``urlstring`` which is the current value of url() - excluding ``url(`` and ``)`` and surrounding single or double quotes. +def replaceUrls(sheet, replacer): + """Replace all URLs in :class:`cssutils.css.CSSImportRule` or + :class:`cssutils.css.CSSValue` objects of given `sheet`. + + :param sheet: + :class:`cssutils.css.CSSStyleSheet` which is changed + :param replacer: + a function which is called with a single argument `urlstring` which is + the current value of each url() excluding ``url(`` and ``)`` and + surrounding single or double quotes. """ for importrule in (r for r in sheet if r.type == r.IMPORT_RULE): importrule.href = replacer(importrule.href) @@ -249,6 +249,37 @@ def replaceUrls(sheet, replacer): elif v.CSS_PRIMITIVE_VALUE == v.cssValueType: setProperty(v) +def resolveImports(sheet, target=None): + """Recurcively combine all rules in given `sheet` into a `target` sheet. + + :param sheet: + in this given :class:`cssutils.css.CSSStyleSheet` all import rules are + resolved and added to a resulting *flat* sheet. + :param target: + A :class:`cssutils.css.CSSStyleSheet` object which will be the resulting + *flat* sheet if given + :returns: given `target` or a new :class:`cssutils.css.CSSStyleSheet` object + """ + if not target: + target = css.CSSStyleSheet() + + #target.add(css.CSSComment(cssText=u'/* START %s */' % sheet.href)) + for rule in sheet.cssRules: + if rule.type == rule.CHARSET_RULE: + pass + elif rule.type == rule.IMPORT_RULE: + log.info(u'Processing @import %r' % rule.href, neverraise=True) + if rule.styleSheet: + target.add(css.CSSComment(cssText=u'/* START @import "%s" */' % rule.href)) + resolveImports(rule.styleSheet, target) + target.add(css.CSSComment(cssText=u'/* END "%s" */' % rule.href)) + else: + log.error(u'Cannot get referenced stylesheet %r' % + rule.href, neverraise=True) + target.add(rule) + else: + target.add(rule) + return target if __name__ == '__main__': print __doc__ diff --git a/src/cssutils/_fetch.py b/src/cssutils/_fetch.py new file mode 100644 index 0000000000..75b3e525fb --- /dev/null +++ b/src/cssutils/_fetch.py @@ -0,0 +1,40 @@ +"""Default URL reading functions""" +__all__ = ['_defaultFetcher', '_readUrl'] +__docformat__ = 'restructuredtext' +__version__ = '$Id: tokenize2.py 1547 2008-12-10 20:42:26Z cthedot $' + +import encutils +import errorhandler +import urllib2 +import util + +log = errorhandler.ErrorHandler() + +def _defaultFetcher(url): + """Retrieve data from ``url``. cssutils default implementation of fetch + URL function. + + Returns ``(encoding, string)`` or ``None`` + """ + try: + res = urllib2.urlopen(url) + except OSError, e: + # e.g if file URL and not found + log.warn(e, error=OSError) + except (OSError, ValueError), e: + # invalid url, e.g. "1" + log.warn(u'ValueError, %s' % e.args[0], error=ValueError) + except urllib2.HTTPError, e: + # http error, e.g. 404, e can be raised + log.warn(u'HTTPError opening url=%r: %s %s' % + (url, e.code, e.msg), error=e) + except urllib2.URLError, e: + # URLError like mailto: or other IO errors, e can be raised + log.warn(u'URLError, %s' % e.reason, error=e) + else: + if res: + mimeType, encoding = encutils.getHTTPInfo(res) + if mimeType != u'text/css': + log.error(u'Expected "text/css" mime type for url=%r but found: %r' % + (url, mimeType), error=ValueError) + return encoding, res.read() diff --git a/src/cssutils/_fetchgae.py b/src/cssutils/_fetchgae.py new file mode 100644 index 0000000000..4250e8bc8c --- /dev/null +++ b/src/cssutils/_fetchgae.py @@ -0,0 +1,68 @@ +"""GAE specific URL reading functions""" +__all__ = ['_defaultFetcher', '_readUrl'] +__docformat__ = 'restructuredtext' +__version__ = '$Id: tokenize2.py 1547 2008-12-10 20:42:26Z cthedot $' + +# raises ImportError of not on GAE +from google.appengine.api import urlfetch +import cgi +import errorhandler +import util + +log = errorhandler.ErrorHandler() + +def _defaultFetcher(url): + """ + uses GoogleAppEngine (GAE) + fetch(url, payload=None, method=GET, headers={}, allow_truncated=False) + + Response + content + The body content of the response. + content_was_truncated + True if the allow_truncated parameter to fetch() was True and + the response exceeded the maximum response size. In this case, + the content attribute contains the truncated response. + status_code + The HTTP status code. + headers + The HTTP response headers, as a mapping of names to values. + + Exceptions + exception InvalidURLError() + The URL of the request was not a valid URL, or it used an + unsupported method. Only http and https URLs are supported. + exception DownloadError() + There was an error retrieving the data. + + This exception is not raised if the server returns an HTTP + error code: In that case, the response data comes back intact, + including the error code. + + exception ResponseTooLargeError() + The response data exceeded the maximum allowed size, and the + allow_truncated parameter passed to fetch() was False. + """ + #from google.appengine.api import urlfetch + try: + r = urlfetch.fetch(url, method=urlfetch.GET) + except urlfetch.Error, e: + log.warn(u'Error opening url=%r: %s' % (url, e), + error=IOError) + else: + if r.status_code == 200: + # find mimetype and encoding + mimetype = 'application/octet-stream' + try: + mimetype, params = cgi.parse_header(r.headers['content-type']) + encoding = params['charset'] + except KeyError: + encoding = None + if mimetype != u'text/css': + log.error(u'Expected "text/css" mime type for url %r but found: %r' % + (url, mimetype), error=ValueError) + return encoding, r.content + else: + # TODO: 301 etc + log.warn(u'Error opening url=%r: HTTP status %s' % + (url, r.status_code), error=IOError) diff --git a/src/cssutils/codec.py b/src/cssutils/codec.py index 72fbbaddec..c6e9823ef6 100644 --- a/src/cssutils/codec.py +++ b/src/cssutils/codec.py @@ -4,7 +4,8 @@ __docformat__ = 'restructuredtext' __author__ = 'Walter Doerwald' __version__ = '$Id: util.py 1114 2008-03-05 13:22:59Z cthedot $' -import codecs, marshal +import codecs +import marshal # We're using bits to store all possible candidate encodings (or variants, i.e. # we have two bits for the variants of UTF-16 and two for the @@ -26,17 +27,17 @@ import codecs, marshal def detectencoding_str(input, final=False): """ Detect the encoding of the byte string ``input``, which contains the - beginning of a CSS file. This function returs the detected encoding (or + beginning of a CSS file. This function returns the detected encoding (or ``None`` if it hasn't got enough data), and a flag that indicates whether - to encoding has been detected explicitely or implicitely. To detect the + that encoding has been detected explicitely or implicitely. To detect the encoding the first few bytes are used (or if ``input`` is ASCII compatible and starts with a charset rule the encoding name from the rule). "Explicit" detection means that the bytes start with a BOM or a charset rule. If the encoding can't be detected yet, ``None`` is returned as the encoding. - ``final`` specifies whether more data is available in later calls or not. - If ``final`` is true, ``detectencoding_str()`` will never return ``None`` - as the encoding. + ``final`` specifies whether more data will be available in later calls or + not. If ``final`` is true, ``detectencoding_str()`` will never return + ``None`` as the encoding. """ # A bit for every candidate diff --git a/src/cssutils/css/__init__.py b/src/cssutils/css/__init__.py index b4ee3b8d9c..3cbc6c61eb 100644 --- a/src/cssutils/css/__init__.py +++ b/src/cssutils/css/__init__.py @@ -1,5 +1,4 @@ -""" -Document Object Model Level 2 CSS +"""Implements Document Object Model Level 2 CSS http://www.w3.org/TR/2000/PR-DOM-Level-2-Style-20000927/css.html currently implemented @@ -43,7 +42,7 @@ __all__ = [ 'CSSValue', 'CSSPrimitiveValue', 'CSSValueList' ] __docformat__ = 'restructuredtext' -__version__ = '$Id: __init__.py 1116 2008-03-05 13:52:23Z cthedot $' +__version__ = '$Id: __init__.py 1610 2009-01-03 21:07:57Z cthedot $' from cssstylesheet import * from cssrulelist import * @@ -60,4 +59,5 @@ from cssunknownrule import * from selector import * from selectorlist import * from cssstyledeclaration import * +from property import * from cssvalue import * diff --git a/src/cssutils/css/csscharsetrule.py b/src/cssutils/css/csscharsetrule.py index 4594dd112d..2ef2f23e04 100644 --- a/src/cssutils/css/csscharsetrule.py +++ b/src/cssutils/css/csscharsetrule.py @@ -1,16 +1,12 @@ -"""CSSCharsetRule implements DOM Level 2 CSS CSSCharsetRule. - -TODO: - - check encoding syntax and not codecs.lookup? -""" +"""CSSCharsetRule implements DOM Level 2 CSS CSSCharsetRule.""" __all__ = ['CSSCharsetRule'] __docformat__ = 'restructuredtext' -__version__ = '$Id: csscharsetrule.py 1170 2008-03-20 17:42:07Z cthedot $' +__version__ = '$Id: csscharsetrule.py 1605 2009-01-03 18:27:32Z cthedot $' import codecs -import xml.dom import cssrule import cssutils +import xml.dom class CSSCharsetRule(cssrule.CSSRule): """ @@ -28,35 +24,26 @@ class CSSCharsetRule(cssrule.CSSRule): character encoding information e.g. in an HTTP header, has priority (see CSS document representation) but this is not reflected in the CSSCharsetRule. + + This rule is not really needed anymore as setting + :attr:`CSSStyleSheet.encoding` is much easier. - Properties - ========== - cssText: of type DOMString - The parsable textual representation of this rule - encoding: of type DOMString - The encoding information used in this @charset rule. + Format:: - Inherits properties from CSSRule + charsetrule: + CHARSET_SYM S* STRING S* ';' - Format - ====== - charsetrule: - CHARSET_SYM S* STRING S* ';' - - BUT: Only valid format is: + BUT: Only valid format is (single space, double quotes!):: + @charset "ENCODING"; """ - type = property(lambda self: cssrule.CSSRule.CHARSET_RULE) - def __init__(self, encoding=None, parentRule=None, parentStyleSheet=None, readonly=False): """ - encoding: + :param encoding: a valid character encoding - readonly: + :param readonly: defaults to False, not used yet - - if readonly allows setting of properties in constructor only """ super(CSSCharsetRule, self).__init__(parentRule=parentRule, parentStyleSheet=parentStyleSheet) @@ -67,25 +54,34 @@ class CSSCharsetRule(cssrule.CSSRule): self._readonly = readonly + def __repr__(self): + return "cssutils.css.%s(encoding=%r)" % ( + self.__class__.__name__, self.encoding) + + def __str__(self): + return "" % ( + self.__class__.__name__, self.encoding, id(self)) + def _getCssText(self): - """returns serialized property cssText""" + """The parsable textual representation.""" return cssutils.ser.do_CSSCharsetRule(self) def _setCssText(self, cssText): """ - DOMException on setting - - - SYNTAX_ERR: (self) - Raised if the specified CSS string value has a syntax error and - is unparsable. - - INVALID_MODIFICATION_ERR: (self) - Raised if the specified CSS string value represents a different - type of rule than the current one. - - HIERARCHY_REQUEST_ERR: (CSSStylesheet) - Raised if the rule cannot be inserted at this point in the - style sheet. - - NO_MODIFICATION_ALLOWED_ERR: (CSSRule) - Raised if the rule is readonly. + :param cssText: + A parsable DOMString. + :exceptions: + - :exc:`~xml.dom.SyntaxErr`: + Raised if the specified CSS string value has a syntax error and + is unparsable. + - :exc:`~xml.dom.InvalidModificationErr`: + Raised if the specified CSS string value represents a different + type of rule than the current one. + - :exc:`~xml.dom.HierarchyRequestErr`: + Raised if the rule cannot be inserted at this point in the + style sheet. + - :exc:`~xml.dom.NoModificationAllowedErr`: + Raised if the rule is readonly. """ super(CSSCharsetRule, self)._setCssText(cssText) @@ -120,14 +116,15 @@ class CSSCharsetRule(cssrule.CSSRule): def _setEncoding(self, encoding): """ - DOMException on setting - - - NO_MODIFICATION_ALLOWED_ERR: (CSSRule) - Raised if this encoding rule is readonly. - - SYNTAX_ERR: (self) - Raised if the specified encoding value has a syntax error and - is unparsable. - Currently only valid Python encodings are allowed. + :param encoding: + a valid encoding to be used. Currently only valid Python encodings + are allowed. + :exceptions: + - :exc:`~xml.dom.NoModificationAllowedErr`: + Raised if this encoding rule is readonly. + - :exc:`~xml.dom.SyntaxErr`: + Raised if the specified encoding value has a syntax error and + is unparsable. """ self._checkReadonly() tokenizer = self._tokenize2(encoding) @@ -154,12 +151,8 @@ class CSSCharsetRule(cssrule.CSSRule): encoding = property(lambda self: self._encoding, _setEncoding, doc="(DOM)The encoding information used in this @charset rule.") + type = property(lambda self: self.CHARSET_RULE, + doc="The type of this rule, as defined by a CSSRule " + "type constant.") + wellformed = property(lambda self: bool(self.encoding)) - - def __repr__(self): - return "cssutils.css.%s(encoding=%r)" % ( - self.__class__.__name__, self.encoding) - - def __str__(self): - return "" % ( - self.__class__.__name__, self.encoding, id(self)) diff --git a/src/cssutils/css/csscomment.py b/src/cssutils/css/csscomment.py index e69c2c77cc..179027f437 100644 --- a/src/cssutils/css/csscomment.py +++ b/src/cssutils/css/csscomment.py @@ -1,36 +1,24 @@ """CSSComment is not defined in DOM Level 2 at all but a cssutils defined class only. -Implements CSSRule which is also extended for a CSSComment rule type + +Implements CSSRule which is also extended for a CSSComment rule type. """ __all__ = ['CSSComment'] __docformat__ = 'restructuredtext' -__version__ = '$Id: csscomment.py 1170 2008-03-20 17:42:07Z cthedot $' +__version__ = '$Id: csscomment.py 1638 2009-01-13 20:39:33Z cthedot $' -import xml.dom import cssrule import cssutils +import xml.dom class CSSComment(cssrule.CSSRule): """ - (cssutils) a CSS comment + Represents a CSS comment (cssutils only). - Properties - ========== - cssText: of type DOMString - The comment text including comment delimiters - - Inherits properties from CSSRule - - Format - ====== - :: + Format:: /*...*/ """ - type = property(lambda self: cssrule.CSSRule.COMMENT) # value = -1 - # constant but needed: - wellformed = True - def __init__(self, cssText=None, parentRule=None, parentStyleSheet=None, readonly=False): super(CSSComment, self).__init__(parentRule=parentRule, @@ -42,28 +30,33 @@ class CSSComment(cssrule.CSSRule): self._readonly = readonly + def __repr__(self): + return "cssutils.css.%s(cssText=%r)" % ( + self.__class__.__name__, self.cssText) + + def __str__(self): + return "" % ( + self.__class__.__name__, self.cssText, id(self)) + def _getCssText(self): - """returns serialized property cssText""" + """Return serialized property cssText.""" return cssutils.ser.do_CSSComment(self) def _setCssText(self, cssText): """ - cssText + :param cssText: textual text to set or tokenlist which is not tokenized anymore. May also be a single token for this rule - parser - if called from cssparser directly this is Parser instance - DOMException on setting - - - SYNTAX_ERR: (self) - Raised if the specified CSS string value has a syntax error and - is unparsable. - - INVALID_MODIFICATION_ERR: (self) - Raised if the specified CSS string value represents a different - type of rule than the current one. - - NO_MODIFICATION_ALLOWED_ERR: (CSSRule) - Raised if the rule is readonly. + :exceptions: + - :exc:`~xml.dom.SyntaxErr`: + Raised if the specified CSS string value has a syntax error and + is unparsable. + - :exc:`~xml.dom.InvalidModificationErr`: + Raised if the specified CSS string value represents a different + type of rule than the current one. + - :exc:`~xml.dom.NoModificationAllowedErr`: + Raised if the rule is readonly. """ super(CSSComment, self)._setCssText(cssText) tokenizer = self._tokenize2(cssText) @@ -81,12 +74,11 @@ class CSSComment(cssrule.CSSRule): self._cssText = self._tokenvalue(commenttoken) cssText = property(_getCssText, _setCssText, - doc=u"(cssutils) Textual representation of this comment") + doc=u"The parsable textual representation of this rule.") - def __repr__(self): - return "cssutils.css.%s(cssText=%r)" % ( - self.__class__.__name__, self.cssText) - - def __str__(self): - return "" % ( - self.__class__.__name__, self.cssText, id(self)) + type = property(lambda self: self.COMMENT, + doc="The type of this rule, as defined by a CSSRule " + "type constant.") + + # constant but needed: + wellformed = property(lambda self: True) diff --git a/src/cssutils/css/cssfontfacerule.py b/src/cssutils/css/cssfontfacerule.py index 1f839c6779..76bdc8aeb0 100644 --- a/src/cssutils/css/cssfontfacerule.py +++ b/src/cssutils/css/cssfontfacerule.py @@ -2,12 +2,12 @@ """ __all__ = ['CSSFontFaceRule'] __docformat__ = 'restructuredtext' -__version__ = '$Id: cssfontfacerule.py 1284 2008-06-05 16:29:17Z cthedot $' +__version__ = '$Id: cssfontfacerule.py 1638 2009-01-13 20:39:33Z cthedot $' -import xml.dom +from cssstyledeclaration import CSSStyleDeclaration import cssrule import cssutils -from cssstyledeclaration import CSSStyleDeclaration +import xml.dom class CSSFontFaceRule(cssrule.CSSRule): """ @@ -15,36 +15,19 @@ class CSSFontFaceRule(cssrule.CSSRule): style sheet. The @font-face rule is used to hold a set of font descriptions. - Properties - ========== - atkeyword (cssutils only) - the literal keyword used - cssText: of type DOMString - The parsable textual representation of this rule - style: of type CSSStyleDeclaration - The declaration-block of this rule. - - Inherits properties from CSSRule - - Format - ====== - :: + Format:: font_face : FONT_FACE_SYM S* '{' S* declaration [ ';' S* declaration ]* '}' S* ; """ - type = property(lambda self: cssrule.CSSRule.FONT_FACE_RULE) - # constant but needed: - wellformed = True - def __init__(self, style=None, parentRule=None, parentStyleSheet=None, readonly=False): """ - if readonly allows setting of properties in constructor only + If readonly allows setting of properties in constructor only. - style + :param style: CSSStyleDeclaration for this CSSStyleRule """ super(CSSFontFaceRule, self).__init__(parentRule=parentRule, @@ -57,27 +40,32 @@ class CSSFontFaceRule(cssrule.CSSRule): 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)) + def _getCssText(self): - """ - returns serialized property cssText - """ + """Return serialized property cssText.""" return cssutils.ser.do_CSSFontFaceRule(self) def _setCssText(self, cssText): """ - DOMException on setting - - - SYNTAX_ERR: (self, StyleDeclaration) - Raised if the specified CSS string value has a syntax error and - is unparsable. - - INVALID_MODIFICATION_ERR: (self) - Raised if the specified CSS string value represents a different - type of rule than the current one. - - HIERARCHY_REQUEST_ERR: (CSSStylesheet) - Raised if the rule cannot be inserted at this point in the - style sheet. - - NO_MODIFICATION_ALLOWED_ERR: (CSSRule) - Raised if the rule is readonly. + :exceptions: + - :exc:`~xml.dom.SyntaxErr`: + Raised if the specified CSS string value has a syntax error and + is unparsable. + - :exc:`~xml.dom.InvalidModificationErr`: + Raised if the specified CSS string value represents a different + type of rule than the current one. + - :exc:`~xml.dom.HierarchyRequestErr`: + Raised if the rule cannot be inserted at this point in the + style sheet. + - :exc:`~xml.dom.NoModificationAllowedErr`: + Raised if the rule is readonly. """ super(CSSFontFaceRule, self)._setCssText(cssText) @@ -135,15 +123,12 @@ class CSSFontFaceRule(cssrule.CSSRule): self._setSeq(newseq) # contains (probably comments) upto { only cssText = property(_getCssText, _setCssText, - doc="(DOM) The parsable textual representation of the rule.") - - def _getStyle(self): - return self._style + doc="(DOM) The parsable textual representation of this rule.") def _setStyle(self, style): """ - style - StyleDeclaration or string + :param style: + a CSSStyleDeclaration or string """ self._checkReadonly() if isinstance(style, basestring): @@ -151,13 +136,13 @@ class CSSFontFaceRule(cssrule.CSSRule): else: self._style._seq = style.seq - style = property(_getStyle, _setStyle, - doc="(DOM) The declaration-block of this rule set.") + style = property(lambda self: self._style, _setStyle, + doc="(DOM) The declaration-block of this rule set, " + "a :class:`~cssutils.css.CSSStyleDeclaration`.") - def __repr__(self): - return "cssutils.css.%s(style=%r)" % ( - self.__class__.__name__, self.style.cssText) + type = property(lambda self: self.FONT_FACE_RULE, + doc="The type of this rule, as defined by a CSSRule " + "type constant.") - def __str__(self): - return "" % ( - self.__class__.__name__, self.style.cssText, id(self)) + # constant but needed: + wellformed = property(lambda self: True) diff --git a/src/cssutils/css/cssimportrule.py b/src/cssutils/css/cssimportrule.py index fde1b9b303..d77a2f195f 100644 --- a/src/cssutils/css/cssimportrule.py +++ b/src/cssutils/css/cssimportrule.py @@ -1,60 +1,30 @@ -"""CSSImportRule implements DOM Level 2 CSS CSSImportRule. - -plus: - -``name`` property - http://www.w3.org/TR/css3-cascade/#cascading +"""CSSImportRule implements DOM Level 2 CSS CSSImportRule plus the +``name`` property from http://www.w3.org/TR/css3-cascade/#cascading. """ __all__ = ['CSSImportRule'] __docformat__ = 'restructuredtext' -__version__ = '$Id: cssimportrule.py 1401 2008-07-29 21:07:54Z cthedot $' +__version__ = '$Id: cssimportrule.py 1638 2009-01-13 20:39:33Z cthedot $' +import cssrule +import cssutils import os import urllib import urlparse import xml.dom -import cssrule -import cssutils class CSSImportRule(cssrule.CSSRule): """ Represents an @import rule within a CSS style sheet. The @import rule is used to import style rules from other style sheets. - Properties - ========== - atkeyword: (cssutils only) - the literal keyword used - cssText: of type DOMString - The parsable textual representation of this rule - href: of type DOMString, (DOM readonly, cssutils also writable) - The location of the style sheet to be imported. The attribute will - not contain the url(...) specifier around the URI. - hreftype: 'uri' (serializer default) or 'string' (cssutils only) - The original type of href, not really relevant as it may be - reconfigured in the serializer but it is kept anyway - media: of type stylesheets::MediaList (DOM readonly) - A list of media types for this rule of type MediaList. - name: - An optional name used for cascading - styleSheet: of type CSSStyleSheet (DOM readonly) - The style sheet referred to by this rule. The value of this - attribute is None if the style sheet has not yet been loaded or if - it will not be loaded (e.g. if the stylesheet is for a media type - not supported by the user agent). + Format:: - Inherits properties from CSSRule - - Format - ====== - import - : IMPORT_SYM S* - [STRING|URI] S* [ medium [ COMMA S* medium]* ]? S* STRING? S* ';' S* - ; + import + : IMPORT_SYM S* + [STRING|URI] S* [ medium [ COMMA S* medium]* ]? S* STRING? S* ';' S* + ; """ - type = property(lambda self: cssrule.CSSRule.IMPORT_RULE) - def __init__(self, href=None, mediaText=u'all', name=None, parentRule=None, parentStyleSheet=None, readonly=False): """ @@ -90,30 +60,44 @@ class CSSImportRule(cssrule.CSSRule): self._setSeq(seq) self._readonly = readonly + def __repr__(self): + if self._usemedia: + mediaText = self.media.mediaText + else: + mediaText = None + return "cssutils.css.%s(href=%r, mediaText=%r, name=%r)" % ( + self.__class__.__name__, + self.href, self.media.mediaText, self.name) + + def __str__(self): + if self._usemedia: + mediaText = self.media.mediaText + else: + mediaText = None + return "" % ( + self.__class__.__name__, self.href, mediaText, self.name, id(self)) + _usemedia = property(lambda self: self.media.mediaText not in (u'', u'all'), doc="if self._media is used (or simply empty)") def _getCssText(self): - """ - returns serialized property cssText - """ + """Return serialized property cssText.""" return cssutils.ser.do_CSSImportRule(self) def _setCssText(self, cssText): """ - DOMException on setting - - - HIERARCHY_REQUEST_ERR: (CSSStylesheet) - Raised if the rule cannot be inserted at this point in the - style sheet. - - INVALID_MODIFICATION_ERR: (self) - Raised if the specified CSS string value represents a different - type of rule than the current one. - - NO_MODIFICATION_ALLOWED_ERR: (CSSRule) - Raised if the rule is readonly. - - SYNTAX_ERR: (self) - Raised if the specified CSS string value has a syntax error and - is unparsable. + :exceptions: + - :exc:`~xml.dom.HierarchyRequestErr`: + Raised if the rule cannot be inserted at this point in the + style sheet. + - :exc:`~xml.dom.InvalidModificationErr`: + Raised if the specified CSS string value represents a different + type of rule than the current one. + - :exc:`~xml.dom.NoModificationAllowedErr`: + Raised if the rule is readonly. + - :exc:`~xml.dom.SyntaxErr`: + Raised if the specified CSS string value has a syntax error and + is unparsable. """ super(CSSImportRule, self)._setCssText(cssText) tokenizer = self._tokenize2(cssText) @@ -268,7 +252,7 @@ class CSSImportRule(cssrule.CSSRule): self.styleSheet._parentStyleSheet = self.parentStyleSheet cssText = property(fget=_getCssText, fset=_setCssText, - doc="(DOM attribute) The parsable textual representation.") + doc="(DOM) The parsable textual representation of this rule.") def _setHref(self, href): # update seq @@ -291,11 +275,11 @@ class CSSImportRule(cssrule.CSSRule): doc="Location of the style sheet to be imported.") media = property(lambda self: self._media, - doc=u"(DOM readonly) A list of media types for this rule" - " of type MediaList") + doc="(DOM readonly) A list of media types for this rule " + "of type :class:`~cssutils.stylesheets.MediaList`.") def _setName(self, name): - """raises xml.dom.SyntaxErr if name is not a string""" + """Raises xml.dom.SyntaxErr if name is not a string.""" if isinstance(name, basestring) or name is None: # "" or '' if not name: @@ -322,7 +306,7 @@ class CSSImportRule(cssrule.CSSRule): self._log.error(u'CSSImportRule: Not a valid name: %s' % name) name = property(lambda self: self._name, _setName, - doc=u"An optional name for the imported sheet") + doc=u"An optional name for the imported sheet.") def __setStyleSheet(self): """Read new CSSStyleSheet cssText from href using parentStyleSheet.href @@ -372,28 +356,15 @@ class CSSImportRule(cssrule.CSSRule): styleSheet = property(lambda self: self._styleSheet, doc="(readonly) The style sheet referred to by this rule.") + type = property(lambda self: self.IMPORT_RULE, + doc="The type of this rule, as defined by a CSSRule " + "type constant.") + def _getWellformed(self): - "depending if media is used at all" + "Depending if media is used at all." if self._usemedia: return bool(self.href and self.media.wellformed) else: return bool(self.href) wellformed = property(_getWellformed) - - def __repr__(self): - if self._usemedia: - mediaText = self.media.mediaText - else: - mediaText = None - return "cssutils.css.%s(href=%r, mediaText=%r, name=%r)" % ( - self.__class__.__name__, - self.href, self.media.mediaText, self.name) - - def __str__(self): - if self._usemedia: - mediaText = self.media.mediaText - else: - mediaText = None - return "" % ( - self.__class__.__name__, self.href, mediaText, self.name, id(self)) diff --git a/src/cssutils/css/cssmediarule.py b/src/cssutils/css/cssmediarule.py index 8f84f08ae4..3867f99c16 100644 --- a/src/cssutils/css/cssmediarule.py +++ b/src/cssutils/css/cssmediarule.py @@ -1,12 +1,11 @@ -"""CSSMediaRule implements DOM Level 2 CSS CSSMediaRule. -""" +"""CSSMediaRule implements DOM Level 2 CSS CSSMediaRule.""" __all__ = ['CSSMediaRule'] __docformat__ = 'restructuredtext' -__version__ = '$Id: cssmediarule.py 1370 2008-07-14 20:15:03Z cthedot $' +__version__ = '$Id: cssmediarule.py 1641 2009-01-13 21:05:37Z cthedot $' -import xml.dom import cssrule import cssutils +import xml.dom class CSSMediaRule(cssrule.CSSRule): """ @@ -14,31 +13,17 @@ class CSSMediaRule(cssrule.CSSRule): MEDIA_RULE constant. On these objects the type attribute must return the value of that constant. - Properties - ========== - atkeyword: (cssutils only) - the literal keyword used - cssRules: A css::CSSRuleList of all CSS rules contained within the - media block. - cssText: of type DOMString - The parsable textual representation of this rule - media: of type stylesheets::MediaList, (DOM readonly) - A list of media types for this rule of type MediaList. - name: - An optional name used for cascading + Format:: - Format - ====== - media : MEDIA_SYM S* medium [ COMMA S* medium ]* STRING? # the name LBRACE S* ruleset* '}' S*; + + ``cssRules`` + All Rules in this media rule, a :class:`~cssutils.css.CSSRuleList`. """ - # CONSTANT - type = property(lambda self: cssrule.CSSRule.MEDIA_RULE) - def __init__(self, mediaText='all', name=None, parentRule=None, parentStyleSheet=None, readonly=False): """constructor""" @@ -56,12 +41,20 @@ class CSSMediaRule(cssrule.CSSRule): self._readonly = readonly def __iter__(self): - """generator which iterates over cssRules.""" + """Generator iterating over these rule's cssRules.""" for rule in self.cssRules: yield rule + def __repr__(self): + return "cssutils.css.%s(mediaText=%r)" % ( + self.__class__.__name__, self.media.mediaText) + + def __str__(self): + return "" % ( + self.__class__.__name__, self.media.mediaText, id(self)) + def _getCssText(self): - """return serialized property cssText""" + """Return serialized property cssText.""" return cssutils.ser.do_CSSMediaRule(self) def _setCssText(self, cssText): @@ -69,19 +62,19 @@ class CSSMediaRule(cssrule.CSSRule): :param cssText: a parseable string or a tuple of (cssText, dict-of-namespaces) :Exceptions: - - `NAMESPACE_ERR`: (Selector) + - :exc:`~xml.dom.NamespaceErr`: Raised if a specified selector uses an unknown namespace prefix. - - `SYNTAX_ERR`: (self, StyleDeclaration, etc) + - :exc:`~xml.dom.SyntaxErr`: Raised if the specified CSS string value has a syntax error and is unparsable. - - `INVALID_MODIFICATION_ERR`: (self) + - :exc:`~xml.dom.InvalidModificationErr`: Raised if the specified CSS string value represents a different type of rule than the current one. - - `HIERARCHY_REQUEST_ERR`: (CSSStylesheet) + - :exc:`~xml.dom.HierarchyRequestErr`: Raised if the rule cannot be inserted at this point in the style sheet. - - `NO_MODIFICATION_ALLOWED_ERR`: (CSSRule) + - :exc:`~xml.dom.NoModificationAllowedErr`: Raised if the rule is readonly. """ super(CSSMediaRule, self)._setCssText(cssText) @@ -209,7 +202,7 @@ class CSSMediaRule(cssrule.CSSRule): self.cssRules.append(r) cssText = property(_getCssText, _setCssText, - doc="(DOM attribute) The parsable textual representation.") + doc="(DOM) The parsable textual representation of this rule.") def _setName(self, name): if isinstance(name, basestring) or name is None: @@ -221,30 +214,26 @@ class CSSMediaRule(cssrule.CSSRule): else: self._log.error(u'CSSImportRule: Not a valid name: %s' % name) - name = property(lambda self: self._name, _setName, - doc=u"An optional name for the media rules") + doc=u"An optional name for this media rule.") media = property(lambda self: self._media, - doc=u"(DOM readonly) A list of media types for this rule of type\ - MediaList") - - wellformed = property(lambda self: self.media.wellformed) + doc=u"(DOM readonly) A list of media types for this rule of type " + u":class:`~cssutils.stylesheets.MediaList`.") def deleteRule(self, index): """ - index - within the media block's rule collection of the rule to remove. + Delete the rule at `index` from the media block. + + :param index: + of the rule to remove within the media block's rule collection - Used to delete a rule from the media block. - - DOMExceptions - - - INDEX_SIZE_ERR: (self) - Raised if the specified index does not correspond to a rule in - the media rule list. - - NO_MODIFICATION_ALLOWED_ERR: (self) - Raised if this media rule is readonly. + :Exceptions: + - :exc:`~xml.dom.IndexSizeErr`: + Raised if the specified index does not correspond to a rule in + the media rule list. + - :exc:`~xml.dom.NoModificationAllowedErr`: + Raised if this media rule is readonly. """ self._checkReadonly() @@ -257,46 +246,47 @@ class CSSMediaRule(cssrule.CSSRule): index, self.cssRules.length)) def add(self, rule): - """Add rule to end of this mediarule. Same as ``.insertRule(rule)``.""" + """Add `rule` to end of this mediarule. + Same as :meth:`~cssutils.css.CSSMediaRule.insertRule`.""" self.insertRule(rule, index=None) def insertRule(self, rule, index=None): """ - rule - The parsable text representing the rule. For rule sets this - contains both the selector and the style declaration. For - at-rules, this specifies both the at-identifier and the rule + Insert `rule` into the media block. + + :param rule: + the parsable text representing the `rule` to be inserted. For rule + sets this contains both the selector and the style declaration. + For at-rules, this specifies both the at-identifier and the rule content. - cssutils also allows rule to be a valid **CSSRule** object + cssutils also allows rule to be a valid :class:`~cssutils.css.CSSRule` + object. - index - within the media block's rule collection of the rule before - which to insert the specified rule. If the specified index is + :param index: + before the specified `rule` will be inserted. + If the specified `index` is equal to the length of the media blocks's rule collection, the rule will be added to the end of the media block. If index is not given or None rule will be appended to rule list. - Used to insert a new rule into the media block. + :returns: + the index within the media block's rule collection of the + newly inserted rule. - DOMException on setting - - - HIERARCHY_REQUEST_ERR: - (no use case yet as no @charset or @import allowed)) - Raised if the rule cannot be inserted at the specified index, - e.g., if an @import rule is inserted after a standard rule set - or other at-rule. - - INDEX_SIZE_ERR: (self) - Raised if the specified index is not a valid insertion point. - - NO_MODIFICATION_ALLOWED_ERR: (self) - Raised if this media rule is readonly. - - SYNTAX_ERR: (CSSStyleRule) - Raised if the specified rule has a syntax error and is - unparsable. - - returns the index within the media block's rule collection of the - newly inserted rule. + :exceptions: + - :exc:`~xml.dom.HierarchyRequestErr`: + Raised if the `rule` cannot be inserted at the specified `index`, + e.g., if an @import rule is inserted after a standard rule set + or other at-rule. + - :exc:`~xml.dom.IndexSizeErr`: + Raised if the specified `index` is not a valid insertion point. + - :exc:`~xml.dom.NoModificationAllowedErr`: + Raised if this media rule is readonly. + - :exc:`~xml.dom.SyntaxErr`: + Raised if the specified `rule` has a syntax error and is + unparsable. """ self._checkReadonly() @@ -340,10 +330,8 @@ class CSSMediaRule(cssrule.CSSRule): rule._parentStyleSheet = self.parentStyleSheet return index - def __repr__(self): - return "cssutils.css.%s(mediaText=%r)" % ( - self.__class__.__name__, self.media.mediaText) - - def __str__(self): - return "" % ( - self.__class__.__name__, self.media.mediaText, id(self)) + type = property(lambda self: self.MEDIA_RULE, + doc="The type of this rule, as defined by a CSSRule " + "type constant.") + + wellformed = property(lambda self: self.media.wellformed) diff --git a/src/cssutils/css/cssnamespacerule.py b/src/cssutils/css/cssnamespacerule.py index 4a05868a48..ba2e84810c 100644 --- a/src/cssutils/css/cssnamespacerule.py +++ b/src/cssutils/css/cssnamespacerule.py @@ -1,16 +1,12 @@ -"""CSSNamespaceRule currently implements -http://dev.w3.org/csswg/css3-namespace/ - -(until 0.9.5a2: http://www.w3.org/TR/2006/WD-css3-namespace-20060828/) -""" +"""CSSNamespaceRule currently implements http://dev.w3.org/csswg/css3-namespace/""" __all__ = ['CSSNamespaceRule'] __docformat__ = 'restructuredtext' -__version__ = '$Id: cssnamespacerule.py 1305 2008-06-22 18:42:51Z cthedot $' +__version__ = '$Id: cssnamespacerule.py 1638 2009-01-13 20:39:33Z cthedot $' -import xml.dom +from cssutils.helper import Deprecated import cssrule import cssutils -from cssutils.helper import Deprecated +import xml.dom class CSSNamespaceRule(cssrule.CSSRule): """ @@ -20,36 +16,19 @@ class CSSNamespaceRule(cssrule.CSSRule): it with a given namespace (a string). This namespace prefix can then be used in namespace-qualified names such as those described in the Selectors Module [SELECT] or the Values and Units module [CSS3VAL]. + + Dealing with these rules directly is not needed anymore, easier is + the use of :attr:`cssutils.css.CSSStyleSheet.namespaces`. - Properties - ========== - atkeyword (cssutils only) - the literal keyword used - cssText: of type DOMString - The parsable textual representation of this rule - namespaceURI: of type DOMString - The namespace URI (a simple string!) which is bound to the given - prefix. If no prefix is set (``CSSNamespaceRule.prefix==''``) - the namespace defined by ``namespaceURI`` is set as the default - namespace. - prefix: of type DOMString - The prefix used in the stylesheet for the given - ``CSSNamespaceRule.nsuri``. If prefix is empty namespaceURI sets a - default namespace for the stylesheet. + Format:: - Inherits properties from CSSRule - - Format - ====== - namespace - : NAMESPACE_SYM S* [namespace_prefix S*]? [STRING|URI] S* ';' S* - ; - namespace_prefix - : IDENT - ; + namespace + : NAMESPACE_SYM S* [namespace_prefix S*]? [STRING|URI] S* ';' S* + ; + namespace_prefix + : IDENT + ; """ - type = property(lambda self: cssrule.CSSRule.NAMESPACE_RULE) - def __init__(self, namespaceURI=None, prefix=None, cssText=None, parentRule=None, parentStyleSheet=None, readonly=False): """ @@ -102,27 +81,31 @@ class CSSNamespaceRule(cssrule.CSSRule): self._readonly = readonly + def __repr__(self): + return "cssutils.css.%s(namespaceURI=%r, prefix=%r)" % ( + self.__class__.__name__, self.namespaceURI, self.prefix) + + def __str__(self): + return "" % ( + self.__class__.__name__, self.namespaceURI, self.prefix, id(self)) + def _getCssText(self): - """ - returns serialized property cssText - """ + """Return serialized property cssText""" return cssutils.ser.do_CSSNamespaceRule(self) def _setCssText(self, cssText): """ - DOMException on setting - :param cssText: initial value for this rules cssText which is parsed - :Exceptions: - - `HIERARCHY_REQUEST_ERR`: (CSSStylesheet) + :exceptions: + - :exc:`~xml.dom.HierarchyRequestErr`: Raised if the rule cannot be inserted at this point in the style sheet. - - `INVALID_MODIFICATION_ERR`: (self) + - :exc:`~xml.dom.InvalidModificationErr`: Raised if the specified CSS string value represents a different type of rule than the current one. - - `NO_MODIFICATION_ALLOWED_ERR`: (CSSRule) + - :exc:`~xml.dom.NoModificationAllowedErr`: Raised if the rule is readonly. - - `SYNTAX_ERR`: (self) + - :exc:`~xml.dom.SyntaxErr`: Raised if the specified CSS string value has a syntax error and is unparsable. """ @@ -222,15 +205,13 @@ class CSSNamespaceRule(cssrule.CSSRule): self._setSeq(newseq) cssText = property(fget=_getCssText, fset=_setCssText, - doc="(DOM attribute) The parsable textual representation.") + doc="(DOM) The parsable textual representation of this rule.") def _setNamespaceURI(self, namespaceURI): """ - DOMException on setting - :param namespaceURI: the initial value for this rules namespaceURI - :Exceptions: - - `NO_MODIFICATION_ALLOWED_ERR`: + :exceptions: + - :exc:`~xml.dom.NoModificationAllowedErr`: (CSSRule) Raised if this rule is readonly or a namespaceURI is already set in this rule. """ @@ -246,18 +227,16 @@ class CSSNamespaceRule(cssrule.CSSRule): error=xml.dom.NoModificationAllowedErr) namespaceURI = property(lambda self: self._namespaceURI, _setNamespaceURI, - doc="URI (string!) of the defined namespace.") + doc="URI (handled as simple string) of the defined namespace.") def _setPrefix(self, prefix=None): """ - DOMException on setting - :param prefix: the new prefix - :Exceptions: - - `SYNTAX_ERR`: (TODO) + :exceptions: + - :exc:`~xml.dom.SyntaxErr`: Raised if the specified CSS string value has a syntax error and is unparsable. - - `NO_MODIFICATION_ALLOWED_ERR`: CSSRule) + - :exc:`~xml.dom.NoModificationAllowedErr`: Raised if this rule is readonly. """ self._checkReadonly() @@ -295,12 +274,9 @@ class CSSNamespaceRule(cssrule.CSSRule): # _setParentStyleSheet, # doc=u"Containing CSSStyleSheet.") + type = property(lambda self: self.NAMESPACE_RULE, + doc="The type of this rule, as defined by a CSSRule " + "type constant.") + wellformed = property(lambda self: self.namespaceURI is not None) - - def __repr__(self): - return "cssutils.css.%s(namespaceURI=%r, prefix=%r)" % ( - self.__class__.__name__, self.namespaceURI, self.prefix) - - def __str__(self): - return "" % ( - self.__class__.__name__, self.namespaceURI, self.prefix, id(self)) + \ No newline at end of file diff --git a/src/cssutils/css/csspagerule.py b/src/cssutils/css/csspagerule.py index e042d2b672..04cde02a05 100644 --- a/src/cssutils/css/csspagerule.py +++ b/src/cssutils/css/csspagerule.py @@ -2,13 +2,13 @@ """ __all__ = ['CSSPageRule'] __docformat__ = 'restructuredtext' -__version__ = '$Id: csspagerule.py 1284 2008-06-05 16:29:17Z cthedot $' +__version__ = '$Id: csspagerule.py 1658 2009-02-07 18:24:40Z cthedot $' -import xml.dom +from cssstyledeclaration import CSSStyleDeclaration +from selectorlist import SelectorList import cssrule import cssutils -from selectorlist import SelectorList -from cssstyledeclaration import CSSStyleDeclaration +import xml.dom class CSSPageRule(cssrule.CSSRule): """ @@ -16,22 +16,7 @@ class CSSPageRule(cssrule.CSSRule): sheet. The @page rule is used to specify the dimensions, orientation, margins, etc. of a page box for paged media. - Properties - ========== - atkeyword (cssutils only) - the literal keyword used - cssText: of type DOMString - The parsable textual representation of this rule - selectorText: of type DOMString - The parsable textual representation of the page selector for the rule. - style: of type CSSStyleDeclaration - The declaration-block of this rule. - - Inherits properties from CSSRule - - Format - ====== - :: + Format:: page : PAGE_SYM S* pseudo_page? S* @@ -40,20 +25,15 @@ class CSSPageRule(cssrule.CSSRule): pseudo_page : ':' IDENT # :first, :left, :right in CSS 2.1 ; - """ - type = property(lambda self: cssrule.CSSRule.PAGE_RULE) - # constant but needed: - wellformed = True - def __init__(self, selectorText=None, style=None, parentRule=None, parentStyleSheet=None, readonly=False): """ - if readonly allows setting of properties in constructor only + If readonly allows setting of properties in constructor only. - selectorText + :param selectorText: type string - style + :param style: CSSStyleDeclaration for this CSSStyleRule """ super(CSSPageRule, self).__init__(parentRule=parentRule, @@ -64,7 +44,7 @@ class CSSPageRule(cssrule.CSSRule): self.selectorText = selectorText tempseq.append(self.selectorText, 'selectorText') else: - self._selectorText = u'' + self._selectorText = self._tempSeq() if style: self.style = style tempseq.append(self.style, 'style') @@ -74,20 +54,29 @@ class CSSPageRule(cssrule.CSSRule): self._readonly = readonly + def __repr__(self): + return "cssutils.css.%s(selectorText=%r, style=%r)" % ( + self.__class__.__name__, self.selectorText, self.style.cssText) + + def __str__(self): + return "" % ( + self.__class__.__name__, self.selectorText, self.style.cssText, + id(self)) + def __parseSelectorText(self, selectorText): """ - parses selectorText which may also be a list of tokens - and returns (selectorText, seq) + Parse `selectorText` which may also be a list of tokens + and returns (selectorText, seq). see _setSelectorText for details """ # for closures: must be a mutable - new = {'selector': None, 'wellformed': True} + new = {'wellformed': True, 'last-S': False} def _char(expected, seq, token, tokenizer=None): # pseudo_page, :left, :right or :first val = self._tokenvalue(token) - if ':' == expected and u':' == val: + if not new['last-S'] and expected in ['page', ': or EOF'] and u':' == val: try: identtoken = tokenizer.next() except StopIteration: @@ -100,8 +89,7 @@ class CSSPageRule(cssrule.CSSRule): u'CSSPageRule selectorText: Expected IDENT but found: %r' % ival, token) else: - new['selector'] = val + ival - seq.append(new['selector'], 'selector') + seq.append(val + ival, 'pseudo') return 'EOF' return expected else: @@ -112,22 +100,37 @@ class CSSPageRule(cssrule.CSSRule): def S(expected, seq, token, tokenizer=None): "Does not raise if EOF is found." + if expected == ': or EOF': + # pseudo must directly follow IDENT if given + new['last-S'] = True return expected + def IDENT(expected, seq, token, tokenizer=None): + "" + val = self._tokenvalue(token) + if 'page' == expected: + seq.append(val, 'IDENT') + return ': or EOF' + else: + new['wellformed'] = False + self._log.error( + u'CSSPageRule selectorText: Unexpected IDENT: %r' % val, token) + return expected + def COMMENT(expected, seq, token, tokenizer=None): "Does not raise if EOF is found." seq.append(cssutils.css.CSSComment([token]), 'COMMENT') return expected newseq = self._tempSeq() - wellformed, expected = self._parse(expected=':', + wellformed, expected = self._parse(expected='page', seq=newseq, tokenizer=self._tokenize2(selectorText), productions={'CHAR': _char, + 'IDENT': IDENT, 'COMMENT': COMMENT, 'S': S}, new=new) wellformed = wellformed and new['wellformed'] - newselector = new['selector'] # post conditions if expected == 'ident': @@ -135,33 +138,30 @@ class CSSPageRule(cssrule.CSSRule): u'CSSPageRule selectorText: No valid selector: %r' % self._valuestr(selectorText)) - if not newselector in (None, u':first', u':left', u':right'): - self._log.warn(u'CSSPageRule: Unknown CSS 2.1 @page selector: %r' % - newselector, neverraise=True) +# if not newselector in (None, u':first', u':left', u':right'): +# self._log.warn(u'CSSPageRule: Unknown CSS 2.1 @page selector: %r' % +# newselector, neverraise=True) - return newselector, newseq + return wellformed, newseq def _getCssText(self): - """ - returns serialized property cssText - """ + """Return serialized property cssText.""" return cssutils.ser.do_CSSPageRule(self) def _setCssText(self, cssText): """ - DOMException on setting - - - SYNTAX_ERR: (self, StyleDeclaration) - Raised if the specified CSS string value has a syntax error and - is unparsable. - - INVALID_MODIFICATION_ERR: (self) - Raised if the specified CSS string value represents a different - type of rule than the current one. - - HIERARCHY_REQUEST_ERR: (CSSStylesheet) - Raised if the rule cannot be inserted at this point in the - style sheet. - - NO_MODIFICATION_ALLOWED_ERR: (CSSRule) - Raised if the rule is readonly. + :exceptions: + - :exc:`~xml.dom.SyntaxErr`: + Raised if the specified CSS string value has a syntax error and + is unparsable. + - :exc:`~xml.dom.InvalidModificationErr`: + Raised if the specified CSS string value represents a different + type of rule than the current one. + - :exc:`~xml.dom.HierarchyRequestErr`: + Raised if the rule cannot be inserted at this point in the + style sheet. + - :exc:`~xml.dom.NoModificationAllowedErr`: + Raised if the rule is readonly. """ super(CSSPageRule, self)._setCssText(cssText) @@ -190,7 +190,7 @@ class CSSPageRule(cssrule.CSSRule): u'CSSPageRule: Trailing content found.', token=nonetoken) - newselector, newselectorseq = self.__parseSelectorText(selectortokens) + wellformed, newselectorseq = self.__parseSelectorText(selectortokens) newstyle = CSSStyleDeclaration() val, typ = self._tokenvalue(braceorEOFtoken), self._type(braceorEOFtoken) @@ -206,63 +206,49 @@ class CSSPageRule(cssrule.CSSRule): newstyle.cssText = styletokens if wellformed: - self._selectorText = newselector # already parsed + self._selectorText = newselectorseq # already parsed self.style = newstyle self._setSeq(newselectorseq) # contains upto style only cssText = property(_getCssText, _setCssText, - doc="(DOM) The parsable textual representation of the rule.") + doc="(DOM) The parsable textual representation of this rule.") def _getSelectorText(self): - """ - wrapper for cssutils Selector object - """ - return self._selectorText + """Wrapper for cssutils Selector object.""" + return cssutils.ser.do_CSSPageRuleSelector(self._selectorText)#self._selectorText def _setSelectorText(self, selectorText): - """ - wrapper for cssutils Selector object + """Wrapper for cssutils Selector object. - selector: DOM String - in CSS 2.1 one of + :param selectorText: + DOM String, in CSS 2.1 one of + - :first - :left - :right - empty - If WS or Comments are included they are ignored here! Only - way to add a comment is via setting ``cssText`` - - DOMException on setting - - - SYNTAX_ERR: - Raised if the specified CSS string value has a syntax error - and is unparsable. - - NO_MODIFICATION_ALLOWED_ERR: (self) - Raised if this rule is readonly. + :exceptions: + - :exc:`~xml.dom.SyntaxErr`: + Raised if the specified CSS string value has a syntax error + and is unparsable. + - :exc:`~xml.dom.NoModificationAllowedErr`: + Raised if this rule is readonly. """ self._checkReadonly() # may raise SYNTAX_ERR - newselectortext, newseq = self.__parseSelectorText(selectorText) - - if newselectortext: - for i, x in enumerate(self.seq): - if x == self._selectorText: - self.seq[i] = newselectortext - self._selectorText = newselectortext + wellformed, newseq = self.__parseSelectorText(selectorText) + if wellformed and newseq: + self._selectorText = newseq selectorText = property(_getSelectorText, _setSelectorText, doc="""(DOM) The parsable textual representation of the page selector for the rule.""") - def _getStyle(self): - - return self._style - def _setStyle(self, style): """ - style - StyleDeclaration or string + :param style: + a CSSStyleDeclaration or string """ self._checkReadonly() @@ -273,14 +259,14 @@ class CSSPageRule(cssrule.CSSRule): # so use seq! self._style._seq = style.seq - style = property(_getStyle, _setStyle, - doc="(DOM) The declaration-block of this rule set.") + style = property(lambda self: self._style, _setStyle, + doc="(DOM) The declaration-block of this rule set, " + "a :class:`~cssutils.css.CSSStyleDeclaration`.") - def __repr__(self): - return "cssutils.css.%s(selectorText=%r, style=%r)" % ( - self.__class__.__name__, self.selectorText, self.style.cssText) - def __str__(self): - return "" % ( - self.__class__.__name__, self.selectorText, self.style.cssText, - id(self)) + type = property(lambda self: self.PAGE_RULE, + doc="The type of this rule, as defined by a CSSRule " + "type constant.") + + # constant but needed: + wellformed = property(lambda self: True) diff --git a/src/cssutils/css/cssproperties.py b/src/cssutils/css/cssproperties.py index bc253b3e3d..fb55ce0dc4 100644 --- a/src/cssutils/css/cssproperties.py +++ b/src/cssutils/css/cssproperties.py @@ -45,264 +45,16 @@ TODO: CSS2Properties DOMImplementation string for this extended interface listed in this section is "CSS2" and the version is "2.0". - -cssvalues -========= -contributed by Kevin D. Smith, thanks! - -"cssvalues" is used as a property validator. -it is an importable object that contains a dictionary of compiled regular -expressions. The keys of this dictionary are all of the valid CSS property -names. The values are compiled regular expressions that can be used to -validate the values for that property. (Actually, the values are references -to the 'match' method of a compiled regular expression, so that they are -simply called like functions.) - """ -__all__ = ['CSS2Properties', 'cssvalues'] +__all__ = ['CSS2Properties'] __docformat__ = 'restructuredtext' -__version__ = '$Id: cssproperties.py 1469 2008-09-15 19:06:00Z cthedot $' +__version__ = '$Id: cssproperties.py 1638 2009-01-13 20:39:33Z cthedot $' +import cssutils.profiles import re -""" -Define some regular expression fragments that will be used as -macros within the CSS property value regular expressions. -""" -MACROS = { - 'ident': r'[-]?{nmstart}{nmchar}*', - 'name': r'{nmchar}+', - 'nmstart': r'[_a-z]|{nonascii}|{escape}', - 'nonascii': r'[^\0-\177]', - 'unicode': r'\\[0-9a-f]{1,6}(\r\n|[ \n\r\t\f])?', - 'escape': r'{unicode}|\\[ -~\200-\777]', -# 'escape': r'{unicode}|\\[ -~\200-\4177777]', - 'int': r'[-]?\d+', - 'nmchar': r'[\w-]|{nonascii}|{escape}', - 'num': r'[-]?\d+|[-]?\d*\.\d+', - 'number': r'{num}', - 'string': r'{string1}|{string2}', - 'string1': r'"(\\\"|[^\"])*"', - 'string2': r"'(\\\'|[^\'])*'", - 'nl': r'\n|\r\n|\r|\f', - 'w': r'\s*', - - 'integer': r'{int}', - 'length': r'0|{num}(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', - '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}\)', - 'uri': r'url\({w}({string}|(\\\)|[^\)])+){w}\)', - 'percentage': r'{num}%', - 'border-style': 'none|hidden|dotted|dashed|solid|double|groove|ridge|inset|outset', - 'border-color': '{color}', - 'border-width': '{length}|thin|medium|thick', - - 'background-color': r'{color}|transparent|inherit', - 'background-image': r'{uri}|none|inherit', - 'background-position': r'({percentage}|{length})(\s*({percentage}|{length}))?|((top|center|bottom)\s*(left|center|right))|((left|center|right)\s*(top|center|bottom))|inherit', - 'background-repeat': r'repeat|repeat-x|repeat-y|no-repeat|inherit', - 'background-attachment': r'scroll|fixed|inherit', - - '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}', - '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-style': r'normal|italic|oblique|inherit', - 'font-variant': r'normal|small-caps|inherit', - 'font-weight': r'normal|bold|bolder|lighter|[1-9]00|inherit', - 'line-height': r'normal|{number}|{length}|{percentage}|inherit', - 'list-style-image': r'{uri}|none|inherit', - 'list-style-position': r'inside|outside|inherit', - 'list-style-type': r'disc|circle|square|decimal|decimal-leading-zero|lower-roman|upper-roman|lower-greek|lower-(latin|alpha)|upper-(latin|alpha)|armenian|georgian|none|inherit', - 'margin-width': r'{length}|{percentage}|auto', - 'outline-color': r'{color}|invert|inherit', - 'outline-style': r'{border-style}|inherit', - 'outline-width': r'{border-width}|inherit', - 'padding-width': r'{length}|{percentage}', - 'specific-voice': r'{identifier}', - 'generic-voice': r'male|female|child', - 'content': r'{string}|{uri}|{counter}|attr\({w}{identifier}{w}\)|open-quote|close-quote|no-open-quote|no-close-quote', - 'border-attrs': r'{border-width}|{border-style}|{border-color}', - 'background-attrs': r'{background-color}|{background-image}|{background-repeat}|{background-attachment}|{background-position}', - 'list-attrs': r'{list-style-type}|{list-style-position}|{list-style-image}', - 'font-attrs': r'{font-style}|{font-variant}|{font-weight}', - 'outline-attrs': r'{outline-color}|{outline-style}|{outline-width}', - 'text-attrs': r'underline|overline|line-through|blink', -} - -""" -Define the regular expressions for validation all CSS values -""" -cssvalues = { - 'azimuth': r'{angle}|(behind\s+)?(left-side|far-left|left|center-left|center|center-right|right|far-right|right-side)(\s+behind)?|behind|leftwards|rightwards|inherit', - 'background-attachment': r'{background-attachment}', - 'background-color': r'{background-color}', - 'background-image': r'{background-image}', - 'background-position': r'{background-position}', - 'background-repeat': r'{background-repeat}', - # Each piece should only be allowed one time - 'background': r'{background-attrs}(\s+{background-attrs})*|inherit', - 'border-collapse': r'collapse|separate|inherit', - 'border-color': r'({border-color}|transparent)(\s+({border-color}|transparent)){0,3}|inherit', - 'border-spacing': r'{length}(\s+{length})?|inherit', - 'border-style': r'{border-style}(\s+{border-style}){0,3}|inherit', - 'border-top': r'{border-attrs}(\s+{border-attrs})*|inherit', - 'border-right': r'{border-attrs}(\s+{border-attrs})*|inherit', - 'border-bottom': r'{border-attrs}(\s+{border-attrs})*|inherit', - 'border-left': r'{border-attrs}(\s+{border-attrs})*|inherit', - 'border-top-color': r'{border-color}|transparent|inherit', - 'border-right-color': r'{border-color}|transparent|inherit', - 'border-bottom-color': r'{border-color}|transparent|inherit', - 'border-left-color': r'{border-color}|transparent|inherit', - 'border-top-style': r'{border-style}|inherit', - 'border-right-style': r'{border-style}|inherit', - 'border-bottom-style': r'{border-style}|inherit', - 'border-left-style': r'{border-style}|inherit', - 'border-top-width': r'{border-width}|inherit', - 'border-right-width': r'{border-width}|inherit', - 'border-bottom-width': r'{border-width}|inherit', - 'border-left-width': r'{border-width}|inherit', - 'border-width': r'{border-width}(\s+{border-width}){0,3}|inherit', - 'border': r'{border-attrs}(\s+{border-attrs})*|inherit', - 'bottom': r'{length}|{percentage}|auto|inherit', - 'caption-side': r'top|bottom|inherit', - 'clear': r'none|left|right|both|inherit', - 'clip': r'{shape}|auto|inherit', - 'color': r'{color}|inherit', - 'content': r'normal|{content}(\s+{content})*|inherit', - 'counter-increment': r'({identifier}(\s+{integer})?)(\s+({identifier}(\s+{integer})))*|none|inherit', - 'counter-reset': r'({identifier}(\s+{integer})?)(\s+({identifier}(\s+{integer})))*|none|inherit', - 'cue-after': r'{uri}|none|inherit', - 'cue-before': r'{uri}|none|inherit', - 'cue': r'({uri}|none|inherit){1,2}|inherit', - 'cursor': r'((({uri}{w},{w})*)?(auto|crosshair|default|pointer|move|(e|ne|nw|n|se|sw|s|w)-resize|text|wait|help|progress))|inherit', - 'direction': r'ltr|rtl|inherit', - 'display': r'inline|block|list-item|run-in|inline-block|table|inline-table|table-row-group|table-header-group|table-footer-group|table-row|table-column-group|table-column|table-cell|table-caption|none|inherit', - 'elevation': r'{angle}|below|level|above|higher|lower|inherit', - 'empty-cells': r'show|hide|inherit', - 'float': r'left|right|none|inherit', - 'font-family': r'{font-family}', - 'font-size': r'{font-size}', - 'font-style': r'{font-style}', - 'font-variant': r'{font-variant}', - 'font-weight': r'{font-weight}', - 'font': r'({font-attrs}\s+)*{font-size}({w}/{w}{line-height})?\s+{font-family}|caption|icon|menu|message-box|small-caption|status-bar|inherit', - 'height': r'{length}|{percentage}|auto|inherit', - 'left': r'{length}|{percentage}|auto|inherit', - 'letter-spacing': r'normal|{length}|inherit', - 'line-height': r'{line-height}', - 'list-style-image': r'{list-style-image}', - 'list-style-position': r'{list-style-position}', - 'list-style-type': r'{list-style-type}', - 'list-style': r'{list-attrs}(\s+{list-attrs})*|inherit', - 'margin-right': r'{margin-width}|inherit', - 'margin-left': r'{margin-width}|inherit', - 'margin-top': r'{margin-width}|inherit', - 'margin-bottom': r'{margin-width}|inherit', - 'margin': r'{margin-width}(\s+{margin-width}){0,3}|inherit', - 'max-height': r'{length}|{percentage}|none|inherit', - 'max-width': r'{length}|{percentage}|none|inherit', - 'min-height': r'{length}|{percentage}|none|inherit', - 'min-width': r'{length}|{percentage}|none|inherit', - 'orphans': r'{integer}|inherit', - 'outline-color': r'{outline-color}', - 'outline-style': r'{outline-style}', - 'outline-width': r'{outline-width}', - 'outline': r'{outline-attrs}(\s+{outline-attrs})*|inherit', - 'overflow': r'visible|hidden|scroll|auto|inherit', - 'padding-top': r'{padding-width}|inherit', - 'padding-right': r'{padding-width}|inherit', - 'padding-bottom': r'{padding-width}|inherit', - 'padding-left': r'{padding-width}|inherit', - 'padding': r'{padding-width}(\s+{padding-width}){0,3}|inherit', - 'page-break-after': r'auto|always|avoid|left|right|inherit', - 'page-break-before': r'auto|always|avoid|left|right|inherit', - 'page-break-inside': r'avoid|auto|inherit', - 'pause-after': r'{time}|{percentage}|inherit', - 'pause-before': r'{time}|{percentage}|inherit', - 'pause': r'({time}|{percentage}){1,2}|inherit', - 'pitch-range': r'{number}|inherit', - 'pitch': r'{frequency}|x-low|low|medium|high|x-high|inherit', - 'play-during': r'{uri}(\s+(mix|repeat))*|auto|none|inherit', - 'position': r'static|relative|absolute|fixed|inherit', - 'quotes': r'({string}\s+{string})(\s+{string}\s+{string})*|none|inherit', - 'richness': r'{number}|inherit', - 'right': r'{length}|{percentage}|auto|inherit', - 'speak-header': r'once|always|inherit', - 'speak-numeral': r'digits|continuous|inherit', - 'speak-punctuation': r'code|none|inherit', - 'speak': r'normal|none|spell-out|inherit', - 'speech-rate': r'{number}|x-slow|slow|medium|fast|x-fast|faster|slower|inherit', - 'stress': r'{number}|inherit', - 'table-layout': r'auto|fixed|inherit', - 'text-align': r'left|right|center|justify|inherit', - 'text-decoration': r'none|{text-attrs}(\s+{text-attrs})*|inherit', - 'text-indent': r'{length}|{percentage}|inherit', - 'text-transform': r'capitalize|uppercase|lowercase|none|inherit', - 'top': r'{length}|{percentage}|auto|inherit', - 'unicode-bidi': r'normal|embed|bidi-override|inherit', - 'vertical-align': r'baseline|sub|super|top|text-top|middle|bottom|text-bottom|{percentage}|{length}|inherit', - 'visibility': r'visible|hidden|collapse|inherit', - 'voice-family': r'({specific-voice}|{generic-voice}{w},{w})*({specific-voice}|{generic-voice})|inherit', - 'volume': r'{number}|{percentage}|silent|x-soft|soft|medium|loud|x-loud|inherit', - 'white-space': r'normal|pre|nowrap|pre-wrap|pre-line|inherit', - 'widows': r'{integer}|inherit', - 'width': r'{length}|{percentage}|auto|inherit', - 'word-spacing': r'normal|{length}|inherit', - 'z-index': r'auto|{integer}|inherit', -} - -def _expand_macros(tokdict): - """ Expand macros in token dictionary """ - def macro_value(m): - return '(?:%s)' % MACROS[m.groupdict()['macro']] - for key, value in tokdict.items(): - while re.search(r'{[a-z][a-z0-9-]*}', value): - value = re.sub(r'{(?P[a-z][a-z0-9-]*)}', - macro_value, value) - tokdict[key] = value - return tokdict - -def _compile_regexes(tokdict): - """ Compile all regular expressions into callable objects """ - for key, value in tokdict.items(): - tokdict[key] = re.compile('^(?:%s)$' % value, re.I).match - return tokdict - -_compile_regexes(_expand_macros(cssvalues)) - - -# functions to convert between CSS and DOM name - -_reCSStoDOMname = re.compile('-[a-z]', re.I) -def _toDOMname(CSSname): - """ - returns DOMname for given CSSname e.g. for CSSname 'font-style' returns - 'fontStyle' - """ - def _doCSStoDOMname2(m): return m.group(0)[1].capitalize() - return _reCSStoDOMname.sub(_doCSStoDOMname2, CSSname) - -_reDOMtoCSSname = re.compile('([A-Z])[a-z]+') -def _toCSSname(DOMname): - """ - returns CSSname for given DOMname e.g. for DOMname 'fontStyle' returns - 'font-style' - """ - def _doDOMtoCSSname2(m): return '-' + m.group(0).lower() - return _reDOMtoCSSname.sub(_doDOMtoCSSname2, DOMname) - - class CSS2Properties(object): - """ - The CSS2Properties interface represents a convenience mechanism + """The CSS2Properties interface represents a convenience mechanism for retrieving and setting properties within a CSSStyleDeclaration. The attributes of this interface correspond to all the properties specified in CSS2. Getting an attribute of this interface is @@ -325,17 +77,38 @@ class CSS2Properties(object): def _getP(self, CSSname): pass def _setP(self, CSSname, value): pass def _delP(self, CSSname): pass + + +_reCSStoDOMname = re.compile('-[a-z]', re.I) +def _toDOMname(CSSname): + """Returns DOMname for given CSSname e.g. for CSSname 'font-style' returns + 'fontStyle'. + """ + def _doCSStoDOMname2(m): return m.group(0)[1].capitalize() + return _reCSStoDOMname.sub(_doCSStoDOMname2, CSSname) + +_reDOMtoCSSname = re.compile('([A-Z])[a-z]+') +def _toCSSname(DOMname): + """Return CSSname for given DOMname e.g. for DOMname 'fontStyle' returns + 'font-style'. + """ + def _doDOMtoCSSname2(m): return '-' + m.group(0).lower() + return _reDOMtoCSSname.sub(_doDOMtoCSSname2, DOMname) # add list of DOMname properties to CSS2Properties # used for CSSStyleDeclaration to check if allowed properties # but somehow doubled, any better way? -CSS2Properties._properties = [_toDOMname(p) for p in cssvalues.keys()] +CSS2Properties._properties = [] +for group in cssutils.profiles.properties: + for name in cssutils.profiles.properties[group]: + CSS2Properties._properties.append(_toDOMname(name)) + # add CSS2Properties to CSSStyleDeclaration: def __named_property_def(DOMname): """ - closure to keep name known in each properties accessor function - DOMname is converted to CSSname here, so actual calls use CSSname + Closure to keep name known in each properties accessor function + DOMname is converted to CSSname here, so actual calls use CSSname. """ CSSname = _toCSSname(DOMname) def _get(self): return self._getP(CSSname) diff --git a/src/cssutils/css/cssrule.py b/src/cssutils/css/cssrule.py index 58cc16f537..e3eb7296c7 100644 --- a/src/cssutils/css/cssrule.py +++ b/src/cssutils/css/cssrule.py @@ -1,46 +1,17 @@ """CSSRule implements DOM Level 2 CSS CSSRule.""" __all__ = ['CSSRule'] __docformat__ = 'restructuredtext' -__version__ = '$Id: cssrule.py 1177 2008-03-20 17:47:23Z cthedot $' +__version__ = '$Id: cssrule.py 1638 2009-01-13 20:39:33Z cthedot $' -import xml.dom import cssutils +import xml.dom class CSSRule(cssutils.util.Base2): - """ - Abstract base interface for any type of CSS statement. This includes + """Abstract base interface for any type of CSS statement. This includes both rule sets and at-rules. An implementation is expected to preserve all rules specified in a CSS style sheet, even if the rule is not recognized by the parser. Unrecognized rules are represented using the - CSSUnknownRule interface. - - Properties - ========== - cssText: of type DOMString - The parsable textual representation of the rule. This reflects the - current state of the rule and not its initial value. - parentRule: of type CSSRule, readonly - If this rule is contained inside another rule (e.g. a style rule - inside an @media block), this is the containing rule. If this rule - is not nested inside any other rules, this returns None. - parentStyleSheet: of type CSSStyleSheet, readonly - The style sheet that contains this rule. - type: of type unsigned short, readonly - The type of the rule, as defined above. The expectation is that - binding-specific casting methods can be used to cast down from an - instance of the CSSRule interface to the specific derived interface - implied by the type. - - cssutils only - ------------- - seq (READONLY): - contains sequence of parts of the rule including comments but - excluding @KEYWORD and braces - typeString: string - A string name of the type of this rule, e.g. 'STYLE_RULE'. Mainly - useful for debugging - wellformed: - if a rule is valid + :class:`CSSUnknownRule` interface. """ """ @@ -61,21 +32,8 @@ class CSSRule(cssutils.util.Base2): 'MEDIA_RULE', 'FONT_FACE_RULE', 'PAGE_RULE', 'NAMESPACE_RULE', 'COMMENT'] - type = UNKNOWN_RULE - """ - The type of this rule, as defined by a CSSRule type constant. - Overwritten in derived classes. - - The expectation is that binding-specific casting methods can be used to - cast down from an instance of the CSSRule interface to the specific - derived interface implied by the type. - (Casting not for this Python implementation I guess...) - """ - 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__() self._parentRule = parentRule self._parentStyleSheet = parentStyleSheet @@ -83,33 +41,8 @@ class CSSRule(cssutils.util.Base2): # must be set after initialization of #inheriting rule is done self._readonly = False - def _setCssText(self, cssText): - """ - DOMException on setting - - - SYNTAX_ERR: - Raised if the specified CSS string value has a syntax error and - is unparsable. - - INVALID_MODIFICATION_ERR: - Raised if the specified CSS string value represents a different - type of rule than the current one. - - HIERARCHY_REQUEST_ERR: - Raised if the rule cannot be inserted at this point in the - style sheet. - - NO_MODIFICATION_ALLOWED_ERR: (self) - Raised if the rule is readonly. - """ - self._checkReadonly() - - cssText = property(lambda self: u'', _setCssText, - doc="""(DOM) The parsable textual representation of the rule. This - reflects the current state of the rule and not its initial value. - The initial value is saved, but this may be removed in a future - version! - MUST BE OVERWRITTEN IN SUBCLASS TO WORK!""") - def _setAtkeyword(self, akw): - """checks if new keyword is normalized same as old""" + """Check if new keyword fits the rule it is used for.""" if not self.atkeyword or (self._normalize(akw) == self._normalize(self.atkeyword)): self._atkeyword = akw @@ -119,16 +52,48 @@ class CSSRule(cssutils.util.Base2): error=xml.dom.InvalidModificationErr) atkeyword = property(lambda self: self._atkeyword, _setAtkeyword, - doc=u"@keyword for @rules") + doc=u"Literal keyword of an @rule (e.g. ``@IMport``).") + + def _setCssText(self, cssText): + """ + :param cssText: + A parsable DOMString. + :exceptions: + - :exc:`~xml.dom.SyntaxErr`: + Raised if the specified CSS string value has a syntax error and + is unparsable. + - :exc:`~xml.dom.InvalidModificationErr`: + Raised if the specified CSS string value represents a different + type of rule than the current one. + - :exc:`~xml.dom.HierarchyRequestErr`: + Raised if the rule cannot be inserted at this point in the + style sheet. + - :exc:`~xml.dom.NoModificationAllowedErr`: + Raised if the rule is readonly. + """ + self._checkReadonly() + + cssText = property(lambda self: u'', _setCssText, + doc="(DOM) The parsable textual representation of the " + "rule. This reflects the current state of the rule " + "and not its initial value.") parentRule = property(lambda self: self._parentRule, - doc=u"READONLY") + doc="If this rule is contained inside " + "another rule (e.g. a style rule inside " + "an @media block), this is the containing " + "rule. If this rule is not nested inside " + "any other rules, this returns None.") parentStyleSheet = property(lambda self: self._parentStyleSheet, - doc=u"READONLY") + doc="The style sheet that contains this rule.") - wellformed = property(lambda self: False, - doc=u"READONLY") + type = property(lambda self: self.UNKNOWN_RULE, + doc="The type of this rule, as defined by a CSSRule " + "type constant.") typeString = property(lambda self: CSSRule._typestrings[self.type], - doc="Name of this rules type.") + doc="Descriptive name of this rule's type.") + + wellformed = property(lambda self: False, + doc=u"If the rule is wellformed.") diff --git a/src/cssutils/css/cssrulelist.py b/src/cssutils/css/cssrulelist.py index fdc9f29caf..43adfe1415 100644 --- a/src/cssutils/css/cssrulelist.py +++ b/src/cssutils/css/cssrulelist.py @@ -1,16 +1,12 @@ -""" -CSSRuleList implements DOM Level 2 CSS CSSRuleList. - -Partly also - * http://dev.w3.org/csswg/cssom/#the-cssrulelist +"""CSSRuleList implements DOM Level 2 CSS CSSRuleList. +Partly also http://dev.w3.org/csswg/cssom/#the-cssrulelist """ __all__ = ['CSSRuleList'] __docformat__ = 'restructuredtext' -__version__ = '$Id: cssrulelist.py 1116 2008-03-05 13:52:23Z cthedot $' +__version__ = '$Id: cssrulelist.py 1641 2009-01-13 21:05:37Z cthedot $' class CSSRuleList(list): - """ - The CSSRuleList object represents an (ordered) list of statements. + """The CSSRuleList object represents an (ordered) list of statements. The items in the CSSRuleList are accessible via an integral index, starting from 0. @@ -21,28 +17,20 @@ class CSSRuleList(list): class if so desired. E.g. CSSStyleSheet adds ``append`` which is not available in a simple instance of this class! - - Properties - ========== - length: of type unsigned long, readonly - The number of CSSRules in the list. The range of valid child rule - indices is 0 to length-1 inclusive. """ def __init__(self, *ignored): - "nothing is set as this must also be defined later" + "Nothing is set as this must also be defined later." pass def __notimplemented(self, *ignored): - "no direct setting possible" + "Implemented in class using a CSSRuleList only." raise NotImplementedError( 'Must be implemented by class using an instance of this class.') append = extend = __setitem__ = __setslice__ = __notimplemented def item(self, index): - """ - (DOM) - Used to retrieve a CSS rule by ordinal index. The order in this + """(DOM) Retrieve a CSS rule by ordinal `index`. The order in this collection represents the order of the rules in the CSS style sheet. If index is greater than or equal to the number of rules in the list, this returns None. diff --git a/src/cssutils/css/cssstyledeclaration.py b/src/cssutils/css/cssstyledeclaration.py index 43eda2049b..c766151116 100644 --- a/src/cssutils/css/cssstyledeclaration.py +++ b/src/cssutils/css/cssstyledeclaration.py @@ -51,16 +51,15 @@ TODO: """ __all__ = ['CSSStyleDeclaration', 'Property'] __docformat__ = 'restructuredtext' -__version__ = '$Id: cssstyledeclaration.py 1284 2008-06-05 16:29:17Z cthedot $' +__version__ = '$Id: cssstyledeclaration.py 1658 2009-02-07 18:24:40Z cthedot $' -import xml.dom -import cssutils from cssproperties import CSS2Properties from property import Property +import cssutils +import xml.dom class CSSStyleDeclaration(CSS2Properties, cssutils.util.Base2): - """ - The CSSStyleDeclaration class represents a single CSS declaration + """The CSSStyleDeclaration class represents a single CSS declaration block. This class may be used to determine the style properties currently set in a block or to set style properties explicitly within the block. @@ -76,24 +75,6 @@ class CSSStyleDeclaration(CSS2Properties, cssutils.util.Base2): Additionally the CSS2Properties interface is implemented. - Properties - ========== - cssText - The parsable textual representation of the declaration block - (excluding the surrounding curly braces). Setting this attribute - will result in the parsing of the new value and resetting of the - properties in the declaration block. It also allows the insertion - of additional properties and their values into the block. - length: of type unsigned long, readonly - The number of properties that have been explicitly set in this - declaration block. The range of valid indices is 0 to length-1 - inclusive. - parentRule: of type CSSRule, readonly - The CSS rule that contains this declaration block or None if this - CSSStyleDeclaration is not attached to a CSSRule. - seq: a list (cssutils) - All parts of this style declaration including CSSComments - $css2propertyname All properties defined in the CSS2Properties class are available as direct properties of CSSStyleDeclaration with their respective @@ -106,33 +87,32 @@ class CSSStyleDeclaration(CSS2Properties, cssutils.util.Base2): >>> print style.color green >>> del style.color - >>> print style.color # print empty string + >>> print style.color + - Format - ====== - [Property: Value Priority?;]* [Property: Value Priority?]? + Format:: + + [Property: Value Priority?;]* [Property: Value Priority?]? """ def __init__(self, cssText=u'', parentRule=None, readonly=False): """ - cssText + :param cssText: Shortcut, sets CSSStyleDeclaration.cssText - parentRule + :param parentRule: The CSS rule that contains this declaration block or None if this CSSStyleDeclaration is not attached to a CSSRule. - readonly + :param readonly: defaults to False """ super(CSSStyleDeclaration, self).__init__() self._parentRule = parentRule - #self._seq = self._tempSeq() self.cssText = cssText self._readonly = readonly def __contains__(self, nameOrProperty): - """ - checks if a property (or a property with given name is in style + """Check if a property (or a property with given name) is in style. - name + :param name: a string or Property, uses normalized name and not literalname """ if isinstance(nameOrProperty, Property): @@ -142,47 +122,12 @@ class CSSStyleDeclaration(CSS2Properties, cssutils.util.Base2): return name in self.__nnames() def __iter__(self): - """ - iterator of set Property objects with different normalized names. - """ + """Iterator of set Property objects with different normalized names.""" def properties(): for name in self.__nnames(): yield self.getProperty(name) return properties() - def __setattr__(self, n, v): - """ - Prevent setting of unknown properties on CSSStyleDeclaration - which would not work anyway. For these - ``CSSStyleDeclaration.setProperty`` MUST be called explicitly! - - TODO: - implementation of known is not really nice, any alternative? - """ - known = ['_tokenizer', '_log', '_ttypes', - '_seq', 'seq', 'parentRule', '_parentRule', 'cssText', - 'valid', 'wellformed', - '_readonly'] - known.extend(CSS2Properties._properties) - if n in known: - super(CSSStyleDeclaration, self).__setattr__(n, v) - else: - raise AttributeError( - 'Unknown CSS Property, ``CSSStyleDeclaration.setProperty("%s", ...)`` MUST be used.' - % n) - - def __nnames(self): - """ - returns iterator for all different names in order as set - if names are set twice the last one is used (double reverse!) - """ - names = [] - for item in reversed(self.seq): - val = item.value - if isinstance(val, Property) and not val.name in names: - names.append(val.name) - return reversed(names) - def __getitem__(self, CSSName): """Retrieve the value of property ``CSSName`` from this declaration. @@ -211,11 +156,49 @@ class CSSStyleDeclaration(CSS2Properties, cssutils.util.Base2): """ return self.removeProperty(CSSName) + def __setattr__(self, n, v): + """Prevent setting of unknown properties on CSSStyleDeclaration + which would not work anyway. For these + ``CSSStyleDeclaration.setProperty`` MUST be called explicitly! + + TODO: + implementation of known is not really nice, any alternative? + """ + known = ['_tokenizer', '_log', '_ttypes', + '_seq', 'seq', 'parentRule', '_parentRule', 'cssText', + 'valid', 'wellformed', + '_readonly', '_profiles'] + known.extend(CSS2Properties._properties) + if n in known: + super(CSSStyleDeclaration, self).__setattr__(n, v) + else: + raise AttributeError( + 'Unknown CSS Property, ``CSSStyleDeclaration.setProperty("%s", ...)`` MUST be used.' + % n) + + def __repr__(self): + return "cssutils.css.%s(cssText=%r)" % ( + self.__class__.__name__, self.getCssText(separator=u' ')) + + def __str__(self): + return "" % ( + self.__class__.__name__, self.length, + len(self.getProperties(all=True)), id(self)) + + def __nnames(self): + """Return iterator for all different names in order as set + if names are set twice the last one is used (double reverse!) + """ + names = [] + for item in reversed(self.seq): + val = item.value + if isinstance(val, Property) and not val.name in names: + names.append(val.name) + return reversed(names) + # overwritten accessor functions for CSS2Properties' properties def _getP(self, CSSName): - """ - (DOM CSS2Properties) - Overwritten here and effectively the same as + """(DOM CSS2Properties) Overwritten here and effectively the same as ``self.getPropertyValue(CSSname)``. Parameter is in CSSname format ('font-style'), see CSS2Properties. @@ -229,9 +212,7 @@ class CSSStyleDeclaration(CSS2Properties, cssutils.util.Base2): return self.getPropertyValue(CSSName) def _setP(self, CSSName, value): - """ - (DOM CSS2Properties) - Overwritten here and effectively the same as + """(DOM CSS2Properties) Overwritten here and effectively the same as ``self.setProperty(CSSname, value)``. Only known CSS2Properties may be set this way, otherwise an @@ -247,44 +228,40 @@ class CSSStyleDeclaration(CSS2Properties, cssutils.util.Base2): >>> style.fontStyle = 'italic' >>> # or >>> style.setProperty('font-style', 'italic', '!important') + """ self.setProperty(CSSName, value) # TODO: Shorthand ones def _delP(self, CSSName): - """ - (cssutils only) - Overwritten here and effectively the same as + """(cssutils only) Overwritten here and effectively the same as ``self.removeProperty(CSSname)``. Example:: >>> style = CSSStyleDeclaration(cssText='font-style:italic;') >>> del style.fontStyle - >>> print style.fontStyle # prints u'' + >>> print style.fontStyle + """ self.removeProperty(CSSName) def _getCssText(self): - """ - returns serialized property cssText - """ + """Return serialized property cssText.""" return cssutils.ser.do_css_CSSStyleDeclaration(self) def _setCssText(self, cssText): - """ - Setting this attribute will result in the parsing of the new value + """Setting this attribute will result in the parsing of the new value and resetting of all the properties in the declaration block including the removal or addition of properties. - DOMException on setting - - - NO_MODIFICATION_ALLOWED_ERR: (self) - Raised if this declaration is readonly or a property is readonly. - - SYNTAX_ERR: (self) - Raised if the specified CSS string value has a syntax error and - is unparsable. + :exceptions: + - :exc:`~xml.dom.NoModificationAllowedErr`: + Raised if this declaration is readonly or a property is readonly. + - :exc:`~xml.dom.SyntaxErr`: + Raised if the specified CSS string value has a syntax error and + is unparsable. """ self._checkReadonly() tokenizer = self._tokenize2(cssText) @@ -336,11 +313,12 @@ class CSSStyleDeclaration(CSS2Properties, cssutils.util.Base2): def getCssText(self, separator=None): """ - returns serialized property cssText, each property separated by - given ``separator`` which may e.g. be u'' to be able to use - cssText directly in an HTML style attribute. ";" is always part of - each property (except the last one) and can **not** be set with - separator! + :returns: + serialized property cssText, each property separated by + given `separator` which may e.g. be ``u''`` to be able to use + cssText directly in an HTML style attribute. ``;`` is part of + each property (except the last one) and **cannot** be set with + separator! """ return cssutils.ser.do_css_CSSStyleDeclaration(self, separator) @@ -351,25 +329,27 @@ class CSSStyleDeclaration(CSS2Properties, cssutils.util.Base2): self._parentRule = parentRule parentRule = property(_getParentRule, _setParentRule, - doc="(DOM) The CSS rule that contains this declaration block or\ - None if this CSSStyleDeclaration is not attached to a CSSRule.") + doc="(DOM) The CSS rule that contains this declaration block or " + "None if this CSSStyleDeclaration is not attached to a CSSRule.") def getProperties(self, name=None, all=False): """ - Returns a list of Property objects set in this declaration. + :param name: + optional `name` of properties which are requested. + Only properties with this **always normalized** `name` are returned. + If `name` is ``None`` all properties are returned (at least one for + each set name depending on parameter `all`). + :param all: + if ``False`` (DEFAULT) only the effective properties are returned. + If name is given a list with only one property is returned. - name - optional name of properties which are requested (a filter). - Only properties with this **always normalized** name are returned. - all=False - if False (DEFAULT) only the effective properties (the ones set - last) are returned. If name is given a list with only one property - is returned. - - if True all properties including properties set multiple times with - different values or priorities for different UAs are returned. + if ``True`` all properties including properties set multiple times + with different values or priorities for different UAs are returned. The order of the properties is fully kept as in the original stylesheet. + :returns: + a list of :class:`~cssutils.css.Property` objects set in + this declaration. """ if name and not all: # single prop but list @@ -394,16 +374,16 @@ class CSSStyleDeclaration(CSS2Properties, cssutils.util.Base2): def getProperty(self, name, normalize=True): """ - Returns the effective Property object. - - name + :param name: of the CSS property, always lowercase (even if not normalized) - normalize - if True (DEFAULT) name will be normalized (lowercase, no simple + :param normalize: + if ``True`` (DEFAULT) name will be normalized (lowercase, no simple escapes) so "color", "COLOR" or "C\olor" will all be equivalent - If False may return **NOT** the effective value but the effective - for the unnormalized name. + If ``False`` may return **NOT** the effective value but the + effective for the unnormalized name. + :returns: + the effective :class:`~cssutils.css.Property` object. """ nname = self._normalize(name) found = None @@ -419,17 +399,17 @@ class CSSStyleDeclaration(CSS2Properties, cssutils.util.Base2): def getPropertyCSSValue(self, name, normalize=True): """ - Returns CSSValue, the value of the effective property if it has been - explicitly set for this declaration block. - - name + :param name: of the CSS property, always lowercase (even if not normalized) - normalize - if True (DEFAULT) name will be normalized (lowercase, no simple + :param normalize: + if ``True`` (DEFAULT) name will be normalized (lowercase, no simple escapes) so "color", "COLOR" or "C\olor" will all be equivalent - If False may return **NOT** the effective value but the effective + If ``False`` may return **NOT** the effective value but the effective for the unnormalized name. + :returns: + :class:`~cssutils.css.CSSValue`, the value of the effective + property if it has been explicitly set for this declaration block. (DOM) Used to retrieve the object representation of the value of a CSS @@ -461,18 +441,18 @@ class CSSStyleDeclaration(CSS2Properties, cssutils.util.Base2): def getPropertyValue(self, name, normalize=True): """ - Returns the value of the effective property if it has been explicitly - set for this declaration block. Returns the empty string if the - property has not been set. - - name + :param name: of the CSS property, always lowercase (even if not normalized) - normalize - if True (DEFAULT) name will be normalized (lowercase, no simple + :param normalize: + if ``True`` (DEFAULT) name will be normalized (lowercase, no simple escapes) so "color", "COLOR" or "C\olor" will all be equivalent - If False may return **NOT** the effective value but the effective - for the unnormalized name. + If ``False`` may return **NOT** the effective value but the + effective for the unnormalized name. + :returns: + the value of the effective property if it has been explicitly set + for this declaration block. Returns the empty string if the + property has not been set. """ p = self.getProperty(name, normalize) if p: @@ -482,18 +462,18 @@ class CSSStyleDeclaration(CSS2Properties, cssutils.util.Base2): def getPropertyPriority(self, name, normalize=True): """ - Returns the priority of the effective CSS property (e.g. the - "important" qualifier) if the property has been explicitly set in - this declaration block. The empty string if none exists. - - name + :param name: of the CSS property, always lowercase (even if not normalized) - normalize - if True (DEFAULT) name will be normalized (lowercase, no simple + :param normalize: + if ``True`` (DEFAULT) name will be normalized (lowercase, no simple escapes) so "color", "COLOR" or "C\olor" will all be equivalent - If False may return **NOT** the effective value but the effective - for the unnormalized name. + If ``False`` may return **NOT** the effective value but the + effective for the unnormalized name. + :returns: + the priority of the effective CSS property (e.g. the + "important" qualifier) if the property has been explicitly set in + this declaration block. The empty string if none exists. """ p = self.getProperty(name, normalize) if p: @@ -507,28 +487,28 @@ class CSSStyleDeclaration(CSS2Properties, cssutils.util.Base2): Used to remove a CSS property if it has been explicitly set within this declaration block. - Returns the value of the property if it has been explicitly set for - this declaration block. Returns the empty string if the property - has not been set or the property name does not correspond to a - known CSS property - - name + :param name: of the CSS property - normalize - if True (DEFAULT) name will be normalized (lowercase, no simple + :param normalize: + if ``True`` (DEFAULT) name will be normalized (lowercase, no simple escapes) so "color", "COLOR" or "C\olor" will all be equivalent. The effective Property value is returned and *all* Properties with ``Property.name == name`` are removed. - If False may return **NOT** the effective value but the effective - for the unnormalized ``name`` only. Also only the Properties with - the literal name ``name`` are removed. + If ``False`` may return **NOT** the effective value but the + effective for the unnormalized `name` only. Also only the + Properties with the literal name `name` are removed. + :returns: + the value of the property if it has been explicitly set for + this declaration block. Returns the empty string if the property + has not been set or the property name does not correspond to a + known CSS property - raises DOMException - - NO_MODIFICATION_ALLOWED_ERR: (self) - Raised if this declaration is readonly or the property is - readonly. + :exceptions: + - :exc:`~xml.dom.NoModificationAllowedErr`: + Raised if this declaration is readonly or the property is + readonly. """ self._checkReadonly() r = self.getPropertyValue(name, normalize=normalize) @@ -548,41 +528,40 @@ class CSSStyleDeclaration(CSS2Properties, cssutils.util.Base2): return r def setProperty(self, name, value=None, priority=u'', normalize=True): - """ - (DOM) - Used to set a property value and priority within this declaration + """(DOM) Set a property value and priority within this declaration block. - name + :param name: of the CSS property to set (in W3C DOM the parameter is called "propertyName"), always lowercase (even if not normalized) - If a property with this name is present it will be reset + If a property with this `name` is present it will be reset. - cssutils also allowed name to be a Property object, all other + cssutils also allowed `name` to be a + :class:`~cssutils.css.Property` object, all other parameter are ignored in this case - value - the new value of the property, omit if name is already a Property - priority - the optional priority of the property (e.g. "important") - normalize - if True (DEFAULT) name will be normalized (lowercase, no simple + :param value: + the new value of the property, ignored if `name` is a Property. + :param priority: + the optional priority of the property (e.g. "important"), + ignored if `name` is a Property. + :param normalize: + if True (DEFAULT) `name` will be normalized (lowercase, no simple escapes) so "color", "COLOR" or "C\olor" will all be equivalent - DOMException on setting - - - SYNTAX_ERR: (self) - Raised if the specified value has a syntax error and is - unparsable. - - NO_MODIFICATION_ALLOWED_ERR: (self) - Raised if this declaration is readonly or the property is - readonly. + :exceptions: + - :exc:`~xml.dom.SyntaxErr`: + Raised if the specified value has a syntax error and is + unparsable. + - :exc:`~xml.dom.NoModificationAllowedErr`: + Raised if this declaration is readonly or the property is + readonly. """ self._checkReadonly() if isinstance(name, Property): - newp = name + newp = name name = newp.literalname else: newp = Property(name, value, priority) @@ -607,27 +586,26 @@ class CSSStyleDeclaration(CSS2Properties, cssutils.util.Base2): self.seq._readonly = True def item(self, index): - """ - (DOM) - Used to retrieve the properties that have been explicitly set in + """(DOM) Retrieve the properties that have been explicitly set in this declaration block. The order of the properties retrieved using this method does not have to be the order in which they were set. This method can be used to iterate over all properties in this declaration block. - index + :param index: of the property to retrieve, negative values behave like negative indexes on Python lists, so -1 is the last element - returns the name of the property at this ordinal position. The - empty string if no property exists at this position. + :returns: + the name of the property at this ordinal position. The + empty string if no property exists at this position. - ATTENTION: - Only properties with a different name are counted. If two + **ATTENTION:** + Only properties with different names are counted. If two properties with the same name are present in this declaration only the effective one is included. - ``item()`` and ``length`` work on the same set here. + :meth:`item` and :attr:`length` work on the same set here. """ names = list(self.__nnames()) try: @@ -636,16 +614,7 @@ class CSSStyleDeclaration(CSS2Properties, cssutils.util.Base2): return u'' length = property(lambda self: len(self.__nnames()), - doc="(DOM) The number of distinct properties that have been explicitly\ - in this declaration block. The range of valid indices is 0 to\ - length-1 inclusive. These are properties with a different ``name``\ - only. ``item()`` and ``length`` work on the same set here.") - - def __repr__(self): - return "cssutils.css.%s(cssText=%r)" % ( - self.__class__.__name__, self.getCssText(separator=u' ')) - - def __str__(self): - return "" % ( - self.__class__.__name__, self.length, - len(self.getProperties(all=True)), id(self)) + doc="(DOM) The number of distinct properties that have been explicitly " + "in this declaration block. The range of valid indices is 0 to " + "length-1 inclusive. These are properties with a different ``name`` " + "only. :meth:`item` and :attr:`length` work on the same set here.") diff --git a/src/cssutils/css/cssstylerule.py b/src/cssutils/css/cssstylerule.py index e6dcfd0205..661e719743 100644 --- a/src/cssutils/css/cssstylerule.py +++ b/src/cssutils/css/cssstylerule.py @@ -1,49 +1,25 @@ -"""CSSStyleRule implements DOM Level 2 CSS CSSStyleRule. -""" +"""CSSStyleRule implements DOM Level 2 CSS CSSStyleRule.""" __all__ = ['CSSStyleRule'] __docformat__ = 'restructuredtext' -__version__ = '$Id: cssstylerule.py 1284 2008-06-05 16:29:17Z cthedot $' +__version__ = '$Id: cssstylerule.py 1638 2009-01-13 20:39:33Z cthedot $' -import xml.dom +from cssstyledeclaration import CSSStyleDeclaration +from selectorlist import SelectorList import cssrule import cssutils -from selectorlist import SelectorList -from cssstyledeclaration import CSSStyleDeclaration +import xml.dom class CSSStyleRule(cssrule.CSSRule): - """ - The CSSStyleRule object represents a ruleset specified (if any) in a CSS + """The CSSStyleRule object represents a ruleset specified (if any) in a CSS style sheet. It provides access to a declaration block as well as to the associated group of selectors. - Properties - ========== - selectorList: of type SelectorList (cssutils only) - A list of all Selector elements for the rule set. - selectorText: of type DOMString - The textual representation of the selector for the rule set. The - implementation may have stripped out insignificant whitespace while - parsing the selector. - style: of type CSSStyleDeclaration, (DOM) - The declaration-block of this rule set. - type - the type of this rule, constant cssutils.CSSRule.STYLE_RULE - - inherited properties: - - cssText - - parentRule - - parentStyleSheet - - Format - ====== - ruleset:: + Format:: : selector [ COMMA S* selector ]* LBRACE S* declaration [ ';' S* declaration ]* '}' S* ; """ - type = property(lambda self: cssrule.CSSRule.STYLE_RULE) - def __init__(self, selectorText=None, style=None, parentRule=None, parentStyleSheet=None, readonly=False): """ @@ -67,31 +43,41 @@ class CSSStyleRule(cssrule.CSSRule): self._readonly = readonly + def __repr__(self): + if self._namespaces: + st = (self.selectorText, self._namespaces) + else: + st = self.selectorText + return "cssutils.css.%s(selectorText=%r, style=%r)" % ( + self.__class__.__name__, st, self.style.cssText) + + def __str__(self): + return "" % ( + self.__class__.__name__, self.selectorText, self.style.cssText, + self._namespaces, id(self)) def _getCssText(self): - """ - returns serialized property cssText - """ + """Return serialized property cssText.""" return cssutils.ser.do_CSSStyleRule(self) def _setCssText(self, cssText): """ :param cssText: a parseable string or a tuple of (cssText, dict-of-namespaces) - :Exceptions: - - `NAMESPACE_ERR`: (Selector) + :exceptions: + - :exc:`~xml.dom.NamespaceErr`: Raised if the specified selector uses an unknown namespace prefix. - - `SYNTAX_ERR`: (self, StyleDeclaration, etc) + - :exc:`~xml.dom.SyntaxErr`: Raised if the specified CSS string value has a syntax error and is unparsable. - - `INVALID_MODIFICATION_ERR`: (self) + - :exc:`~xml.dom.InvalidModificationErr`: Raised if the specified CSS string value represents a different type of rule than the current one. - - `HIERARCHY_REQUEST_ERR`: (CSSStylesheet) + - :exc:`~xml.dom.HierarchyRequestErr`: Raised if the rule cannot be inserted at this point in the style sheet. - - `NO_MODIFICATION_ALLOWED_ERR`: (CSSRule) + - :exc:`~xml.dom.NoModificationAllowedErr`: Raised if the rule is readonly. """ super(CSSStyleRule, self)._setCssText(cssText) @@ -160,20 +146,21 @@ class CSSStyleRule(cssrule.CSSRule): self.style = newstyle cssText = property(_getCssText, _setCssText, - doc="(DOM) The parsable textual representation of the rule.") + doc="(DOM) The parsable textual representation of this rule.") def __getNamespaces(self): - "uses children namespaces if not attached to a sheet, else the sheet's ones" + "Uses children namespaces if not attached to a sheet, else the sheet's ones." try: return self.parentStyleSheet.namespaces except AttributeError: return self.selectorList._namespaces - _namespaces = property(__getNamespaces, doc=u"""if this Rule is - attached to a CSSStyleSheet the namespaces of that sheet are mirrored - here. While the Rule is not attached the namespaces of selectorList - are used.""") + _namespaces = property(__getNamespaces, + doc="If this Rule is attached to a CSSStyleSheet " + "the namespaces of that sheet are mirrored " + "here. While the Rule is not attached the " + "namespaces of selectorList are used.""") def _setSelectorList(self, selectorList): """ @@ -190,16 +177,17 @@ class CSSStyleRule(cssrule.CSSRule): """ wrapper for cssutils SelectorList object - :param selectorText: of type string, might also be a comma separated list + :param selectorText: + of type string, might also be a comma separated list of selectors - :Exceptions: - - `NAMESPACE_ERR`: (Selector) + :exceptions: + - :exc:`~xml.dom.NamespaceErr`: Raised if the specified selector uses an unknown namespace prefix. - - `SYNTAX_ERR`: (SelectorList, Selector) + - :exc:`~xml.dom.SyntaxErr`: Raised if the specified CSS string value has a syntax error and is unparsable. - - `NO_MODIFICATION_ALLOWED_ERR`: (self) + - :exc:`~xml.dom.NoModificationAllowedErr`: Raised if this rule is readonly. """ self._checkReadonly() @@ -207,8 +195,8 @@ class CSSStyleRule(cssrule.CSSRule): selectorText = property(lambda self: self._selectorList.selectorText, _setSelectorText, - doc="""(DOM) The textual representation of the selector for the - rule set.""") + doc="(DOM) The textual representation of the " + "selector for the rule set.") def _setStyle(self, style): """ @@ -224,19 +212,10 @@ class CSSStyleRule(cssrule.CSSRule): self._style._seq = style._seq 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.") + + type = property(lambda self: self.STYLE_RULE, + doc="The type of this rule, as defined by a CSSRule " + "type constant.") wellformed = property(lambda self: self.selectorList.wellformed) - - def __repr__(self): - if self._namespaces: - st = (self.selectorText, self._namespaces) - else: - st = self.selectorText - return "cssutils.css.%s(selectorText=%r, style=%r)" % ( - self.__class__.__name__, st, self.style.cssText) - - def __str__(self): - return "" % ( - self.__class__.__name__, self.selectorText, self.style.cssText, - self._namespaces, id(self)) diff --git a/src/cssutils/css/cssstylesheet.py b/src/cssutils/css/cssstylesheet.py index c749086015..bfc5c0f5d2 100644 --- a/src/cssutils/css/cssstylesheet.py +++ b/src/cssutils/css/cssstylesheet.py @@ -1,5 +1,4 @@ -""" -CSSStyleSheet implements DOM Level 2 CSS CSSStyleSheet. +"""CSSStyleSheet implements DOM Level 2 CSS CSSStyleSheet. Partly also: - http://dev.w3.org/csswg/cssom/#the-cssstylesheet @@ -10,53 +9,32 @@ TODO: """ __all__ = ['CSSStyleSheet'] __docformat__ = 'restructuredtext' -__version__ = '$Id: cssstylesheet.py 1429 2008-08-11 19:01:52Z cthedot $' +__version__ = '$Id: cssstylesheet.py 1641 2009-01-13 21:05:37Z cthedot $' -import xml.dom -import cssutils.stylesheets -from cssutils.util import _Namespaces, _SimpleNamespaces, _readUrl from cssutils.helper import Deprecated +from cssutils.util import _Namespaces, _SimpleNamespaces, _readUrl +import cssutils.stylesheets +import xml.dom class CSSStyleSheet(cssutils.stylesheets.StyleSheet): - """ - The CSSStyleSheet interface represents a CSS style sheet. + """CSSStyleSheet represents a CSS style sheet. - Properties - ========== - CSSOM - ----- - cssRules - of type CSSRuleList, (DOM readonly) - encoding - reflects the encoding of an @charset rule or 'utf-8' (default) - if set to ``None`` - ownerRule - of type CSSRule, readonly. If this sheet is imported this is a ref - to the @import rule that imports it. + Format:: + + stylesheet + : [ CHARSET_SYM S* STRING S* ';' ]? + [S|CDO|CDC]* [ import [S|CDO|CDC]* ]* + [ namespace [S|CDO|CDC]* ]* # according to @namespace WD + [ [ ruleset | media | page ] [S|CDO|CDC]* ]* - Inherits properties from stylesheet.StyleSheet - - cssutils - -------- - cssText: string - a textual representation of the stylesheet - namespaces - reflects set @namespace rules of this rule. - A dict of {prefix: namespaceURI} mapping. - - Format - ====== - stylesheet - : [ CHARSET_SYM S* STRING S* ';' ]? - [S|CDO|CDC]* [ import [S|CDO|CDC]* ]* - [ namespace [S|CDO|CDC]* ]* # according to @namespace WD - [ [ ruleset | media | page ] [S|CDO|CDC]* ]* + ``cssRules`` + All Rules in this style sheet, a :class:`~cssutils.css.CSSRuleList`. """ def __init__(self, href=None, media=None, title=u'', disabled=None, ownerNode=None, parentStyleSheet=None, readonly=False, ownerRule=None): """ - init parameters are the same as for stylesheets.StyleSheet + For parameters see :class:`~cssutils.stylesheets.StyleSheet` """ super(CSSStyleSheet, self).__init__( 'text/css', href, media, title, disabled, @@ -74,12 +52,32 @@ class CSSStyleSheet(cssutils.stylesheets.StyleSheet): self._fetcher = None def __iter__(self): - "generator which iterates over cssRules." + "Generator which iterates over cssRules." for rule in self.cssRules: yield rule + def __repr__(self): + if self.media: + mediaText = self.media.mediaText + else: + mediaText = None + return "cssutils.css.%s(href=%r, media=%r, title=%r)" % ( + self.__class__.__name__, + self.href, mediaText, self.title) + + def __str__(self): + if self.media: + mediaText = self.media.mediaText + else: + mediaText = None + return "" % ( + self.__class__.__name__, self.encoding, self.href, + mediaText, self.title, self.namespaces.namespaces, + id(self)) + def _cleanNamespaces(self): - "removes all namespace rules with same namespaceURI but last one set" + "Remove all namespace rules with same namespaceURI but last one set." rules = self.cssRules namespaceitems = self.namespaces.items() i = 0 @@ -92,7 +90,7 @@ class CSSStyleSheet(cssutils.stylesheets.StyleSheet): i += 1 def _getUsedURIs(self): - "returns set of URIs used in the sheet" + "Return set of URIs used in the sheet." useduris = set() for r1 in self: if r1.STYLE_RULE == r1.type: @@ -104,21 +102,20 @@ class CSSStyleSheet(cssutils.stylesheets.StyleSheet): return useduris def _getCssText(self): + "Textual representation of the stylesheet (a byte string)." return cssutils.ser.do_CSSStyleSheet(self) def _setCssText(self, cssText): - """ - (cssutils) - Parses ``cssText`` and overwrites the whole stylesheet. + """Parse `cssText` and overwrites the whole stylesheet. :param cssText: a parseable string or a tuple of (cssText, dict-of-namespaces) - :Exceptions: - - `NAMESPACE_ERR`: + :exceptions: + - :exc:`~xml.dom.NamespaceErr`: If a namespace prefix is found which is not declared. - - `NO_MODIFICATION_ALLOWED_ERR`: (self) + - :exc:`~xml.dom.NoModificationAllowedErr`: Raised if the rule is readonly. - - `SYNTAX_ERR`: + - :exc:`~xml.dom.SyntaxErr`: Raised if the specified CSS string value has a syntax error and is unparsable. """ @@ -269,10 +266,10 @@ class CSSStyleSheet(cssutils.stylesheets.StyleSheet): self._cleanNamespaces() cssText = property(_getCssText, _setCssText, - "(cssutils) a textual representation of the stylesheet") + "Textual representation of the stylesheet (a byte string)") def _resolveImport(self, url): - """Read (encoding, enctype, decodedContent) from ``url`` for @import + """Read (encoding, enctype, decodedContent) from `url` for @import sheets.""" try: # only available during parse of a complete sheet @@ -289,12 +286,12 @@ class CSSStyleSheet(cssutils.stylesheets.StyleSheet): overrideEncoding=self.__encodingOverride, parentEncoding=selfAsParentEncoding) - def _setCssTextWithEncodingOverride(self, cssText, encodingOverride=None, + def _setCssTextWithEncodingOverride(self, cssText, encodingOverride=None, encoding=None): - """Set cssText but use ``encodingOverride`` to overwrite detected + """Set `cssText` but use `encodingOverride` to overwrite detected encoding. This is used by parse and @import during setting of cssText. - If ``encoding`` is given use this but do not save it as encodingOverride""" + If `encoding` is given use this but do not save it as `encodingOverride`.""" if encodingOverride: # encoding during resolving of @import self.__encodingOverride = encodingOverride @@ -312,14 +309,14 @@ class CSSStyleSheet(cssutils.stylesheets.StyleSheet): self.encoding = encoding def _setFetcher(self, fetcher=None): - """sets @import URL loader, if None the default is used""" + """Set @import URL loader, if None the default is used.""" self._fetcher = fetcher def _setEncoding(self, encoding): - """ - sets encoding of charset rule if present or inserts new charsetrule - with given encoding. If encoding if None removes charsetrule if - present. + """Set `encoding` of charset rule if present in sheet or insert a new + :class:`~cssutils.css.CSSCharsetRule` with given `encoding`. + If `encoding` is None removes charsetrule if present resulting in + default encoding of utf-8. """ try: rule = self.cssRules[0] @@ -334,41 +331,41 @@ class CSSStyleSheet(cssutils.stylesheets.StyleSheet): self.insertRule(cssutils.css.CSSCharsetRule(encoding=encoding), 0) def _getEncoding(self): - "return encoding if @charset rule if given or default of 'utf-8'" + """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) reflects the encoding of an @charset rule or 'UTF-8' (default) if set to ``None``") + "(cssutils) Reflect encoding of an @charset rule or 'utf-8' " + "(default) if set to ``None``") namespaces = property(lambda self: self._namespaces, - doc="Namespaces used in this CSSStyleSheet.") + doc="All Namespaces used in this CSSStyleSheet.") def add(self, rule): - """ - Adds rule to stylesheet at appropriate position. - Same as ``sheet.insertRule(rule, inOrder=True)``. + """Add `rule` to style sheet at appropriate position. + Same as ``insertRule(rule, inOrder=True)``. """ return self.insertRule(rule, index=None, inOrder=True) def deleteRule(self, index): - """ - Used to delete a rule from the style sheet. + """Delete rule at `index` from the style sheet. :param index: of the rule to remove in the StyleSheet's rule list. For an - index < 0 **no** INDEX_SIZE_ERR is raised but rules for - normal Python lists are used. E.g. ``deleteRule(-1)`` removes - the last rule in cssRules. - :Exceptions: - - `INDEX_SIZE_ERR`: (self) + `index` < 0 **no** :exc:`~xml.dom.IndexSizeErr` is raised but + rules for normal Python lists are used. E.g. ``deleteRule(-1)`` + removes the last rule in cssRules. + :exceptions: + - :exc:`~xml.dom.IndexSizeErr`: Raised if the specified index does not correspond to a rule in the style sheet's rule list. - - `NAMESPACE_ERR`: (self) + - :exc:`~xml.dom.NamespaceErr`: Raised if removing this rule would result in an invalid StyleSheet - - `NO_MODIFICATION_ALLOWED_ERR`: (self) + - :exc:`~xml.dom.NoModificationAllowedErr`: Raised if this style sheet is readonly. """ self._checkReadonly() @@ -398,32 +395,31 @@ class CSSStyleSheet(cssutils.stylesheets.StyleSheet): Used to insert a new rule into the style sheet. The new rule now becomes part of the cascade. - :Parameters: - rule - a parsable DOMString, in cssutils also a CSSRule or a - CSSRuleList - index - of the rule before the new rule will be inserted. - If the specified index is equal to the length of the - StyleSheet's rule collection, the rule will be added to the end - of the style sheet. - If index is not given or None rule will be appended to rule - list. - inOrder - if True the rule will be put to a proper location while - ignoring index but without raising HIERARCHY_REQUEST_ERR. - The resulting index is returned nevertheless - :returns: the index within the stylesheet's rule collection + :param rule: + a parsable DOMString, in cssutils also a + :class:`~cssutils.css.CSSRule` or :class:`~cssutils.css.CSSRuleList` + :param index: + of the rule before the new rule will be inserted. + If the specified `index` is equal to the length of the + StyleSheet's rule collection, the rule will be added to the end + of the style sheet. + If `index` is not given or ``None`` rule will be appended to rule + list. + :param inOrder: + if ``True`` the rule will be put to a proper location while + ignoring `index` and without raising :exc:`~xml.dom.HierarchyRequestErr`. + The resulting index is returned nevertheless. + :returns: The index within the style sheet's rule collection :Exceptions: - - `HIERARCHY_REQUEST_ERR`: (self) - Raised if the rule cannot be inserted at the specified index + - :exc:`~xml.dom.HierarchyRequestErr`: + Raised if the rule cannot be inserted at the specified `index` e.g. if an @import rule is inserted after a standard rule set or other at-rule. - - `INDEX_SIZE_ERR`: (self) - Raised if the specified index is not a valid insertion point. - - `NO_MODIFICATION_ALLOWED_ERR`: (self) + - :exc:`~xml.dom.IndexSizeErr`: + Raised if the specified `index` is not a valid insertion point. + - :exc:`~xml.dom.NoModificationAllowedErr`: Raised if this style sheet is readonly. - - `SYNTAX_ERR`: (rule) + - :exc:`~xml.dom.SyntaxErr`: Raised if the specified rule has a syntax error and is unparsable. """ @@ -618,57 +614,18 @@ class CSSStyleSheet(cssutils.stylesheets.StyleSheet): return index ownerRule = property(lambda self: self._ownerRule, - doc="(DOM attribute) NOT IMPLEMENTED YET") - - @Deprecated('Use cssutils.replaceUrls(sheet, replacer) instead.') - def replaceUrls(self, replacer): - """ - **EXPERIMENTAL** - - Utility method to replace all ``url(urlstring)`` values in - ``CSSImportRules`` and ``CSSStyleDeclaration`` objects (properties). - - ``replacer`` must be a function which is called with a single - argument ``urlstring`` which is the current value of url() - excluding ``url(`` and ``)``. It still may have surrounding - single or double quotes though. - """ - cssutils.replaceUrls(self, replacer) + doc="A ref to an @import rule if it is imported, else ``None``.") def setSerializer(self, cssserializer): - """ - Sets the global Serializer used for output of all stylesheet - output. - """ + """Set the cssutils global Serializer used for all output.""" if isinstance(cssserializer, cssutils.CSSSerializer): cssutils.ser = cssserializer else: raise ValueError(u'Serializer must be an instance of cssutils.CSSSerializer.') def setSerializerPref(self, pref, value): - """ - Sets Preference of CSSSerializer used for output of this - stylesheet. See cssutils.serialize.Preferences for possible + """Set a Preference of CSSSerializer used for output. + See :class:`cssutils.serialize.Preferences` for possible preferences to be set. """ cssutils.ser.prefs.__setattr__(pref, value) - - def __repr__(self): - if self.media: - mediaText = self.media.mediaText - else: - mediaText = None - return "cssutils.css.%s(href=%r, media=%r, title=%r)" % ( - self.__class__.__name__, - self.href, mediaText, self.title) - - def __str__(self): - if self.media: - mediaText = self.media.mediaText - else: - mediaText = None - return "" % ( - self.__class__.__name__, self.encoding, self.href, - mediaText, self.title, self.namespaces.namespaces, - id(self)) diff --git a/src/cssutils/css/cssunknownrule.py b/src/cssutils/css/cssunknownrule.py index 4396932ae1..c0096d4301 100644 --- a/src/cssutils/css/cssunknownrule.py +++ b/src/cssutils/css/cssunknownrule.py @@ -1,44 +1,25 @@ -"""CSSUnknownRule implements DOM Level 2 CSS CSSUnknownRule. -""" +"""CSSUnknownRule implements DOM Level 2 CSS CSSUnknownRule.""" __all__ = ['CSSUnknownRule'] __docformat__ = 'restructuredtext' -__version__ = '$Id: cssunknownrule.py 1170 2008-03-20 17:42:07Z cthedot $' +__version__ = '$Id: cssunknownrule.py 1638 2009-01-13 20:39:33Z cthedot $' -import xml.dom import cssrule import cssutils +import xml.dom class CSSUnknownRule(cssrule.CSSRule): """ - represents an at-rule not supported by this user agent. + Represents an at-rule not supported by this user agent, so in + effect all other at-rules not defined in cssutils. - Properties - ========== - inherited from CSSRule - - cssText - - type + Format:: - cssutils only - ------------- - atkeyword - the literal keyword used - seq - All parts of this rule excluding @KEYWORD but including CSSComments - wellformed - if this Rule is wellformed, for Unknown rules if an atkeyword is set - at all - - Format - ====== - unknownrule: @xxx until ';' or block {...} """ - type = property(lambda self: cssrule.CSSRule.UNKNOWN_RULE) - def __init__(self, cssText=u'', parentRule=None, parentStyleSheet=None, readonly=False): """ - cssText + :param cssText: of type string """ super(CSSUnknownRule, self).__init__(parentRule=parentRule, @@ -49,25 +30,32 @@ class CSSUnknownRule(cssrule.CSSRule): self._readonly = readonly + def __repr__(self): + return "cssutils.css.%s(cssText=%r)" % ( + self.__class__.__name__, self.cssText) + + def __str__(self): + return "" % ( + self.__class__.__name__, self.cssText, id(self)) + def _getCssText(self): - """ returns serialized property cssText """ + """Return serialized property cssText.""" return cssutils.ser.do_CSSUnknownRule(self) def _setCssText(self, cssText): """ - DOMException on setting - - - SYNTAX_ERR: - Raised if the specified CSS string value has a syntax error and - is unparsable. - - INVALID_MODIFICATION_ERR: - Raised if the specified CSS string value represents a different - type of rule than the current one. - - HIERARCHY_REQUEST_ERR: (never raised) - Raised if the rule cannot be inserted at this point in the - style sheet. - - NO_MODIFICATION_ALLOWED_ERR: (CSSRule) - Raised if the rule is readonly. + :exceptions: + - :exc:`~xml.dom.SyntaxErr`: + Raised if the specified CSS string value has a syntax error and + is unparsable. + - :exc:`~xml.dom.InvalidModificationErr`: + Raised if the specified CSS string value represents a different + type of rule than the current one. + - :exc:`~xml.dom.HierarchyRequestErr`: + Raised if the rule cannot be inserted at this point in the + style sheet. + - :exc:`~xml.dom.NoModificationAllowedErr`: + Raised if the rule is readonly. """ super(CSSUnknownRule, self)._setCssText(cssText) tokenizer = self._tokenize2(cssText) @@ -197,12 +185,9 @@ class CSSUnknownRule(cssrule.CSSRule): cssText = property(fget=_getCssText, fset=_setCssText, doc="(DOM) The parsable textual representation.") - wellformed = property(lambda self: bool(self.atkeyword)) + type = property(lambda self: self.UNKNOWN_RULE, + doc="The type of this rule, as defined by a CSSRule " + "type constant.") - def __repr__(self): - return "cssutils.css.%s(cssText=%r)" % ( - self.__class__.__name__, self.cssText) - - def __str__(self): - return "" % ( - self.__class__.__name__, self.cssText, id(self)) + wellformed = property(lambda self: bool(self.atkeyword)) + \ No newline at end of file diff --git a/src/cssutils/css/cssvalue.py b/src/cssutils/css/cssvalue.py index ddf38a4747..856e42e5c1 100644 --- a/src/cssutils/css/cssvalue.py +++ b/src/cssutils/css/cssvalue.py @@ -5,82 +5,48 @@ - CSSValueList implements DOM Level 2 CSS CSSValueList """ -__all__ = ['CSSValue', 'CSSPrimitiveValue', 'CSSValueList', 'CSSColor'] +__all__ = ['CSSValue', 'CSSPrimitiveValue', 'CSSValueList', 'RGBColor'] __docformat__ = 'restructuredtext' -__version__ = '$Id: cssvalue.py 1473 2008-09-15 21:15:54Z cthedot $' +__version__ = '$Id: cssvalue.py 1638 2009-01-13 20:39:33Z cthedot $' +from cssutils.prodparser import * +from cssutils.profiles import profiles +import cssutils +import cssutils.helper import re import xml.dom -import cssutils -from cssutils.profiles import profiles -from cssutils.prodparser import * -class CSSValue(cssutils.util.Base2): - """ - The CSSValue interface represents a simple or a complex value. - A CSSValue object only occurs in a context of a CSS property - - Properties - ========== - cssText - A string representation of the current value. - cssValueType - A (readonly) code defining the type of the value. - - seq: a list (cssutils) - All parts of this style declaration including CSSComments - valid: boolean - if the value is valid at all, False for e.g. color: #1 - wellformed - if this Property is syntactically ok - - _value (INTERNAL!) - value without any comments, used to validate +class CSSValue(cssutils.util._NewBase): + """The CSSValue interface represents a simple or a complex value. + A CSSValue object only occurs in a context of a CSS property. """ + # The value is inherited and the cssText contains "inherit". CSS_INHERIT = 0 - """ - The value is inherited and the cssText contains "inherit". - """ + # The value is a CSSPrimitiveValue. CSS_PRIMITIVE_VALUE = 1 - """ - The value is a primitive value and an instance of the - CSSPrimitiveValue interface can be obtained by using binding-specific - casting methods on this instance of the CSSValue interface. - """ + # The value is a CSSValueList. CSS_VALUE_LIST = 2 - """ - The value is a CSSValue list and an instance of the CSSValueList - interface can be obtained by using binding-specific casting - methods on this instance of the CSSValue interface. - """ + # The value is a custom value. CSS_CUSTOM = 3 - """ - The value is a custom value. - """ - _typestrings = ['CSS_INHERIT' , 'CSS_PRIMITIVE_VALUE', 'CSS_VALUE_LIST', - 'CSS_CUSTOM'] - def __init__(self, cssText=None, readonly=False, _propertyName=None): + _typestrings = {0: 'CSS_INHERIT' , + 1: 'CSS_PRIMITIVE_VALUE', + 2: 'CSS_VALUE_LIST', + 3: 'CSS_CUSTOM'} + + def __init__(self, cssText=None, readonly=False): """ - inits a new CSS Value - - cssText + :param cssText: the parsable cssText of the value - readonly + :param readonly: defaults to False - property - used to validate this value in the context of a property """ super(CSSValue, self).__init__() - #self.seq = [] - self.valid = False + self._cssValueType = None self.wellformed = False - self._valueValue = u'' - self._linetoken = None # used for line report only - self._propertyName = _propertyName if cssText is not None: # may be 0 if type(cssText) in (int, float): @@ -89,47 +55,18 @@ class CSSValue(cssutils.util.Base2): self._readonly = readonly - def _getValue(self): - # TODO: - v = [] - for item in self.seq: - type_, val = item.type, item.value - if isinstance(val, cssutils.css.CSSComment): - # only value here - continue - elif 'STRING' == type_: - v.append(cssutils.ser._string(val)) - elif 'URI' == type_: - v.append(cssutils.ser._uri(val)) - elif u',' == val: - # list of items - v.append(u' ') - v.append(val) - elif isinstance(val, basestring): - v.append(val) - else: - # maybe CSSPrimitiveValue - v.append(val.cssText) - if v and u'' == v[-1].strip(): - # simple strip of joined string does not work for escaped spaces - del v[-1] - return u''.join(v) + def __repr__(self): + return "cssutils.css.%s(%r)" % ( + self.__class__.__name__, self.cssText) - def _setValue(self, value): - "overwritten by CSSValueList!" - self._valueValue = value - - _value = property(_getValue, _setValue, - doc="Actual cssText value of this CSSValue.") - - def _getCssText(self): - return cssutils.ser.do_css_CSSValue(self) + def __str__(self): + return "" % ( + self.__class__.__name__, self.cssValueTypeString, + self.cssText, id(self)) def _setCssText(self, cssText): """ - Format - ====== - :: + Format:: unary_operator : '-' | '+' @@ -158,395 +95,217 @@ class CSSValue(cssutils.util.Base2): : HASH S* ; - DOMException on setting - - - SYNTAX_ERR: (self) - Raised if the specified CSS string value has a syntax error - (according to the attached property) or is unparsable. - - TODO: INVALID_MODIFICATION_ERR: - Raised if the specified CSS string value represents a different - type of values than the values allowed by the CSS property. - - NO_MODIFICATION_ALLOWED_ERR: (self) - Raised if this value is readonly. + :exceptions: + - :exc:`~xml.dom.SyntaxErr`: + Raised if the specified CSS string value has a syntax error + (according to the attached property) or is unparsable. + - :exc:`~xml.dom.InvalidModificationErr`: + TODO: Raised if the specified CSS string value represents a different + type of values than the values allowed by the CSS property. + - :exc:`~xml.dom.NoModificationAllowedErr`: + Raised if this value is readonly. """ self._checkReadonly() - - # for closures: must be a mutable - new = {'rawvalues': [], # used for validation - 'values': [], - 'commas': 0, - 'valid': True, - 'wellformed': True } - - def _S(expected, seq, token, tokenizer=None): - type_, val, line, col = token - new['rawvalues'].append(u' ') - if expected == 'operator': #expected.endswith('operator'): - seq.append(u' ', 'separator', line=line, col=col) - return 'term or operator' - elif expected.endswith('S'): - return 'term or S' - else: - return expected - - def _char(expected, seq, token, tokenizer=None): - type_, val, line, col = token - new['rawvalues'].append(val) - - if 'funcend' == expected and u')' == val: - # end of FUNCTION - seq.appendToVal(val) - new['values'].append(seq[-1]) - return 'operator' - - elif expected in (')', ']', '}') and expected == val: - # end of any block: (), [], {} - seq.appendToVal(val) - return 'operator' - - elif expected in ('funcend', ')', ']', '}'): - # content of func or block: (), [], {} - seq.appendToVal(val) - return expected - - elif expected.endswith('operator') and ',' == val: - # term, term; remove all WS between terms!!! - new['commas'] += 1 - if seq and seq[-1].type == 'separator': - seq.replace(-1, val, type_, line=line, col=col) - else: - seq.append(val, type_, line=line, col=col) - return 'term or S' - - elif expected.endswith('operator') and '/' == val: - # term / term - if seq and seq[-1].value == u' ': - old = seq[-1] - seq.replace(-1, val, old.type, old.line, old.col) - #seq[-1] = val - else: - seq.append(val, type_, line=line, col=col) - return 'term or S' - - elif expected.startswith('term') and u'(' == val: - # start of ( any* ) block - seq.append(val, type_, line=line, col=col) - return ')' - elif expected.startswith('term') and u'[' == val: - # start of [ any* ] block - seq.append(val, type_, line=line, col=col) - return ']' - elif expected.startswith('term') and u'{' == val: - # start of { any* } block - seq.append(val, type_, line=line, col=col) - return '}' - elif expected.startswith('term') and u'+' == val: - # unary operator "+" - seq.append(val, type_, line=line, col=col) - new['values'].append(val) - return 'number percentage dimension' - elif expected.startswith('term') and u'-' == val: - # unary "-" operator - seq.append(val, type_, line=line, col=col) - new['values'].append(val) - return 'number percentage dimension' - elif expected.startswith('term') and u'/' == val: - # font-size/line-height separator - seq.append(val, type_, line=line, col=col) - new['values'].append(val) - return 'number percentage dimension' - else: - new['wellformed'] = False - self._log.error(u'CSSValue: Unexpected char.', token) - return expected - - def _number_percentage_dimension(expected, seq, token, tokenizer=None): - # NUMBER PERCENTAGE DIMENSION after -/+ or operator - type_, val, line, col = token - new['rawvalues'].append(val) - if expected.startswith('term') or expected == 'number percentage dimension': - # normal value - if new['values'] and new['values'][-1] in (u'-', u'+'): - new['values'][-1] += val - else: - new['values'].append(val) - seq.append(val, type_, line=line, col=col) - return 'operator' - elif 'operator' == expected: - # expected S but token which is ok - if new['values'] and new['values'][-1] in (u'-', u'+'): - new['values'][-1] += val - else: - new['values'].append(u' ') - seq.append(u' ', 'separator') # self._prods.S - new['values'].append(val) - seq.append(val, type_, line=line, col=col) - return 'operator' - elif expected in ('funcend', ')', ']', '}'): - # a block - seq.appendToVal(val) - return expected - else: - new['wellformed'] = False - self._log.error(u'CSSValue: Unexpected token.', token) - return expected - - def _string_ident_uri(expected, seq, token, tokenizer=None): - # STRING IDENT URI - type_, val, line, col = token - - new['rawvalues'].append(val) - if expected.startswith('term'): - # normal value - if self._prods.STRING == type_: - val = self._stringtokenvalue(token) - elif self._prods.URI == type_: - val = self._uritokenvalue(token) - new['values'].append(val) - seq.append(val, type_, line=line, col=col) - return 'operator' - elif 'operator' == expected: - # expected S but still ok - if self._prods.STRING == type_: - val = self._stringtokenvalue(token) - elif self._prods.URI == type_: - val = self._uritokenvalue(token) - new['values'].append(u' ') - new['values'].append(val) - seq.append(u' ', 'separator') # self._prods.S - seq.append(val, type_, line=line, col=col) - return 'operator' - elif expected in ('funcend', ')', ']', '}'): - # a block - seq.appendToVal(val) - return expected - else: - new['wellformed'] = False - self._log.error(u'CSSValue: Unexpected token.', token) - return expected - - def _hash(expected, seq, token, tokenizer=None): - # HASH - type_, val, line, col = token - new['rawvalues'].append(val) - - val = CSSColor(cssText=token) - type_ = type(val) - if expected.startswith('term'): - # normal value - new['values'].append(val) - seq.append(val, type_, line=line, col=col) - return 'operator' - elif 'operator' == expected: - # expected S but still ok - new['values'].append(u' ') - new['values'].append(val) - seq.append(u' ', 'separator') # self._prods.S - seq.append(val, type_, line=line, col=col) - return 'operator' - elif expected in ('funcend', ')', ']', '}'): - # a block - seq.appendToVal(val) - return expected - else: - new['wellformed'] = False - self._log.error(u'CSSValue: Unexpected token.', token) - return expected - - def _function(expected, seq, token, tokenizer=None): - # FUNCTION incl colors - type_, val, line, col = token - - if self._normalize(val) in ('rgb(', 'rgba(', 'hsl(', 'hsla('): - # a CSSColor - val = CSSColor(cssText=(token, tokenizer)) - type_ = type(val) - seq.append(val, type_, line=line, col=col) - new['values'].append(val) - new['rawvalues'].append(val.cssText) - return 'operator' - - new['rawvalues'].append(val) - - if expected.startswith('term'): - # normal value but add if funcend is found - seq.append(val, type_, line=line, col=col) - return 'funcend' - elif 'operator' == expected: - # normal value but add if funcend is found - seq.append(u' ', 'separator') # self._prods.S - seq.append(val, type_, line=line, col=col) - return 'funcend' - elif expected in ('funcend', ')', ']', '}'): - # a block - seq.appendToVal(val) - return expected - else: - new['wellformed'] = False - self._log.error(u'CSSValue: Unexpected token.', token) - return expected - - tokenizer = self._tokenize2(cssText) - linetoken = self._nexttoken(tokenizer) - if not linetoken: - self._log.error(u'CSSValue: Unknown syntax or no value: %r.' % - self._valuestr(cssText)) - else: - newseq = self._tempSeq() # [] - wellformed, expected = self._parse(expected='term', - seq=newseq, tokenizer=tokenizer,initialtoken=linetoken, - productions={'S': _S, - 'CHAR': _char, - - 'NUMBER': _number_percentage_dimension, - 'PERCENTAGE': _number_percentage_dimension, - 'DIMENSION': _number_percentage_dimension, - - 'STRING': _string_ident_uri, - 'IDENT': _string_ident_uri, - 'URI': _string_ident_uri, - 'HASH': _hash, - 'UNICODE-RANGE': _string_ident_uri, #? - - 'FUNCTION': _function - }) - - wellformed = wellformed and new['wellformed'] - - # post conditions - def lastseqvalue(seq): - """find last actual value in seq, not COMMENT!""" - for i, item in enumerate(reversed(seq)): - if 'COMMENT' != item.type: - return len(seq)-1-i, item.value - else: - return 0, None - lastpos, lastval = lastseqvalue(newseq) - - if expected.startswith('term') and lastval != u' ' or ( - expected in ('funcend', ')', ']', '}')): - wellformed = False - self._log.error(u'CSSValue: Incomplete value: %r.' % - self._valuestr(cssText)) - - if not new['values']: - wellformed = False - self._log.error(u'CSSValue: Unknown syntax or no value: %r.' % - self._valuestr(cssText)) - - else: - # remove last token if 'separator' - if lastval == u' ': - del newseq[lastpos] + # used as operator is , / or S + nextSor = u',/' + + term = Choice(Sequence(PreDef.unary(), + Choice(PreDef.number(nextSor=nextSor), + PreDef.percentage(nextSor=nextSor), + PreDef.dimension(nextSor=nextSor))), + PreDef.string(nextSor=nextSor), + PreDef.ident(nextSor=nextSor), + PreDef.uri(nextSor=nextSor), + PreDef.hexcolor(nextSor=nextSor), + # special case IE only expression + Prod(name='expression', + match=lambda t, v: t == self._prods.FUNCTION and + cssutils.helper.normalize(v) == 'expression(', + nextSor=nextSor, + toSeq=lambda t, tokens: (ExpressionValue.name, + ExpressionValue(cssutils.helper.pushtoken(t, + 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])), + optional=True) + # CSSValue PRODUCTIONS + valueprods = Sequence(term, + Sequence(operator, # mayEnd this Sequence if whitespace + term, + minmax=lambda: (0, None))) + # parse + wellformed, seq, store, unusedtokens = ProdParser().parse(cssText, + u'CSSValue', + valueprods) + if wellformed: + # - count actual values and set firstvalue which is used later on + # - combine comma separated list, e.g. font-family to a single item + # - remove S which should be an operator but is no needed + count, firstvalue = 0, () + newseq = self._tempSeq() + i, end = 0, len(seq) + while i < end: + 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 + 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 + elif item.value == u'/': + # counts as a single one + newseq.appendItem(item) - self._linetoken = linetoken # used for line report - self._setSeq(newseq) + elif item.value == u'+' or item.value == u'-': + # combine +- and following number or other + i += 1 + try: + next = seq[i] + except IndexError: + firstvalue = () # raised later + break + + newval = item.value + next.value + newseq.append(newval, next.type, + item.line, item.col) + if not firstvalue: + firstvalue = (newval, next.type) + count += 1 - self.valid = self._validate(u''.join(new['rawvalues'])) - - if len(new['values']) == 1 and new['values'][0] == u'inherit': - self._value = u'inherit' - self._cssValueType = CSSValue.CSS_INHERIT - self.__class__ = CSSValue # reset - elif len(new['values']) == 1: - self.__class__ = CSSPrimitiveValue - self._init() #inits CSSPrimitiveValue - elif len(new['values']) > 1 and\ - len(new['values']) == new['commas'] + 1: - # e.g. value for font-family: a, b - self.__class__ = CSSPrimitiveValue - self._init() #inits CSSPrimitiveValue - elif len(new['values']) > 1: - # separated by S - self.__class__ = CSSValueList - self._init() # inits CSSValueList + elif item.type != cssutils.css.CSSComment: + newseq.appendItem(item) + if not firstvalue: + firstvalue = (item.value, item.type) + count += 1 + else: - self._cssValueType = CSSValue.CSS_CUSTOM - self.__class__ = CSSValue # reset - - self.wellformed = wellformed - - cssText = property(_getCssText, _setCssText, - doc="A string representation of the current value.") - - def _getCssValueType(self): - if hasattr(self, '_cssValueType'): - return self._cssValueType - - cssValueType = property(_getCssValueType, - doc="A (readonly) code defining the type of the value as defined above.") - - def _getCssValueTypeString(self): - t = self.cssValueType - if t is not None: # may be 0! - return CSSValue._typestrings[t] - else: - return None - - cssValueTypeString = property(_getCssValueTypeString, - doc="cssutils: Name of cssValueType of this CSSValue (readonly).") - - def _validate(self, value=None, profile=None): - """ - validates value against _propertyName context if given - """ - valid = False - if self._value: - if self._propertyName and self._propertyName in profiles.propertiesByProfile(): - valid, validprofile = \ - profiles.validateWithProfile(self._propertyName, - self._normalize(self._value)) - if not validprofile: - validprofile = u'' - - if not valid: - self._log.warn( - u'CSSValue: Invalid value for %s property "%s: %s".' % - (validprofile, self._propertyName, - self._value), neverraise=True) - elif profile and validprofile != profile: - self._log.warn( - u'CSSValue: Invalid value for %s property "%s: %s" but valid %s property.' % - (profile, self._propertyName, self._value, - validprofile), neverraise=True) - else: - self._log.debug( - u'CSSValue: Found valid %s property "%s: %s".' % - (validprofile, self._propertyName, self._value), - neverraise=True) + newseq.appendItem(item) + + i += 1 + + if not firstvalue: + self._log.error( + u'CSSValue: Unknown syntax or no value: %r.' % + self._valuestr(cssText)) else: - self._log.debug(u'CSSValue: Unable to validate as no or unknown property context set for value: %r' - % self._value, neverraise=True) - - if not value: - # if value is given this should not be saved - self.valid = valid - return valid + # ok and set + self._setSeq(newseq) + self.wellformed = wellformed + + if hasattr(self, '_value'): + # only in case of CSSPrimitiveValue, else remove! + del self._value - def _get_propertyName(self): - return self.__propertyName + if count == 1: + if isinstance(firstvalue[0], basestring) and\ + u'inherit' == cssutils.helper.normalize(firstvalue[0]): + self.__class__ = CSSValue + self._cssValueType = CSSValue.CSS_INHERIT + else: + self.__class__ = CSSPrimitiveValue + self._value = firstvalue + + elif count > 1: + self.__class__ = CSSValueList + # change items in list to specific type (primitive etc) + newseq = self._tempSeq() + commalist = [] + nexttocommalist = False + + def itemValue(item): + "Reserialized simple item.value" + if self._prods.STRING == item.type: + return cssutils.helper.string(item.value) + elif self._prods.URI == item.type: + return cssutils.helper.uri(item.value) + else: + return item.value + + def saveifcommalist(commalist, newseq): + """ + saves items in commalist to seq and items + if anything in there + """ + if commalist: + newseq.replace(-1, + CSSPrimitiveValue(cssText=u''.join( + commalist)), + CSSPrimitiveValue, + newseq[-1].line, + newseq[-1].col) + del commalist[:] + + for i, item in enumerate(self._seq): + if item.type in (self._prods.DIMENSION, + self._prods.FUNCTION, + self._prods.HASH, + self._prods.IDENT, + self._prods.NUMBER, + self._prods.PERCENTAGE, + self._prods.STRING, + self._prods.URI): + if nexttocommalist: + # wait until complete + commalist.append(itemValue(item)) + else: + saveifcommalist(commalist, newseq) + # append new item + if hasattr(item.value, 'cssText'): + newseq.append(item.value, + item.value.__class__, + item.line, item.col) - def _set_propertyName(self, _propertyName): - self.__propertyName = _propertyName - self._validate() + else: + newseq.append(CSSPrimitiveValue(itemValue(item)), + CSSPrimitiveValue, + item.line, item.col) - _propertyName = property(_get_propertyName, _set_propertyName, - doc="cssutils: Property this values is validated against") + nexttocommalist = False + + elif u',' == item.value: + if not commalist: + # save last item to commalist + commalist.append(itemValue(self._seq[i-1])) + commalist.append(u',') + nexttocommalist = True + + else: + if nexttocommalist: + commalist.append(item.value.cssText) + else: + newseq.appendItem(item) - def __repr__(self): - return "cssutils.css.%s(%r, _propertyName=%r)" % ( - self.__class__.__name__, self.cssText, self._propertyName) + saveifcommalist(commalist, newseq) + self._setSeq(newseq) + else: + # should not happen... + self.__class__ = CSSValue + self._cssValueType = CSSValue.CSS_CUSTOM - def __str__(self): - return "" % ( - self.__class__.__name__, self.cssValueTypeString, - self.cssText, self._propertyName, self.valid, id(self)) + cssText = property(lambda self: cssutils.ser.do_css_CSSValue(self), + _setCssText, + doc="A string representation of the current value.") + + cssValueType = property(lambda self: self._cssValueType, + doc="A (readonly) code defining the type of the value.") + + cssValueTypeString = property( + lambda self: CSSValue._typestrings.get(self.cssValueType, None), + doc="(readonly) Name of cssValueType.") class CSSPrimitiveValue(CSSValue): - """ - represents a single CSS Value. May be used to determine the value of a + """Represents a single CSS Value. May be used to determine the value of a specific style property currently set in a block or to set a specific style property explicitly within the block. Might be obtained from the getPropertyCSSValue method of CSSStyleDeclaration. @@ -563,6 +322,8 @@ class CSSPrimitiveValue(CSSValue): # constant: type of this CSSValue class cssValueType = CSSValue.CSS_PRIMITIVE_VALUE + __types = cssutils.cssproductions.CSSProductions + # An integer indicating which type of unit applies to the value. CSS_UNKNOWN = 0 # only obtainable via cssText CSS_NUMBER = 1 @@ -593,17 +354,16 @@ class CSSPrimitiveValue(CSSValue): # NOT OFFICIAL: CSS_RGBACOLOR = 26 - _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_DEG, CSS_RAD, CSS_GRAD, CSS_MS, CSS_S, - CSS_HZ, CSS_KHZ, CSS_DIMENSION - ] - _stringtypes = [CSS_ATTR, CSS_IDENT, CSS_STRING, CSS_URI] - _countertypes = [CSS_COUNTER] - _recttypes = [CSS_RECT] - _rbgtypes = [CSS_RGBCOLOR, CSS_RGBACOLOR] - - _reNumDim = re.compile(ur'^(.*?)([a-z]+|%)$', re.I| re.U|re.X) + CSS_HZ, CSS_KHZ, CSS_DIMENSION) + _stringtypes = (CSS_ATTR, CSS_IDENT, CSS_STRING, CSS_URI) + _countertypes = (CSS_COUNTER,) + _recttypes = (CSS_RECT,) + _rbgtypes = (CSS_RGBCOLOR, CSS_RGBACOLOR) + _lengthtypes = (CSS_NUMBER, CSS_EMS, CSS_EXS, + CSS_PX, CSS_CM, CSS_MM, CSS_IN, CSS_PT, CSS_PC) # oldtype: newType: converterfunc _converter = { @@ -645,143 +405,95 @@ class CSSPrimitiveValue(CSSValue): # TODO: convert deg <-> rad <-> grad } - def __init__(self, cssText=None, readonly=False, _propertyName=None): - """ - see CSSPrimitiveValue.__init__() - """ + def __init__(self, cssText=None, readonly=False): + """See CSSPrimitiveValue.__init__()""" super(CSSPrimitiveValue, self).__init__(cssText=cssText, - readonly=readonly, - _propertyName=_propertyName) + readonly=readonly) - #(String representation for unit types, token type of unit type, detail) - # used to detect primitiveType and for __repr__ - self._init() + def __str__(self): + return "" % ( + self.__class__.__name__, self.primitiveTypeString, + self.cssText, id(self)) - def _init(self): - # _unitinfos must be set here as self._prods is not known before - self._unitinfos = [ - ('CSS_UNKNOWN', None, None), - ('CSS_NUMBER', self._prods.NUMBER, None), - ('CSS_PERCENTAGE', self._prods.PERCENTAGE, None), - ('CSS_EMS', self._prods.DIMENSION, 'em'), - ('CSS_EXS', self._prods.DIMENSION, 'ex'), - ('CSS_PX', self._prods.DIMENSION, 'px'), - ('CSS_CM', self._prods.DIMENSION, 'cm'), - ('CSS_MM', self._prods.DIMENSION, 'mm'), - ('CSS_IN', self._prods.DIMENSION, 'in'), - ('CSS_PT', self._prods.DIMENSION, 'pt'), - ('CSS_PC', self._prods.DIMENSION, 'pc'), - ('CSS_DEG', self._prods.DIMENSION, 'deg'), - ('CSS_RAD', self._prods.DIMENSION, 'rad'), - ('CSS_GRAD', self._prods.DIMENSION, 'grad'), - ('CSS_MS', self._prods.DIMENSION, 'ms'), - ('CSS_S', self._prods.DIMENSION, 's'), - ('CSS_HZ', self._prods.DIMENSION, 'hz'), - ('CSS_KHZ', self._prods.DIMENSION, 'khz'), - ('CSS_DIMENSION', self._prods.DIMENSION, None), - ('CSS_STRING', self._prods.STRING, None), - ('CSS_URI', self._prods.URI, None), - ('CSS_IDENT', self._prods.IDENT, None), - ('CSS_ATTR', self._prods.FUNCTION, 'attr('), - ('CSS_COUNTER', self._prods.FUNCTION, 'counter('), - ('CSS_RECT', self._prods.FUNCTION, 'rect('), - ('CSS_RGBCOLOR', self._prods.FUNCTION, 'rgb('), - ('CSS_RGBACOLOR', self._prods.FUNCTION, 'rgba('), - ] + _unitnames = ['CSS_UNKNOWN', + 'CSS_NUMBER', 'CSS_PERCENTAGE', + 'CSS_EMS', 'CSS_EXS', + 'CSS_PX', + 'CSS_CM', 'CSS_MM', + 'CSS_IN', + 'CSS_PT', 'CSS_PC', + 'CSS_DEG', 'CSS_RAD', 'CSS_GRAD', + 'CSS_MS', 'CSS_S', + 'CSS_HZ', 'CSS_KHZ', + 'CSS_DIMENSION', + 'CSS_STRING', 'CSS_URI', 'CSS_IDENT', + 'CSS_ATTR', 'CSS_COUNTER', 'CSS_RECT', + 'CSS_RGBCOLOR', 'CSS_RGBACOLOR', + ] + + _reNumDim = re.compile(ur'([+-]?\d*\.\d+|[+-]?\d+)(.*)$', re.I| re.U|re.X) + + def _unitDIMENSION(value): + """Check val for dimension name.""" + units = {'em': 'CSS_EMS', 'ex': 'CSS_EXS', + 'px': 'CSS_PX', + 'cm': 'CSS_CM', 'mm': 'CSS_MM', + 'in': 'CSS_IN', + 'pt': 'CSS_PT', 'pc': 'CSS_PC', + 'deg': 'CSS_DEG', 'rad': 'CSS_RAD', 'grad': 'CSS_GRAD', + 'ms': 'CSS_MS', 's': 'CSS_S', + 'hz': 'CSS_HZ', 'khz': 'CSS_KHZ' + } + val, dim = CSSPrimitiveValue._reNumDim.findall(cssutils.helper.normalize(value))[0] + return units.get(dim, 'CSS_DIMENSION') + + def _unitFUNCTION(value): + """Check val for function name.""" + units = {'attr(': 'CSS_ATTR', + 'counter(': 'CSS_COUNTER', + 'rect(': 'CSS_RECT', + 'rgb(': 'CSS_RGBCOLOR', + 'rgba(': 'CSS_RGBACOLOR', + } + return units.get(re.findall(ur'^(.*?\()', + cssutils.helper.normalize(value.cssText), + re.U)[0], + 'CSS_UNKNOWN') + + __unitbytype = { + __types.NUMBER: 'CSS_NUMBER', + __types.PERCENTAGE: 'CSS_PERCENTAGE', + __types.STRING: 'CSS_STRING', + __types.URI: 'CSS_URI', + __types.IDENT: 'CSS_IDENT', + __types.HASH: 'CSS_RGBCOLOR', + __types.DIMENSION: _unitDIMENSION, + __types.FUNCTION: _unitFUNCTION + } def __set_primitiveType(self): - """ - primitiveType is readonly but is set lazy if accessed - no value is given as self._value is used - """ - primitiveType = self.CSS_UNKNOWN + """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') + if callable(pt): + # multiple options, check value too + pt = pt(val) + self._primitiveType = getattr(self, pt) - for item in self.seq: - if item.type == self._prods.URI: - primitiveType = self.CSS_URI - break - elif item.type == self._prods.STRING: - primitiveType = self.CSS_STRING - break - else: - - _floatType = False # if unary expect NUMBER DIMENSION or PERCENTAGE - tokenizer = self._tokenize2(self._value) - t = self._nexttoken(tokenizer) - if not t: - self._log.error(u'CSSPrimitiveValue: No value.') - - # unary operator: - if self._tokenvalue(t) in (u'-', u'+'): - t = self._nexttoken(tokenizer) - if not t: - self._log.error(u'CSSPrimitiveValue: No value.') - - _floatType = True - - # check for font1, "font2" etc which is treated as ONE string - fontstring = 0 # should be at leayst 2 - expected = 'ident or string' - tokenizer = self._tokenize2(self._value) # add used tokens again - for token in tokenizer: - val, typ = self._tokenvalue(token, normalize=True), self._type(token) - if expected == 'ident or string' and typ in ( - self._prods.IDENT, self._prods.STRING): - expected = 'comma' - fontstring += 1 - elif expected == 'comma' and val == ',': - expected = 'ident or string' - fontstring += 1 - elif typ in ('separator', self._prods.S, self._prods.COMMENT): - continue - else: - fontstring = False - break - - if fontstring > 2: - # special case: e.g. for font-family: a, b; only COMMA IDENT and STRING - primitiveType = CSSPrimitiveValue.CSS_STRING - elif self._type(t) == self._prods.HASH: - # special case, maybe should be converted to rgb in any case? - primitiveType = CSSPrimitiveValue.CSS_RGBCOLOR - else: - for i, (name, tokentype, search) in enumerate(self._unitinfos): - val, typ = self._tokenvalue(t, normalize=True), self._type(t) - if typ == tokentype: - if typ == self._prods.DIMENSION: - if not search: - primitiveType = i - break - elif re.match(ur'^[^a-z]*(%s)$' % search, val): - primitiveType = i - break - elif typ == self._prods.FUNCTION: - if not search: - primitiveType = i - break - elif val.startswith(search): - primitiveType = i - break - else: - primitiveType = i - break - - if _floatType and primitiveType not in self._floattypes: - # - or + only expected before floattype - primitiveType = self.CSS_UNKNOWN - - self._primitiveType = primitiveType - def _getPrimitiveType(self): if not hasattr(self, '_primitivetype'): self.__set_primitiveType() return self._primitiveType primitiveType = property(_getPrimitiveType, - doc="READONLY: The type of the value as defined by the constants specified above.") + doc="(readonly) The type of the value as defined " + "by the constants in this class.") def _getPrimitiveTypeString(self): - return self._unitinfos[self.primitiveType][0] + return self._unitnames[self.primitiveType] primitiveTypeString = property(_getPrimitiveTypeString, doc="Name of primitive type of this value.") @@ -789,50 +501,54 @@ class CSSPrimitiveValue(CSSValue): def _getCSSPrimitiveTypeString(self, type): "get TypeString by given type which may be unknown, used by setters" try: - return self._unitinfos[type][0] + return self._unitnames[type] except (IndexError, TypeError): return u'%r (UNKNOWN TYPE)' % type - def __getValDim(self): - "splits self._value in numerical and dimension part" + def _getNumDim(self, value=None): + "Split self._value in numerical and dimension part." + if value is None: + value = cssutils.helper.normalize(self._value[0]) + try: - val, dim = self._reNumDim.findall(self._value)[0] + val, dim = CSSPrimitiveValue._reNumDim.findall(value)[0] except IndexError: - val, dim = self._value, u'' + val, dim = value, u'' try: val = float(val) + if val == int(val): + val = int(val) except ValueError: raise xml.dom.InvalidAccessErr( - u'CSSPrimitiveValue: No float value %r' - % (self._value)) + u'CSSPrimitiveValue: No float value %r' % self._value[0]) return val, dim def getFloatValue(self, unitType=None): - """ - (DOM method) This method is used to get a float value in a + """(DOM) This method is used to get a float value in a specified unit. If this CSS value doesn't contain a float value or can't be converted into the specified unit, a DOMException is raised. - unitType + :param unitType: to get the float value. The unit code can only be a float unit type (i.e. CSS_NUMBER, CSS_PERCENTAGE, CSS_EMS, CSS_EXS, CSS_PX, CSS_CM, CSS_MM, CSS_IN, CSS_PT, CSS_PC, CSS_DEG, CSS_RAD, CSS_GRAD, CSS_MS, CSS_S, CSS_HZ, CSS_KHZ, CSS_DIMENSION) or None in which case the current dimension is used. - returns not necessarily a float but some cases just an integer - e.g. if the value is ``1px`` it return ``1`` and **not** ``1.0`` + :returns: + not necessarily a float but some cases just an integer + e.g. if the value is ``1px`` it return ``1`` and **not** ``1.0`` - conversions might return strange values like 1.000000000001 + Conversions might return strange values like 1.000000000001 """ if unitType is not None and unitType not in self._floattypes: raise xml.dom.InvalidAccessErr( u'unitType Parameter is not a float type') - val, dim = self.__getValDim() - + val, dim = self._getNumDim() + if unitType is not None and self.primitiveType != unitType: # convert if needed try: @@ -849,25 +565,26 @@ class CSSPrimitiveValue(CSSValue): return val def setFloatValue(self, unitType, floatValue): - """ - (DOM method) A method to set the float value with a specified unit. + """(DOM) A method to set the float value with a specified unit. If the property attached with this value can not accept the specified unit or the float value, the value will be unchanged and a DOMException will be raised. - unitType + :param unitType: a unit code as defined above. The unit code can only be a float unit type - floatValue + :param floatValue: the new float value which does not have to be a float value but may simple be an int e.g. if setting:: setFloatValue(CSS_PX, 1) - raises DOMException - - INVALID_ACCESS_ERR: Raised if the attached property doesn't - support the float value or the unit type. - - NO_MODIFICATION_ALLOWED_ERR: Raised if this property is readonly. + :exceptions: + - :exc:`~xml.dom.InvalidAccessErr`: + Raised if the attached property doesn't + support the float value or the unit type. + - :exc:`~xml.dom.NoModificationAllowedErr`: + Raised if this property is readonly. """ self._checkReadonly() if unitType not in self._floattypes: @@ -881,13 +598,11 @@ class CSSPrimitiveValue(CSSValue): u'CSSPrimitiveValue: floatValue %r is not a float' % floatValue) - oldval, dim = self.__getValDim() - + oldval, dim = self._getNumDim() if self.primitiveType != unitType: # convert if possible try: - val = self._converter[ - unitType, self.primitiveType](val) + val = self._converter[unitType, self.primitiveType](val) except KeyError: raise xml.dom.InvalidAccessErr( u'CSSPrimitiveValue: Cannot coerce primitiveType %r to %r' @@ -900,8 +615,7 @@ class CSSPrimitiveValue(CSSValue): self.cssText = '%s%s' % (val, dim) def getStringValue(self): - """ - (DOM method) This method is used to get the string value. If the + """(DOM) This method is used to get the string value. If the CSS value doesn't contain a string value, a DOMException is raised. Some properties (like 'font-family' or 'voice-family') @@ -915,43 +629,36 @@ class CSSPrimitiveValue(CSSValue): u'CSSPrimitiveValue %r is not a string type' % self.primitiveTypeString) - if CSSPrimitiveValue.CSS_STRING == self.primitiveType: - # _stringtokenvalue expects tuple with at least 2 - return self._stringtokenvalue((None,self._value)) - elif CSSPrimitiveValue.CSS_URI == self.primitiveType: - # _uritokenvalue expects tuple with at least 2 - return self._uritokenvalue((None, self._value)) - elif CSSPrimitiveValue.CSS_ATTR == self.primitiveType: - return self._value[5:-1] + if CSSPrimitiveValue.CSS_ATTR == self.primitiveType: + return self._value[0].cssText[5:-1] else: - return self._value + return self._value[0] def setStringValue(self, stringType, stringValue): - """ - (DOM method) A method to set the string value with the specified + """(DOM) A method to set the string value with the specified unit. If the property attached to this value can't accept the specified unit or the string value, the value will be unchanged and a DOMException will be raised. - stringType + :param stringType: a string code as defined above. The string code can only be a string unit type (i.e. CSS_STRING, CSS_URI, CSS_IDENT, and CSS_ATTR). - stringValue + :param stringValue: the new string value Only the actual value is expected so for (CSS_URI, "a") the new value will be ``url(a)``. For (CSS_STRING, "'a'") the new value will be ``"\\'a\\'"`` as the surrounding ``'`` are not part of the string value - raises - DOMException - - - INVALID_ACCESS_ERR: Raised if the CSS value doesn't contain a + :exceptions: + - :exc:`~xml.dom.InvalidAccessErr`: + Raised if the CSS value doesn't contain a string value or if the string value can't be converted into the specified unit. - - NO_MODIFICATION_ALLOWED_ERR: Raised if this property is readonly. + - :exc:`~xml.dom.NoModificationAllowedErr`: + Raised if this property is readonly. """ self._checkReadonly() # self not stringType @@ -972,27 +679,9 @@ class CSSPrimitiveValue(CSSValue): self._getCSSPrimitiveTypeString(stringType))) if CSSPrimitiveValue.CSS_STRING == self._primitiveType: - self.cssText = u'"%s"' % stringValue.replace(u'"', ur'\\"') + self.cssText = cssutils.helper.string(stringValue) elif CSSPrimitiveValue.CSS_URI == self._primitiveType: - # Some characters appearing in an unquoted URI, such as - # parentheses, commas, whitespace characters, single quotes - # (') and double quotes ("), must be escaped with a backslash - # so that the resulting URI value is a URI token: - # '\(', '\)', '\,'. - # - # Here the URI is set in quotes alltogether! - if u'(' in stringValue or\ - u')' in stringValue or\ - u',' in stringValue or\ - u'"' in stringValue or\ - u'\'' in stringValue or\ - u'\n' in stringValue or\ - u'\t' in stringValue or\ - u'\r' in stringValue or\ - u'\f' in stringValue or\ - u' ' in stringValue: - stringValue = '"%s"' % stringValue.replace(u'"', ur'\"') - self.cssText = u'url(%s)' % stringValue + self.cssText = cssutils.helper.uri(stringValue) elif CSSPrimitiveValue.CSS_ATTR == self._primitiveType: self.cssText = u'attr(%s)' % stringValue else: @@ -1000,11 +689,12 @@ class CSSPrimitiveValue(CSSValue): self._primitiveType = stringType def getCounterValue(self): - """ - (DOM method) This method is used to get the Counter value. If + """(DOM) This method is used to get the Counter value. If this CSS value doesn't contain a counter value, a DOMException is raised. Modification to the corresponding style property can be achieved using the Counter interface. + + **Not implemented.** """ if not self.CSS_COUNTER == self.primitiveType: raise xml.dom.InvalidAccessErr(u'Value is not a counter type') @@ -1012,24 +702,22 @@ class CSSPrimitiveValue(CSSValue): raise NotImplementedError() def getRGBColorValue(self): - """ - (DOM method) This method is used to get the RGB color. If this + """(DOM) This method is used to get the RGB color. If this CSS value doesn't contain a RGB color value, a DOMException is raised. Modification to the corresponding style property can be achieved using the RGBColor interface. """ - # TODO: what about coercing #000 to RGBColor? if self.primitiveType not in self._rbgtypes: - raise xml.dom.InvalidAccessErr(u'Value is not a RGB value') - # TODO: use RGBColor class - raise NotImplementedError() + raise xml.dom.InvalidAccessErr(u'Value is not a RGBColor value') + return RGBColor(self._value[0]) def getRectValue(self): - """ - (DOM method) This method is used to get the Rect value. If this CSS + """(DOM) This method is used to get the Rect value. If this CSS value doesn't contain a rect value, a DOMException is raised. Modification to the corresponding style property can be achieved using the Rect interface. + + **Not implemented.** """ if self.primitiveType not in self._recttypes: raise xml.dom.InvalidAccessErr(u'value is not a Rect value') @@ -1037,25 +725,19 @@ class CSSPrimitiveValue(CSSValue): raise NotImplementedError() def _getCssText(self): - """overwritten from CSSValue""" + """Overwrites CSSValue.""" return cssutils.ser.do_css_CSSPrimitiveValue(self) def _setCssText(self, cssText): - """use CSSValue's implementation""" + """Use CSSValue.""" return super(CSSPrimitiveValue, self)._setCssText(cssText) cssText = property(_getCssText, _setCssText, doc="A string representation of the current value.") - def __str__(self): - return "" % ( - self.__class__.__name__, self.primitiveTypeString, - self.cssText, self._propertyName, self.valid, id(self)) - class CSSValueList(CSSValue): - """ - The CSSValueList interface provides the abstraction of an ordered + """The CSSValueList interface provides the abstraction of an ordered collection of CSS values. Some properties allow an empty list into their syntax. In that case, @@ -1067,165 +749,57 @@ class CSSValueList(CSSValue): """ cssValueType = CSSValue.CSS_VALUE_LIST - def __init__(self, cssText=None, readonly=False, _propertyName=None): - """ - inits a new CSSValueList - """ - super(CSSValueList, self).__init__(cssText=cssText, - readonly=readonly, - _propertyName=_propertyName) - self._init() - - def _init(self): - "called by CSSValue if newly identified as CSSValueList" - # defines which values - ivalueseq, valueseq = 0, self._SHORTHANDPROPERTIES.get( - self._propertyName, []) + def __init__(self, cssText=None, readonly=False): + """Init a new CSSValueList""" + super(CSSValueList, self).__init__(cssText=cssText, readonly=readonly) self._items = [] - newseq = self._tempSeq(False) - i, max = 0, len(self.seq) - minus = None - while i < max: - item = self.seq[i] - type_, val, line, col = item.type, item.value, item.line, item.col - if u'-' == val: - if minus: # 2 "-" after another - self._log.error( # TODO: - u'CSSValueList: Unknown syntax: %r.' - % u''.join(self.seq)) - else: - minus = val - elif isinstance(val, basestring) and not type_ == 'separator' and\ - not u'/' == val: - if minus: - val = minus + val - minus = None - # TODO: complete - # if shorthand get new propname - if ivalueseq < len(valueseq): - propname, mandatory = valueseq[ivalueseq] - if mandatory: - ivalueseq += 1 - else: - propname = None - ivalueseq = len(valueseq) # end - else: - propname = self._propertyName + def __iter__(self): + "CSSValueList is iterable." + def itemsiter(): + for i in range (0, self.length): + yield self.item(i) + return itemsiter() - # TODO: more (do not check individual values for these props) - if propname in self._SHORTHANDPROPERTIES: - propname = None + def __str__(self): + return "" % ( + self.__class__.__name__, self.cssValueTypeString, + self.cssText, self.length, id(self)) - if i+1 < max and self.seq[i+1].value == u',': - # a comma separated list of values as ONE value - # e.g. font-family: a,b - # CSSValue already has removed extra S tokens! - fullvalue = [val] - - expected = 'comma' # or 'value' - for j in range(i+1, max): - item2 = self.seq[j] - typ2, val2, line2, col2 = (item2.type, item2.value, - item2.line, item2.col) - if u' ' == val2: - # end or a single value follows - break - elif 'value' == expected and val2 in u'-+': - # unary modifier - fullvalue.append(val2) - expected = 'value' - elif 'comma' == expected and u',' == val2: - fullvalue.append(val2) - expected = 'value' - elif 'value' == expected and u',' != val2: - if 'STRING' == typ2: - val2 = cssutils.ser._string(val2) - fullvalue.append(val2) - expected = 'comma' - else: - self._log.error( - u'CSSValueList: Unknown syntax: %r.' - % val2) - return - if expected == 'value': - self._log.error( # TODO: - u'CSSValueList: Unknown syntax: %r.' - % u''.join(self.seq)) - return - # setting _propertyName this way does not work - # for compound props like font! - i += len(fullvalue) - 1 - obj = CSSValue(cssText=u''.join(fullvalue), - _propertyName=propname) - else: - # a single value, u' ' or nothing should be following - if 'STRING' == type_: - val = cssutils.ser._string(val) - elif 'URI' == type_: - val = cssutils.ser._uri(val) - - obj = CSSValue(cssText=val, _propertyName=propname) - - self._items.append(obj) - newseq.append(obj, CSSValue) - - elif CSSColor == type_: - self._items.append(val) - newseq.append(val, CSSColor) - - else: - # S (or TODO: comment?) - newseq.append(val, type_) - - i += 1 - - self._setSeq(newseq) - - length = property(lambda self: len(self._items), - doc="(DOM attribute) The number of CSSValues in the list.") + def __items(self): + return [item for item in self._seq + if isinstance(item.value, CSSValue)] def item(self, index): - """ - (DOM method) Used to retrieve a CSSValue by ordinal index. The + """(DOM) Retrieve a CSSValue by ordinal `index`. The order in this collection represents the order of the values in the - CSS style property. If index is greater than or equal to the number - of values in the list, this returns None. + CSS style property. If `index` is greater than or equal to the number + of values in the list, this returns ``None``. """ try: - return self._items[index] + return self.__items()[index].value except IndexError: return None - def __iter__(self): - "CSSValueList is iterable" - return CSSValueList.__items(self) - - def __items(self): - "the iterator" - for i in range (0, self.length): - yield self.item(i) - - def __str__(self): - return "" % ( - self.__class__.__name__, self.cssValueTypeString, - self.cssText, self.length, self._propertyName, - self.valid, id(self)) - + length = property(lambda self: len(self.__items()), + doc="(DOM attribute) The number of CSSValues in the list.") + class CSSFunction(CSSPrimitiveValue): """A CSS function value like rect() etc.""" + name = u'CSSFunction' + primitiveType = CSSPrimitiveValue.CSS_UNKNOWN def __init__(self, cssText=None, readonly=False): """ Init a new CSSFunction - cssText + :param cssText: the parsable cssText of the value - readonly + :param readonly: defaults to False """ - super(CSSColor, self).__init__() + super(CSSFunction, self).__init__() self.valid = False self.wellformed = False if cssText is not None: @@ -1234,74 +808,97 @@ class CSSFunction(CSSPrimitiveValue): self._funcType = None self._readonly = readonly - - def _setCssText(self, cssText): - self._checkReadonly() - if False: - pass - else: - types = self._prods # rename! - valueProd = Prod(name='value', - match=lambda t, v: t in (types.NUMBER, types.PERCENTAGE), - toSeq=CSSPrimitiveValue, - toStore='parts' - ) - # COLOR PRODUCTION - funcProds = Sequence([ - Prod(name='FUNC', - match=lambda t, v: t == types.FUNCTION, - toStore='funcType' ), - Prod(**PreDef.sign), - valueProd, - # more values starting with Comma - # should use store where colorType is saved to - # define min and may, closure? - Sequence([Prod(**PreDef.comma), - Prod(**PreDef.sign), - valueProd], - minmax=lambda: (2, 2)), - Prod(**PreDef.funcEnd) - ]) - # store: colorType, parts - wellformed, seq, store, unusedtokens = ProdsParser().parse(cssText, - u'CSSFunction', - funcProds, - {'parts': []}) - - if wellformed: - self.wellformed = True - self._setSeq(seq) - self._funcType = self._normalize(store['colorType'].value[:-1]) - cssText = property(lambda self: cssutils.ser.do_css_CSSColor(self), - _setCssText) - - funcType = property(lambda self: self._funcType) - def __repr__(self): return "cssutils.css.%s(%r)" % (self.__class__.__name__, self.cssText) def __str__(self): - return "" % ( - self.__class__.__name__, self.colorType, self.cssText, + return "" % ( + self.__class__.__name__, self.primitiveTypeString, self.cssText, id(self)) - - - - -class CSSColor(CSSPrimitiveValue): - """A CSS color like RGB, RGBA or a simple value like `#000` or `red`.""" + def _productiondefinition(self): + """Return defintion used for parsing.""" + types = self._prods # rename! + valueProd = Prod(name='PrimitiveValue', + match=lambda t, v: t in (types.DIMENSION, + types.IDENT, + types.NUMBER, + 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]))), + Choice(Sequence(PreDef.unary(), + valueProd, + # more values starting with Comma + # should use store where colorType is saved to + # define min and may, closure? + Sequence(PreDef.comma(), + PreDef.unary(), + valueProd, + minmax=lambda: (0, 3)), + PreDef.funcEnd(stop=True)), + PreDef.funcEnd(stop=True)) + ) + return funcProds + + def _setCssText(self, cssText): + self._checkReadonly() + # store: colorType, parts + wellformed, seq, store, unusedtokens = ProdParser().parse(cssText, + self.name, + self._productiondefinition()) + if wellformed: + # combine +/- and following CSSPrimitiveValue, remove S + newseq = self._tempSeq() + i, end = 0, len(seq) + while i < end: + item = seq[i] + if item.type == self._prods.S: + pass + elif item.value == u'+' or item.value == u'-': + i += 1 + next = seq[i] + newval = next.value + if isinstance(newval, CSSPrimitiveValue): + newval.setFloatValue(newval.primitiveType, + float(item.value + str(newval.getFloatValue()))) + newseq.append(newval, next.type, + item.line, item.col) + else: + # expressions only? + newseq.appendItem(item) + newseq.appendItem(next) + else: + newseq.appendItem(item) + + i += 1 + + self.wellformed = True + self._setSeq(newseq) + self._funcType = newseq[0].value + + cssText = property(lambda self: cssutils.ser.do_css_RGBColor(self), + _setCssText) + + funcType = property(lambda self: self._funcType) + + +class RGBColor(CSSPrimitiveValue): + """A CSS color like RGB, RGBA or a simple value like `#000` or `red`.""" def __init__(self, cssText=None, readonly=False): """ - Init a new CSSColor + Init a new RGBColor - cssText + :param cssText: the parsable cssText of the value - readonly + :param readonly: defaults to False """ - super(CSSColor, self).__init__() + super(RGBColor, self).__init__() self._colorType = None self.valid = False self.wellformed = False @@ -1310,66 +907,6 @@ class CSSColor(CSSPrimitiveValue): self._readonly = readonly - def _setCssText(self, cssText): - self._checkReadonly() - if False: - pass - else: - types = self._prods # rename! - valueProd = Prod(name='value', - match=lambda t, v: t in (types.NUMBER, types.PERCENTAGE), - toSeq=CSSPrimitiveValue, - toStore='parts' - ) - # COLOR PRODUCTION - funccolor = Sequence([Prod(name='FUNC', - match=lambda t, v: self._normalize(v) in ('rgb(', 'rgba(', 'hsl(', 'hsla(') and t == types.FUNCTION, - toSeq=lambda v: self._normalize(v), - toStore='colorType' ), - PreDef.unary(), - valueProd, - # 2 or 3 more values starting with Comma - Sequence([PreDef.comma(), - PreDef.unary(), - valueProd], - minmax=lambda: (2,3)), - PreDef.funcEnd() - ] - ) - colorprods = Choice([funccolor, - Prod(name='HEX color', - match=lambda t, v: t == types.HASH and - len(v) == 4 or len(v) == 7, - toStore='colorType' - ), - Prod(name='named color', - match=lambda t, v: t == types.IDENT, - toStore='colorType' - ), - ] - ) - # store: colorType, parts - wellformed, seq, store, unusedtokens = ProdParser().parse(cssText, - u'CSSColor', - colorprods, - {'parts': []}) - - if wellformed: - self.wellformed = True - if store['colorType'].type == self._prods.HASH: - self._colorType = 'HEX' - elif store['colorType'].type == self._prods.IDENT: - self._colorType = 'Named Color' - else: - self._colorType = self._normalize(store['colorType'].value)[:-1] - - self._setSeq(seq) - - cssText = property(lambda self: cssutils.ser.do_css_CSSColor(self), - _setCssText) - - colorType = property(lambda self: self._colorType) - def __repr__(self): return "cssutils.css.%s(%r)" % (self.__class__.__name__, self.cssText) @@ -1377,3 +914,86 @@ class CSSColor(CSSPrimitiveValue): return "" % ( self.__class__.__name__, self.colorType, self.cssText, id(self)) + + def _setCssText(self, cssText): + self._checkReadonly() + types = self._prods # rename! + valueProd = Prod(name='value', + match=lambda t, v: t in (types.NUMBER, types.PERCENTAGE), + toSeq=lambda t, v: (CSSPrimitiveValue, CSSPrimitiveValue(v)), + toStore='parts' + ) + # COLOR PRODUCTION + funccolor = Sequence(Prod(name='FUNC', + match=lambda t, v: self._normalize(v) in ('rgb(', 'rgba(', 'hsl(', 'hsla(') and t == types.FUNCTION, + toSeq=lambda t, v: (t, self._normalize(v)), + toStore='colorType' ), + PreDef.unary(), + valueProd, + # 2 or 3 more values starting with Comma + Sequence(PreDef.comma(), + PreDef.unary(), + valueProd, + minmax=lambda: (2, 3)), + PreDef.funcEnd() + ) + colorprods = Choice(funccolor, + PreDef.hexcolor('colorType'), + Prod(name='named color', + match=lambda t, v: t == types.IDENT, + toStore='colorType' + ) + ) + # store: colorType, parts + wellformed, seq, store, unusedtokens = ProdParser().parse(cssText, + u'RGBColor', + colorprods, + {'parts': []}) + + if wellformed: + self.wellformed = True + if store['colorType'].type == self._prods.HASH: + self._colorType = 'HEX' + elif store['colorType'].type == self._prods.IDENT: + self._colorType = 'Named Color' + else: + self._colorType = self._normalize(store['colorType'].value)[:-1] + + self._setSeq(seq) + + cssText = property(lambda self: cssutils.ser.do_css_RGBColor(self), + _setCssText) + + colorType = property(lambda self: self._colorType) + + +class ExpressionValue(CSSFunction): + """Special IE only CSSFunction which may contain *anything*.""" + name = u'Expression (IE only)' + + def _productiondefinition(self): + """Return defintion used for parsing.""" + types = self._prods # rename! + funcProds = Sequence(Prod(name='expression', + match=lambda t, v: t == types.FUNCTION, + toSeq=lambda t, tokens: (t[0], cssutils.helper.normalize(t[1]))), + 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)))), + Prod(name='part', + match=lambda t, v: v != u')', + toSeq=lambda t, tokens: (t[0], t[1])), ), + minmax=lambda: (0, None)), + PreDef.funcEnd(stop=True)) + return funcProds + + def _getCssText(self): + return cssutils.ser.do_css_ExpressionValue(self) + + def _setCssText(self, cssText): + return super(ExpressionValue, self)._setCssText(cssText) + + cssText = property(_getCssText, _setCssText, + doc="A string representation of the current value.") diff --git a/src/cssutils/css/property.py b/src/cssutils/css/property.py index 9d8cd07932..04a5e3c0eb 100644 --- a/src/cssutils/css/property.py +++ b/src/cssutils/css/property.py @@ -1,55 +1,18 @@ -"""Property is a single CSS property in a CSSStyleDeclaration - -Internal use only, may be removed in the future! -""" +"""Property is a single CSS property in a CSSStyleDeclaration.""" __all__ = ['Property'] __docformat__ = 'restructuredtext' -__version__ = '$Id: property.py 1444 2008-08-31 18:45:35Z cthedot $' +__version__ = '$Id: property.py 1664 2009-02-07 22:47:09Z cthedot $' -import xml.dom -import cssutils -#import cssproperties +from cssutils.helper import Deprecated from cssutils.profiles import profiles from cssvalue import CSSValue -from cssutils.helper import Deprecated +import cssutils +import xml.dom class Property(cssutils.util.Base): - """ - (cssutils) a CSS property in a StyleDeclaration of a CSSStyleRule + """A CSS property in a StyleDeclaration of a CSSStyleRule (cssutils). - Properties - ========== - cssText - a parsable textual representation of this property - name - normalized name of the property, e.g. "color" when name is "c\olor" - (since 0.9.5) - literalname (since 0.9.5) - original name of the property in the source CSS which is not normalized - e.g. "C\\OLor" - cssValue - the relevant CSSValue instance for this property - value - the string value of the property, same as cssValue.cssText - priority - of the property (currently only u"important" or None) - literalpriority - original priority of the property in the source CSS which is not - normalized e.g. "IM\portant" - seqs - combination of a list for seq of name, a CSSValue object, and - a list for seq of priority (empty or [!important] currently) - valid - if this Property is valid - wellformed - if this Property is syntactically ok - - DEPRECATED normalname (since 0.9.5) - normalized name of the property, e.g. "color" when name is "c\olor" - - Format - ====== - :: + Format:: property = name : IDENT S* @@ -81,60 +44,67 @@ class Property(cssutils.util.Base): ; """ - def __init__(self, name=None, value=None, priority=u'', _mediaQuery=False): + def __init__(self, name=None, value=None, priority=u'', + _mediaQuery=False, _parent=None): """ - inits property - - name + :param name: a property name string (will be normalized) - value + :param value: a property value string - priority + :param priority: an optional priority string which currently must be u'', u'!important' or u'important' - _mediaQuery boolean - if True value is optional as used by MediaQuery objects + :param _mediaQuery: + if ``True`` value is optional (used by MediaQuery) + :param _parent: + the parent object, normally a + :class:`cssutils.css.CSSStyleDeclaration` """ super(Property, self).__init__() self.seqs = [[], None, []] - self.valid = False self.wellformed = False self._mediaQuery = _mediaQuery + self._parent = _parent + self._name = u'' + self._literalname = u'' if name: self.name = name - else: - self._name = u'' - self._literalname = u'' - self.__normalname = u'' # DEPRECATED if value: self.cssValue = value else: self.seqs[1] = CSSValue() + self._priority = u'' + self._literalpriority = u'' if priority: self.priority = priority - else: - self._priority = u'' - self._literalpriority = u'' + + def __repr__(self): + return "cssutils.css.%s(name=%r, value=%r, priority=%r)" % ( + self.__class__.__name__, + self.literalname, self.cssValue.cssText, self.priority) + + def __str__(self): + return "<%s.%s object name=%r value=%r priority=%r valid=%r at 0x%x>" % ( + self.__class__.__module__, self.__class__.__name__, + self.name, self.cssValue.cssText, self.priority, + self.valid, id(self)) def _getCssText(self): - """ - returns serialized property cssText - """ + """Return serialized property cssText.""" return cssutils.ser.do_Property(self) def _setCssText(self, cssText): """ - DOMException on setting - - - NO_MODIFICATION_ALLOWED_ERR: (CSSRule) - Raised if the rule is readonly. - - SYNTAX_ERR: (self) - Raised if the specified CSS string value has a syntax error and - is unparsable. + :exceptions: + - :exc:`~xml.dom.SyntaxErr`: + Raised if the specified CSS string value has a syntax error and + is unparsable. + - :exc:`~xml.dom.NoModificationAllowedErr`: + Raised if the rule is readonly. """ # check and prepare tokenlists for setting tokenizer = self._tokenize2(cssText) @@ -174,11 +144,14 @@ class Property(cssutils.util.Base): self._log.error(u'Property: No property value found: %r.' % self._valuestr(cssText), colontoken) - if wellformed: + if wellformed: self.wellformed = True self.name = nametokens self.cssValue = valuetokens self.priority = prioritytokens + + # also invalid values are set! + self.validate() else: self._log.error(u'Property: No property name found: %r.' % @@ -189,11 +162,10 @@ class Property(cssutils.util.Base): def _setName(self, name): """ - DOMException on setting - - - SYNTAX_ERR: (self) - Raised if the specified name has a syntax error and is - unparsable. + :exceptions: + - :exc:`~xml.dom.SyntaxErr`: + Raised if the specified name has a syntax error and is + unparsable. """ # for closures: must be a mutable new = {'literalname': None, @@ -233,43 +205,42 @@ class Property(cssutils.util.Base): self.wellformed = True self._literalname = new['literalname'] self._name = self._normalize(self._literalname) - self.__normalname = self._name # DEPRECATED self.seqs[0] = newseq - # validate - if self._name not in profiles.propertiesByProfile(): - self.valid = False - tokenizer=self._tokenize2(name) - self._log.warn(u'Property: Unknown Property: %r.' % - new['literalname'], token=token, neverraise=True) +# # validate + if self._name not in profiles.knownnames: + # self.valid = False + self._log.warn(u'Property: Unknown Property.', + token=token, neverraise=True) else: - self.valid = True - if self.cssValue: - self.cssValue._propertyName = self._name - self.valid = self.cssValue.valid + pass +# self.valid = True +# if self.cssValue: +# self.cssValue._propertyName = self._name +# #self.valid = self.cssValue.valid else: self.wellformed = False name = property(lambda self: self._name, _setName, - doc="Name of this property") - + doc="Name of this property.") + literalname = property(lambda self: self._literalname, - doc="Readonly literal (not normalized) name of this property") + doc="Readonly literal (not normalized) name " + "of this property") def _getCSSValue(self): return self.seqs[1] def _setCSSValue(self, cssText): """ - see css.CSSValue + See css.CSSValue - DOMException on setting? - - - SYNTAX_ERR: (self) + :exceptions: + - :exc:`~xml.dom.SyntaxErr`: Raised if the specified CSS string value has a syntax error (according to the attached property) or is unparsable. - - TODO: INVALID_MODIFICATION_ERR: - Raised if the specified CSS string value represents a different + - :exc:`~xml.dom.InvalidModificationErr`: + TODO: Raised if the specified CSS string value represents a different type of values than the values allowed by the CSS property. """ if self._mediaQuery and not cssText: @@ -279,25 +250,24 @@ class Property(cssutils.util.Base): self.seqs[1] = CSSValue() cssvalue = self.seqs[1] - cssvalue._propertyName = self.name cssvalue.cssText = cssText - if cssvalue._value and cssvalue.wellformed: + if cssvalue.wellformed: #cssvalue._value and self.seqs[1] = cssvalue - self.valid = self.valid and cssvalue.valid self.wellformed = self.wellformed and cssvalue.wellformed cssValue = property(_getCSSValue, _setCSSValue, doc="(cssutils) CSSValue object of this property") + def _getValue(self): if self.cssValue: - return self.cssValue._value + return self.cssValue.cssText # _value # [0] else: return u'' def _setValue(self, value): self.cssValue.cssText = value - self.valid = self.valid and self.cssValue.valid +# self.valid = self.valid and self.cssValue.valid self.wellformed = self.wellformed and self.cssValue.wellformed value = property(_getValue, _setValue, @@ -308,9 +278,7 @@ class Property(cssutils.util.Base): priority a string, currently either u'', u'!important' or u'important' - Format - ====== - :: + Format:: prio : IMPORTANT_SYM S* @@ -318,14 +286,13 @@ class Property(cssutils.util.Base): "!"{w}"important" {return IMPORTANT_SYM;} - DOMException on setting - - - SYNTAX_ERR: (self) - Raised if the specified priority has a syntax error and is - unparsable. - In this case a priority not equal to None, "" or "!{w}important". - As CSSOM defines CSSStyleDeclaration.getPropertyPriority resulting in - u'important' this value is also allowed to set a Properties priority + :exceptions: + - :exc:`~xml.dom.SyntaxErr`: + Raised if the specified priority has a syntax error and is + unparsable. + In this case a priority not equal to None, "" or "!{w}important". + As CSSOM defines CSSStyleDeclaration.getPropertyPriority resulting in + u'important' this value is also allowed to set a Properties priority """ if self._mediaQuery: self._priority = u'' @@ -356,8 +323,7 @@ class Property(cssutils.util.Base): def _ident(expected, seq, token, tokenizer=None): # "important" val = self._tokenvalue(token) - normalval = self._tokenvalue(token, normalize=True) - if 'important' == expected == normalval: + if 'important' == expected: new['literalpriority'] = val seq.append(val) return 'EOF' @@ -378,38 +344,66 @@ class Property(cssutils.util.Base): if priority and not new['literalpriority']: wellformed = False self._log.info(u'Property: Invalid priority: %r.' % - self._valuestr(priority)) + self._valuestr(priority)) if wellformed: self.wellformed = self.wellformed and wellformed self._literalpriority = new['literalpriority'] self._priority = self._normalize(self.literalpriority) self.seqs[2] = newseq - - # validate + # validate priority if self._priority not in (u'', u'important'): - self.valid = False - self._log.info(u'Property: No CSS2 priority value: %r.' % - self._priority, neverraise=True) + self._log.error(u'Property: No CSS priority value: %r.' % + self._priority) priority = property(lambda self: self._priority, _setPriority, - doc="(cssutils) Priority of this property") + doc="Priority of this property.") literalpriority = property(lambda self: self._literalpriority, doc="Readonly literal (not normalized) priority of this property") - def __repr__(self): - return "cssutils.css.%s(name=%r, value=%r, priority=%r)" % ( - self.__class__.__name__, - self.literalname, self.cssValue.cssText, self.priority) + def validate(self, profile=None): + """Validate value against `profile`. + + :param profile: + A profile name used for validating. If no `profile` is given + ``Property.profiles + """ + valid = False + + if self.name and self.value: + if profile is None: + usedprofile = cssutils.profiles.defaultprofile + else: + usedprofile = profile + + if self.name in profiles.knownnames: + valid, validprofiles = profiles.validateWithProfile(self.name, + self.value, + usedprofile) - def __str__(self): - return "<%s.%s object name=%r value=%r priority=%r at 0x%x>" % ( - self.__class__.__module__, self.__class__.__name__, - self.name, self.cssValue.cssText, self.priority, id(self)) + if not valid: + self._log.error(u'Property: Invalid value for "%s" property: %s: %s' + % (u'/'.join(validprofiles), + self.name, + self.value), + neverraise=True) + elif valid and (usedprofile and usedprofile not in validprofiles): + self._log.warn(u'Property: Not valid for profile "%s": %s: %s' + % (usedprofile, self.name, self.value), + neverraise=True) + + if valid: + self._log.info(u'Property: Found valid "%s" property: %s: %s' + % (u'/'.join(validprofiles), + self.name, + self.value), + neverraise=True) + + if self._priority not in (u'', u'important'): + valid = False - @Deprecated(u'Use property ``name`` instead (since cssutils 0.9.5).') - def _getNormalname(self): - return self.__normalname - normalname = property(_getNormalname, - doc="DEPRECATED since 0.9.5, use name instead") \ No newline at end of file + return valid + + valid = property(validate, doc="Check if value of this property is valid " + "in the properties context.") diff --git a/src/cssutils/css/selector.py b/src/cssutils/css/selector.py index 3297cc5992..c3120f29d2 100644 --- a/src/cssutils/css/selector.py +++ b/src/cssutils/css/selector.py @@ -1,7 +1,5 @@ """Selector is a single Selector of a CSSStyleRule SelectorList. - -Partly implements - http://www.w3.org/TR/css3-selectors/ +Partly implements http://www.w3.org/TR/css3-selectors/. TODO - .contains(selector) @@ -9,45 +7,18 @@ TODO """ __all__ = ['Selector'] __docformat__ = 'restructuredtext' -__version__ = '$Id: selector.py 1429 2008-08-11 19:01:52Z cthedot $' +__version__ = '$Id: selector.py 1638 2009-01-13 20:39:33Z cthedot $' -import xml.dom -import cssutils from cssutils.util import _SimpleNamespaces +import cssutils +import xml.dom class Selector(cssutils.util.Base2): """ - (cssutils) a single selector in a SelectorList of a CSSStyleRule + (cssutils) a single selector in a :class:`~cssutils.css.SelectorList` + of a :class:`~cssutils.css.CSSStyleRule`. - Properties - ========== - element - Effective element target of this selector - parentList: of type SelectorList, readonly - The SelectorList that contains this selector or None if this - Selector is not attached to a SelectorList. - selectorText - textual representation of this Selector - seq - sequence of Selector parts including comments - specificity (READONLY) - tuple of (a, b, c, d) where: - - a - presence of style in document, always 0 if not used on a document - b - number of ID selectors - c - number of .class selectors - d - number of Element (type) selectors - - wellformed - if this selector is wellformed regarding the Selector spec - - Format - ====== - :: + Format:: # implemented in SelectorList selectors_group @@ -150,14 +121,46 @@ class Selector(cssutils.util.Base2): self._readonly = readonly + def __repr__(self): + if self.__getNamespaces(): + st = (self.selectorText, self._getUsedNamespaces()) + else: + st = self.selectorText + return u"cssutils.css.%s(selectorText=%r)" % ( + self.__class__.__name__, st) + + def __str__(self): + return u"" % ( + self.__class__.__name__, self.selectorText, self.specificity, + self._getUsedNamespaces(), id(self)) + + def _getUsedUris(self): + "Return list of actually used URIs in this Selector." + uris = set() + for item in self.seq: + type_, val = item.type, item.value + if type_.endswith(u'-selector') or type_ == u'universal' and \ + type(val) == tuple and val[0] not in (None, u'*'): + uris.add(val[0]) + return uris + + def _getUsedNamespaces(self): + "Return actually used namespaces only." + useduris = self._getUsedUris() + namespaces = _SimpleNamespaces(log=self._log) + for p, uri in self._namespaces.items(): + if uri in useduris: + namespaces[p] = uri + return namespaces + def __getNamespaces(self): - "uses own namespaces if not attached to a sheet, else the sheet's ones" + "Use own namespaces if not attached to a sheet, else the sheet's ones." try: return self._parent.parentRule.parentStyleSheet.namespaces except AttributeError: return self.__namespaces - _namespaces = property(__getNamespaces, doc="""if this Selector is attached + _namespaces = property(__getNamespaces, doc="""If this Selector is attached to a CSSStyleSheet the namespaces of that sheet are mirrored here. While the Selector (or parent SelectorList or parentRule(s) of that are not attached a own dict of {prefix: namespaceURI} is used.""") @@ -171,9 +174,7 @@ class Selector(cssutils.util.Base2): None if this Selector is not attached to a SelectorList.") def _getSelectorText(self): - """ - returns serialized format - """ + """Return serialized format.""" return cssutils.ser.do_css_Selector(self) def _setSelectorText(self, selectorText): @@ -183,14 +184,14 @@ class Selector(cssutils.util.Base2): Given namespaces are ignored if this object is attached to a CSSStyleSheet! - :Exceptions: - - `NAMESPACE_ERR`: (self) + :exceptions: + - :exc:`~xml.dom.NamespaceErr`: Raised if the specified selector uses an unknown namespace prefix. - - `SYNTAX_ERR`: (self) + - :exc:`~xml.dom.SyntaxErr`: Raised if the specified CSS string value has a syntax error and is unparsable. - - `NO_MODIFICATION_ALLOWED_ERR`: (self) + - :exc:`~xml.dom.NoModificationAllowedErr`: Raised if this rule is readonly. """ self._checkReadonly() @@ -763,38 +764,17 @@ class Selector(cssutils.util.Base2): specificity = property(lambda self: self._specificity, - doc="Specificity of this selector (READONLY).") + doc="""Specificity of this selector (READONLY). + Tuple of (a, b, c, d) where: + + a + presence of style in document, always 0 if not used on a document + b + number of ID selectors + c + number of .class selectors + d + number of Element (type) selectors + """) wellformed = property(lambda self: bool(len(self.seq))) - - def __repr__(self): - if self.__getNamespaces(): - st = (self.selectorText, self._getUsedNamespaces()) - else: - st = self.selectorText - return u"cssutils.css.%s(selectorText=%r)" % ( - self.__class__.__name__, st) - - def __str__(self): - return u"" % ( - self.__class__.__name__, self.selectorText, self.specificity, - self._getUsedNamespaces(), id(self)) - - def _getUsedUris(self): - "returns list of actually used URIs in this Selector" - uris = set() - for item in self.seq: - type_, val = item.type, item.value - if type_.endswith(u'-selector') or type_ == u'universal' and \ - type(val) == tuple and val[0] not in (None, u'*'): - uris.add(val[0]) - return uris - - def _getUsedNamespaces(self): - "returns actually used namespaces only" - useduris = self._getUsedUris() - namespaces = _SimpleNamespaces(log=self._log) - for p, uri in self._namespaces.items(): - if uri in useduris: - namespaces[p] = uri - return namespaces diff --git a/src/cssutils/css/selectorlist.py b/src/cssutils/css/selectorlist.py index 4fe21b1110..3545511718 100644 --- a/src/cssutils/css/selectorlist.py +++ b/src/cssutils/css/selectorlist.py @@ -17,37 +17,18 @@ TODO """ __all__ = ['SelectorList'] __docformat__ = 'restructuredtext' -__version__ = '$Id: selectorlist.py 1174 2008-03-20 17:43:07Z cthedot $' +__version__ = '$Id: selectorlist.py 1638 2009-01-13 20:39:33Z cthedot $' -import xml.dom -import cssutils from selector import Selector +import cssutils +import xml.dom class SelectorList(cssutils.util.Base, cssutils.util.ListSeq): - """ - (cssutils) a list of Selectors of a CSSStyleRule - - Properties - ========== - length: of type unsigned long, readonly - The number of Selector elements in the list. - parentRule: of type CSSRule, readonly - The CSS rule that contains this selector list or None if this - list is not attached to a CSSRule. - selectorText: of type DOMString - The textual representation of the selector for the rule set. The - implementation may have stripped out insignificant whitespace while - parsing the selector. - seq: (internal use!) - A list of Selector objects - wellformed - if this selectorlist is wellformed regarding the Selector spec - """ + """A list of :class:`~cssutils.css.Selector` objects + of a :class:`~cssutils.css.CSSStyleRule`.""" def __init__(self, selectorText=None, parentRule=None, readonly=False): """ - initializes SelectorList with optional selectorText - :Parameters: selectorText parsable list of Selectors @@ -63,8 +44,30 @@ class SelectorList(cssutils.util.Base, cssutils.util.ListSeq): self._readonly = readonly + def __repr__(self): + if self._namespaces: + st = (self.selectorText, self._namespaces) + else: + st = self.selectorText + return "cssutils.css.%s(selectorText=%r)" % ( + self.__class__.__name__, st) + + def __str__(self): + return "" % ( + self.__class__.__name__, self.selectorText, self._namespaces, + id(self)) + + def __setitem__(self, index, newSelector): + """Overwrite ListSeq.__setitem__ + + Any duplicate Selectors are **not** removed. + """ + newSelector = self.__prepareset(newSelector) + if newSelector: + self.seq[index] = newSelector + def __prepareset(self, newSelector, namespaces=None): - "used by appendSelector and __setitem__" + "Used by appendSelector and __setitem__" if not namespaces: namespaces = {} self._checkReadonly() @@ -75,26 +78,8 @@ class SelectorList(cssutils.util.Base, cssutils.util.ListSeq): newSelector._parent = self # maybe set twice but must be! return newSelector - def __setitem__(self, index, newSelector): - """ - overwrites ListSeq.__setitem__ - - Any duplicate Selectors are **not** removed. - """ - newSelector = self.__prepareset(newSelector) - if newSelector: - self.seq[index] = newSelector - - def append(self, newSelector): - "same as appendSelector(newSelector)" - self.appendSelector(newSelector) - - length = property(lambda self: len(self), - doc="The number of Selector elements in the list.") - - def __getNamespaces(self): - "uses children namespaces if not attached to a sheet, else the sheet's ones" + "Use children namespaces if not attached to a sheet, else the sheet's ones." try: return self.parentRule.parentStyleSheet.namespaces except AttributeError: @@ -103,17 +88,67 @@ class SelectorList(cssutils.util.Base, cssutils.util.ListSeq): namespaces.update(selector._namespaces) return namespaces - _namespaces = property(__getNamespaces, doc="""if this SelectorList is + def _getUsedUris(self): + "Used by CSSStyleSheet to check if @namespace rules are needed" + uris = set() + for s in self: + uris.update(s._getUsedUris()) + return uris + + _namespaces = property(__getNamespaces, doc="""If this SelectorList is attached to a CSSStyleSheet the namespaces of that sheet are mirrored here. While the SelectorList (or parentRule(s) are not attached the namespaces of all children Selectors are used.""") - parentRule = property(lambda self: self._parentRule, - doc="(DOM) The CSS rule that contains this SelectorList or\ - None if this SelectorList is not attached to a CSSRule.") + def append(self, newSelector): + "Same as :meth:`appendSelector`." + self.appendSelector(newSelector) + + def appendSelector(self, newSelector): + """ + Append `newSelector` to this list (a string will be converted to a + :class:`~cssutils.css.Selector`). + + :param newSelector: + comma-separated list of selectors (as a single string) or a tuple of + `(newSelector, dict-of-namespaces)` + :returns: New :class:`~cssutils.css.Selector` or ``None`` if + `newSelector` is not wellformed. + :exceptions: + - :exc:`~xml.dom.NamespaceErr`: + Raised if the specified selector uses an unknown namespace + prefix. + - :exc:`~xml.dom.SyntaxErr`: + Raised if the specified CSS string value has a syntax error + and is unparsable. + - :exc:`~xml.dom.NoModificationAllowedErr`: + Raised if this rule is readonly. + """ + self._checkReadonly() + + # might be (selectorText, namespaces) + newSelector, namespaces = self._splitNamespacesOff(newSelector) + try: + # use parent's only if available + namespaces = self.parentRule.parentStyleSheet.namespaces + except AttributeError: + # use already present namespaces plus new given ones + _namespaces = self._namespaces + _namespaces.update(namespaces) + namespaces = _namespaces + + newSelector = self.__prepareset(newSelector, namespaces) + if newSelector: + seq = self.seq[:] + del self.seq[:] + for s in seq: + if s.selectorText != newSelector.selectorText: + self.seq.append(s) + self.seq.append(newSelector) + return newSelector def _getSelectorText(self): - "returns serialized format" + "Return serialized format." return cssutils.ser.do_css_SelectorList(self) def _setSelectorText(self, selectorText): @@ -121,14 +156,14 @@ class SelectorList(cssutils.util.Base, cssutils.util.ListSeq): :param selectorText: comma-separated list of selectors or a tuple of (selectorText, dict-of-namespaces) - :Exceptions: - - `NAMESPACE_ERR`: (Selector) + :exceptions: + - :exc:`~xml.dom.NamespaceErr`: Raised if the specified selector uses an unknown namespace prefix. - - `SYNTAX_ERR`: (self) + - :exc:`~xml.dom.SyntaxErr`: Raised if the specified CSS string value has a syntax error and is unparsable. - - `NO_MODIFICATION_ALLOWED_ERR`: (self) + - :exc:`~xml.dom.NoModificationAllowedErr`: Raised if this rule is readonly. """ self._checkReadonly() @@ -184,66 +219,12 @@ class SelectorList(cssutils.util.Base, cssutils.util.ListSeq): doc="""(cssutils) The textual representation of the selector for a rule set.""") + length = property(lambda self: len(self), + doc="The number of :class:`~cssutils.css.Selector` objects in the list.") + + parentRule = property(lambda self: self._parentRule, + doc="(DOM) The CSS rule that contains this SelectorList or " + "``None`` if this SelectorList is not attached to a CSSRule.") + wellformed = property(lambda self: bool(len(self.seq))) - def appendSelector(self, newSelector): - """ - Append newSelector (a string will be converted to a new - Selector). - - :param newSelector: - comma-separated list of selectors or a tuple of - (selectorText, dict-of-namespaces) - :returns: New Selector or None if newSelector is not wellformed. - :Exceptions: - - `NAMESPACE_ERR`: (self) - Raised if the specified selector uses an unknown namespace - prefix. - - `SYNTAX_ERR`: (self) - Raised if the specified CSS string value has a syntax error - and is unparsable. - - `NO_MODIFICATION_ALLOWED_ERR`: (self) - Raised if this rule is readonly. - """ - self._checkReadonly() - - # might be (selectorText, namespaces) - newSelector, namespaces = self._splitNamespacesOff(newSelector) - try: - # use parent's only if available - namespaces = self.parentRule.parentStyleSheet.namespaces - except AttributeError: - # use already present namespaces plus new given ones - _namespaces = self._namespaces - _namespaces.update(namespaces) - namespaces = _namespaces - - newSelector = self.__prepareset(newSelector, namespaces) - if newSelector: - seq = self.seq[:] - del self.seq[:] - for s in seq: - if s.selectorText != newSelector.selectorText: - self.seq.append(s) - self.seq.append(newSelector) - return newSelector - - def __repr__(self): - if self._namespaces: - st = (self.selectorText, self._namespaces) - else: - st = self.selectorText - return "cssutils.css.%s(selectorText=%r)" % ( - self.__class__.__name__, st) - - def __str__(self): - return "" % ( - self.__class__.__name__, self.selectorText, self._namespaces, - id(self)) - - def _getUsedUris(self): - "used by CSSStyleSheet to check if @namespace rules are needed" - uris = set() - for s in self: - uris.update(s._getUsedUris()) - return uris diff --git a/src/cssutils/cssproductions.py b/src/cssutils/cssproductions.py index 63f856442b..53cb0e0b31 100644 --- a/src/cssutils/cssproductions.py +++ b/src/cssutils/cssproductions.py @@ -12,42 +12,33 @@ open issues """ __all__ = ['CSSProductions', 'MACROS', 'PRODUCTIONS'] __docformat__ = 'restructuredtext' -__version__ = '$Id: cssproductions.py 1378 2008-07-15 20:02:19Z cthedot $' +__version__ = '$Id: cssproductions.py 1537 2008-12-03 14:37:10Z 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}(?:{nl}|{wc})?', + 'unicode': r'\\[0-9a-f]{1,6}(?:{nl}|{s})?', + # 'escape': r'{unicode}|\\[ -~\200-\4177777]', 'escape': r'{unicode}|\\[ -~\200-\777]', - # 'escape': r'{unicode}|\\[ -~\200-\4177777]', + 'nmstart': r'[_a-zA-Z]|{nonascii}|{escape}', 'nmchar': r'[-_a-zA-Z0-9]|{nonascii}|{escape}', - - 'num': r'[0-9]*\.[0-9]+|[0-9]+', #r'[-]?\d+|[-]?\d*\.\d+', - 'string': r"""\'({stringesc1}|{stringchar}|")*\'""" + "|" + '''\"({stringesc2}|{stringchar}|')*\"''', - # seems an error in CSS 3 but is allowed in CSS 2.1 - 'stringesc1' : r"\\'", - 'stringesc2' : r'\\"', - - 'stringchar': r'{urlchar}| |\\{nl}', - - # urlchar ::= [#x9#x21#x23-#x26#x27-#x7E] | nonascii | escape - # 0x27 is "'" which should not be in here..., should ) be in here??? - 'urlchar': r'[\x09\x21\x23-\x26\x28-\x7E]|{nonascii}|{escape}', - - # from CSS2.1 - 'invalid': r'{invalid1}|{invalid2}', + 'string1': r'"([^\n\r\f\\"]|\\{nl}|{escape})*"', + 'string2': r"'([^\n\r\f\\']|\\{nl}|{escape})*'", 'invalid1': r'\"([^\n\r\f\\"]|\\{nl}|{escape})*', 'invalid2': r"\'([^\n\r\f\\']|\\{nl}|{escape})*", - # \r\n should be counted as one char see unicode above - 'nl': r'\n|\r\n|\r|\f', - 'w': r'{wc}*', - 'wc': r'\t|\r|\n|\f|\x20', - 'comment': r'\/\*[^*]*\*+([^/][^*]*\*+)*\/', + 'ident': r'[-]?{nmstart}{nmchar}*', + 'name': r'{nmchar}+', + 'num': r'[0-9]*\.[0-9]+|[0-9]+', #r'[-]?\d+|[-]?\d*\.\d+', + 'string': r'{string1}|{string2}', + # from CSS2.1 + 'invalid': r'{invalid1}|{invalid2}', + 'url': r'[\x09\x21\x23-\x26\x28\x2a-\x7E]|{nonascii}|{escape}', + + 's': r'\t|\r|\n|\f|\x20', + 'w': r'{s}*', + 'nl': r'\n|\r\n|\r|\f', 'A': r'A|a|\\0{0,4}(?:41|61)(?:\r\n|[ \t\r\n\f])?', 'C': r'C|c|\\0{0,4}(?:43|63)(?:\r\n|[ \t\r\n\f])?', @@ -77,8 +68,8 @@ MACROS = { PRODUCTIONS = [ ('BOM', r'\xFEFF'), # will only be checked at beginning of CSS - ('S', r'{wc}+'), # 1st in list of general productions - ('URI', r'{U}{R}{L}\({w}({string}|{urlchar}*){w}\)'), + ('S', r'{s}+'), # 1st in list of general productions + ('URI', r'{U}{R}{L}\({w}({string}|{url}*){w}\)'), ('FUNCTION', r'{ident}\('), ('IDENT', r'{ident}'), ('STRING', r'{string}'), diff --git a/src/cssutils/errorhandler.py b/src/cssutils/errorhandler.py index 89a8c7bca9..63eee8bcba 100644 --- a/src/cssutils/errorhandler.py +++ b/src/cssutils/errorhandler.py @@ -16,12 +16,12 @@ log """ __all__ = ['ErrorHandler'] __docformat__ = 'restructuredtext' -__version__ = '$Id: errorhandler.py 1361 2008-07-13 18:12:40Z cthedot $' +__version__ = '$Id: errorhandler.py 1560 2008-12-14 16:13:16Z cthedot $' +from helper import Deprecated import logging import urllib2 import xml.dom -from helper import Deprecated class _ErrorHandler(object): """ @@ -74,17 +74,22 @@ class _ErrorHandler(object): handles all calls logs or raises exception """ + line, col = None, None if token: if isinstance(token, tuple): - msg = u'%s [%s:%s: %s]' % ( - msg, token[2], token[3], token[1]) + value, line, col = token[1], token[2], token[3] else: - msg = u'%s [%s:%s: %s]' % ( - msg, token.line, token.col, token.value) + 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 error + raise + elif issubclass(error, xml.dom.DOMException): + error.line = line + error.col = col + raise error(msg, line, col) else: raise error(msg) else: diff --git a/src/cssutils/helper.py b/src/cssutils/helper.py index 9dafd9437c..19d77ed27a 100644 --- a/src/cssutils/helper.py +++ b/src/cssutils/helper.py @@ -1,6 +1,5 @@ """cssutils helper """ -__all__ = ['Deprecated', 'normalize'] __docformat__ = 'restructuredtext' __version__ = '$Id: errorhandler.py 1234 2008-05-22 20:26:12Z cthedot $' @@ -31,8 +30,7 @@ class Deprecated(object): return newFunc # simple escapes, all non unicodes -_simpleescapes = re.compile(ur'(\\[^0-9a-fA-F])').sub - +_simpleescapes = re.compile(ur'(\\[^0-9a-fA-F])').sub def normalize(x): """ normalizes x, namely: @@ -48,4 +46,82 @@ def normalize(x): x = _simpleescapes(removeescape, x) return x.lower() else: - return x \ No newline at end of file + return x + +def pushtoken(token, tokens): + """Return new generator starting with token followed by all tokens in + ``tokens``""" + # TODO: may use itertools.chain? + yield token + for x in tokens: + yield x + +def string(value): + """ + Serialize value with quotes e.g.:: + + ``a \'string`` => ``'a \'string'`` + """ + # \n = 0xa, \r = 0xd, \f = 0xc + value = value.replace(u'\n', u'\\a ').replace( + u'\r', u'\\d ').replace( + u'\f', u'\\c ').replace( + u'"', u'\\"') + + return u'"%s"' % value + +def stringvalue(string): + """ + Retrieve actual value of string without quotes. Escaped + quotes inside the value are resolved, e.g.:: + + ``'a \'string'`` => ``a 'string`` + """ + return string.replace('\\'+string[0], string[0])[1:-1] + +_match_forbidden_in_uri = re.compile(ur'''.*?[\(\)\s\;,'"]''', re.U).match +def uri(value): + """ + Serialize value by adding ``url()`` and with quotes if needed e.g.:: + + ``"`` => ``url("\"")`` + """ + if _match_forbidden_in_uri(value): + value = string(value) + return u'url(%s)' % value + +def urivalue(uri): + """ + Return actual content without surrounding "url(" and ")" + and removed surrounding quotes too including contained + escapes of quotes, e.g.:: + + ``url("\"")`` => ``"`` + """ + uri = uri[uri.find('(')+1:-1].strip() + if uri and (uri[0] in '\'"') and (uri[0] == uri[-1]): + return stringvalue(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) diff --git a/src/cssutils/parse.py b/src/cssutils/parse.py index 4aa0bd831e..17c63be7d9 100644 --- a/src/cssutils/parse.py +++ b/src/cssutils/parse.py @@ -1,31 +1,26 @@ #!/usr/bin/env python -"""a validating CSSParser -""" +"""A validating CSSParser""" __all__ = ['CSSParser'] __docformat__ = 'restructuredtext' -__version__ = '$Id: parse.py 1418 2008-08-09 19:27:50Z cthedot $' +__version__ = '$Id: parse.py 1656 2009-02-03 20:31:06Z cthedot $' -import codecs -import os -import urllib from helper import Deprecated -import tokenize2 +import codecs import cssutils +import os +import tokenize2 +import urllib class CSSParser(object): - """ - parses a CSS StyleSheet string or file and - returns a DOM Level 2 CSS StyleSheet object + """Parse a CSS StyleSheet from URL, string or file and return a DOM Level 2 + CSS StyleSheet object. Usage:: parser = CSSParser() - # optionally parser.setFetcher(fetcher) - sheet = parser.parseFile('test1.css', 'ascii') - print sheet.cssText """ def __init__(self, log=None, loglevel=None, raiseExceptions=None, @@ -40,7 +35,7 @@ class CSSParser(object): parsing. Later while working with the resulting sheets the setting used in cssutils.log.raiseExeptions is used fetcher - see ``setFetchUrl(fetcher)`` + see ``setFetcher(fetcher)`` """ if log is not None: cssutils.log.setLog(log) @@ -69,26 +64,28 @@ class CSSParser(object): def parseString(self, cssText, encoding=None, href=None, media=None, title=None): - """Return parsed CSSStyleSheet from given string cssText. - Raises errors during retrieving (e.g. UnicodeDecodeError). + """Parse `cssText` as :class:`~cssutils.css.CSSStyleSheet`. + Errors may be raised (e.g. UnicodeDecodeError). - cssText + :param cssText: CSS string to parse - encoding + :param encoding: If ``None`` the encoding will be read from BOM or an @charset rule or defaults to UTF-8. If given overrides any found encoding including the ones for imported sheets. - It also will be used to decode ``cssText`` if given as a (byte) + It also will be used to decode `cssText` if given as a (byte) string. - href - The href attribute to assign to the parsed style sheet. - Used to resolve other urls in the parsed sheet like @import hrefs - media - The media attribute to assign to the parsed style sheet - (may be a MediaList, list or a string) - title - The title attribute to assign to the parsed style sheet + :param href: + The ``href`` attribute to assign to the parsed style sheet. + Used to resolve other urls in the parsed sheet like @import hrefs. + :param media: + The ``media`` attribute to assign to the parsed style sheet + (may be a MediaList, list or a string). + :param title: + The ``title`` attribute to assign to the parsed style sheet. + :returns: + :class:`~cssutils.css.CSSStyleSheet`. """ self.__parseSetting(True) if isinstance(cssText, str): @@ -107,23 +104,23 @@ class CSSParser(object): def parseFile(self, filename, encoding=None, href=None, media=None, title=None): - """Retrieve and return a CSSStyleSheet from given filename. - Raises errors during retrieving (e.g. IOError). - - filename - of the CSS file to parse, if no ``href`` is given filename is + """Retrieve content from `filename` and parse it. Errors may be raised + (e.g. IOError). + + :param filename: + of the CSS file to parse, if no `href` is given filename is converted to a (file:) URL and set as ``href`` of resulting stylesheet. - If href is given it is set as ``sheet.href``. Either way + If `href` is given it is set as ``sheet.href``. Either way ``sheet.href`` is used to resolve e.g. stylesheet imports via @import rules. - encoding + :param encoding: Value ``None`` defaults to encoding detection via BOM or an @charset rule. Other values override detected encoding for the sheet at - ``filename`` including any imported sheets. - - for other parameters see ``parseString`` + `filename` including any imported sheets. + :returns: + :class:`~cssutils.css.CSSStyleSheet`. """ if not href: # prepend // for file URL, urllib does not do this? @@ -134,19 +131,19 @@ class CSSParser(object): href=href, media=media, title=title) def parseUrl(self, href, encoding=None, media=None, title=None): - """Retrieve and return a CSSStyleSheet from given href (an URL). - In case of any errors while reading the URL returns None. - - href + """Retrieve content from URL `href` and parse it. Errors may be raised + (e.g. URLError). + + :param href: URL of the CSS file to parse, will also be set as ``href`` of resulting stylesheet - encoding + :param encoding: Value ``None`` defaults to encoding detection via HTTP, BOM or an @charset rule. A value overrides detected encoding for the sheet at ``href`` including any imported sheets. - - for other parameters see ``parseString`` + :returns: + :class:`~cssutils.css.CSSStyleSheet`. """ encoding, enctype, text = cssutils.util._readUrl(href, overrideEncoding=encoding) @@ -160,20 +157,24 @@ class CSSParser(object): def setFetcher(self, fetcher=None): """Replace the default URL fetch function with a custom one. - The fetcher function gets a single parameter + + :param fetcher: + A function which gets a single parameter - ``url`` - the URL to read + ``url`` + the URL to read - and returns ``(encoding, content)`` where ``encoding`` is the HTTP - charset normally given via the Content-Type header (which may simply - omit the charset) and ``content`` being the (byte) string content. - The Mimetype should be 'text/css' but this has to be checked by the - fetcher itself (the default fetcher emits a warning if encountering - a different mimetype). + and must return ``(encoding, content)`` where ``encoding`` is the + HTTP charset normally given via the Content-Type header (which may + simply omit the charset in which case ``encoding`` would be + ``None``) and ``content`` being the string (or unicode) content. + + The Mimetype should be 'text/css' but this has to be checked by the + fetcher itself (the default fetcher emits a warning if encountering + a different mimetype). - Calling ``setFetcher`` with ``fetcher=None`` resets cssutils - to use its default function. + Calling ``setFetcher`` with ``fetcher=None`` resets cssutils + to use its default function. """ self.__fetcher = fetcher diff --git a/src/cssutils/prodparser.py b/src/cssutils/prodparser.py index bf2f32484c..0222be97c6 100644 --- a/src/cssutils/prodparser.py +++ b/src/cssutils/prodparser.py @@ -19,153 +19,207 @@ __docformat__ = 'restructuredtext' __version__ = '$Id: parse.py 1418 2008-08-09 19:27:50Z cthedot $' import cssutils +import sys class ParseError(Exception): """Base Exception class for ProdParser (used internally).""" pass +class Done(ParseError): + """Raised if Sequence or Choice is finished and no more Prods left.""" + pass + class Exhausted(ParseError): - """Raised if Sequence or Choice is done.""" + """Raised if Sequence or Choice is finished but token is given.""" + pass + +class Missing(ParseError): + """Raised if Sequence or Choice is not finished but no matching token given.""" pass class NoMatch(ParseError): - """Raised if Sequence or Choice do not match.""" - pass - -class MissingToken(ParseError): - """Raised if Sequence or Choice are not exhausted.""" + """Raised if nothing in Sequence or Choice does match.""" pass class Choice(object): """A Choice of productions (Sequence or single Prod).""" - def __init__(self, prods): + + def __init__(self, *prods, **options): """ - prods + *prods Prod or Sequence objects + options: + optional=False """ self._prods = prods + + try: + self.optional = options['optional'] + except KeyError, e: + for p in self._prods: + if p.optional: + self.optional = True + break + else: + self.optional = False + + self.reset() + + def reset(self): + """Start Choice from zero""" self._exhausted = False + def matches(self, token): + """Check if token matches""" + for prod in self._prods: + if prod.matches(token): + return True + return False + def nextProd(self, token): """ Return: - - - next matching Prod or Sequence - - raises ParseError if nothing matches - - raises Exhausted if choice already done + + - next matching Prod or Sequence + - ``None`` if any Prod or Sequence is optional and no token matched + - raise ParseError if nothing matches and all are mandatory + - raise Exhausted if choice already done ``token`` may be None but this occurs when no tokens left.""" if not self._exhausted: + optional = False for x in self._prods: - if isinstance(x, Prod): - test = x - else: - # nested Sequence matches if 1st prod matches - test = x.first() - try: - if test.matches(token): - self._exhausted = True - return x - except ParseError, e: - # do not raise if other my match - continue + if x.matches(token): + self._exhausted = True + x.reset() + return x + elif x.optional: + optional = True else: - # None matched - raise ParseError(u'No match in choice') - else: + if not optional: + # None matched but also None is optional + raise ParseError(u'No match in %s' % self) + elif token: raise Exhausted(u'Extra token') + def __str__(self): + return u'Choice(%s)' % u', '.join([str(x) for x in self._prods]) + class Sequence(object): """A Sequence of productions (Choice or single Prod).""" - def __init__(self, prods, minmax=None): + def __init__(self, *prods, **options): """ - prods + *prods Prod or Sequence objects - minmax = lambda: (1, 1) - callback returning number of times this sequence may run + **options: + minmax = lambda: (1, 1) + callback returning number of times this sequence may run """ self._prods = prods - if not minmax: + try: + minmax = options['minmax'] + except KeyError: minmax = lambda: (1, 1) + self._min, self._max = minmax() + if self._max is None: + # unlimited + try: + # py2.6/3 + self._max = sys.maxsize + except AttributeError: + # py<2.6 + self._max = sys.maxint - self._number = len(self._prods) - self._round = 1 # 1 based! - self._pos = 0 + self._prodcount = len(self._prods) + self.reset() - def first(self): - """Return 1st element of Sequence, used by Choice""" - # TODO: current impl first only if 1st if an prod! + def matches(self, token): + """Called by Choice to try to find if Sequence matches.""" for prod in self._prods: - if not prod.optional: - return prod + if prod.matches(token): + return True + try: + if not prod.optional: + break + except AttributeError: + pass + return False + + def reset(self): + """Reset this Sequence if it is nested.""" + self._roundstarted = False + self._i = 0 + self._round = 0 def _currentName(self): """Return current element of Sequence, used by name""" # TODO: current impl first only if 1st if an prod! - for prod in self._prods[self._pos:]: + for prod in self._prods[self._i:]: if not prod.optional: - return prod.name + return str(prod) else: - return 'Unknown' + return 'Sequence' - name = property(_currentName, doc='Used for Error reporting') + optional = property(lambda self: self._min == 0) def nextProd(self, token): """Return - - - next matching Prod or Choice + + - next matching Prod or Choice - raises ParseError if nothing matches - raises Exhausted if sequence already done """ - while self._pos < self._number: - x = self._prods[self._pos] - thisround = self._round - - self._pos += 1 - if self._pos == self._number: - if self._round < self._max: - # new round? - self._pos = 0 - self._round += 1 + while self._round < self._max: + # for this round + i = self._i + round = self._round + p = self._prods[i] + if i == 0: + self._roundstarted = False - if isinstance(x, Prod): - if not token and (x.optional or thisround > self._min): - # token is None if nothing expected - raise Exhausted() - elif not token and not x.optional: - raise MissingToken(u'Missing token for production %s' - % x.name) - elif x.matches(token): - return x - elif x.optional: - # try next - continue -# elif thisround > self._min: -# # minimum done -# self._round = self._max -# self._pos = self._number -# return None + # for next round + self._i += 1 + if self._i == self._prodcount: + self._round += 1 + self._i = 0 + + if p.matches(token): + self._roundstarted = True + # reset nested Choice or Prod to use from start + p.reset() + return p + + elif p.optional: + continue + + elif round < self._min: + raise Missing(u'Missing token for production %s' % p) + + elif not token: + if self._roundstarted: + raise Missing(u'Missing token for production %s' % p) else: - # should have matched - raise NoMatch(u'No matching production for token') - + raise Done() + else: - # nested Sequence or Choice - return x - - # Sequence is exhausted - if self._round >= self._max: + raise NoMatch(u'No matching production for token') + + if token: raise Exhausted(u'Extra token') + def __str__(self): + return u'Sequence(%s)' % u', '.join([str(x) for x in self._prods]) + class Prod(object): """Single Prod in Sequence or Choice.""" - def __init__(self, name, match, toSeq=None, toStore=None, - optional=False): + def __init__(self, name, match, optional=False, + toSeq=None, toStore=None, + stop=False, nextSor=False, mayEnd=False): """ name name used for error reporting @@ -173,16 +227,24 @@ class Prod(object): function called with parameters tokentype and tokenvalue returning True, False or raising ParseError toSeq callback (optional) - if given calling toSeq(token) will be appended to seq - else simply seq + calling toSeq(token, tokens) returns (type_, val) == (token[0], token[1]) + to be appended to seq else simply unaltered (type_, val) toStore (optional) key to save util.Item to store or callback(store, util.Item) optional = False wether Prod is optional or not + stop = False + if True stop parsing of tokens here + mayEnd = False + no token must follow even defined by Sequence. + Used for operator ',/ ' currently only """ - self.name = name + self._name = name self.match = match - self.optional=optional + self.optional = optional + self.stop = stop + self.nextSor = nextSor + self.mayEnd = mayEnd def makeToStore(key): "Return a function used by toStore." @@ -198,9 +260,9 @@ class Prod(object): # called: seq.append(toSeq(value)) self.toSeq = toSeq else: - self.toSeq = lambda val: val + self.toSeq = lambda t, tokens: (t[0], t[1]) - if callable(toStore): + if hasattr(toStore, '__call__'): self.toStore = toStore elif toStore: self.toStore = makeToStore(toStore) @@ -210,12 +272,20 @@ class Prod(object): def matches(self, token): """Return if token matches.""" + if not token: + return False type_, val, line, col = token return self.match(type_, val) + def reset(self): + pass + + def __str__(self): + return self._name + def __repr__(self): return "" % ( - self.__class__.__name__, self.name, id(self)) + self.__class__.__name__, self._name, id(self)) class ProdParser(object): @@ -225,17 +295,81 @@ class ProdParser(object): self._log = cssutils.log self._tokenizer = cssutils.tokenize2.Tokenizer() + def _texttotokens(self, text): + """Build a generator which is the only thing that is parsed! + old classes may use lists etc + """ + if isinstance(text, basestring): + # to tokenize strip space + tokens = self._tokenizer.tokenize(text.strip()) + elif isinstance(text, tuple): + # (token, tokens) or a single token + if len(text) == 2: + # (token, tokens) + def gen(token, tokens): + "new generator appending token and tokens" + yield token + for t in tokens: + yield t + + tokens = (t for t in gen(*text)) + + else: + # single token + tokens = (t for t in [text]) + elif isinstance(text, list): + # generator from list + tokens = (t for t in text) + else: + # already tokenized, assume generator + tokens = text + + return tokens + + def _SorTokens(self, tokens, until=',/'): + """New tokens generator which has S tokens removed, + if followed by anything in ``until``, normally a ``,``.""" + def removedS(tokens): + for token in tokens: + if token[0] == self.types.S: + try: + next_ = tokens.next() + except StopIteration: + yield token + else: + if next_[1] in until: + # omit S as e.g. ``,`` has been found + yield next_ + elif next_[0] == self.types.COMMENT: + # pass COMMENT + yield next_ + else: + yield token + yield next_ + + elif token[0] == self.types.COMMENT: + # pass COMMENT + yield token + else: + yield token + break + # normal mode again + for token in tokens: + yield token + + return (token for token in removedS(tokens)) + def parse(self, text, name, productions, store=None): """ text (or token generator) to parse, will be tokenized if not a generator yet - + may be: - a string to be tokenized - a single token, a tuple - a tuple of (token, tokensGenerator) - - already tokenized so a tokens generator - + - already tokenized so a tokens generator + name used for logging productions @@ -255,148 +389,249 @@ class ProdParser(object): :store: filled keys defined by Prod.toStore :unusedtokens: token generator containing tokens not used yet """ - if isinstance(text, basestring): - # to tokenize - tokens = self._tokenizer.tokenize(text) - elif isinstance(text, tuple): - # (token, tokens) or a single token - if len(text) == 2: - # (token, tokens) - def gen(token, tokens): - "new generator appending token and tokens" - yield token - for t in tokens: - yield t - - tokens = (t for t in gen(*text)) - - else: - # single token - tokens = [text] - else: - # already tokenized, assume generator - tokens = text + tokens = self._texttotokens(text) + if not tokens: + self._log.error(u'No content to parse.') + # TODO: return??? - # a new seq to append all Items to seq = cssutils.util.Seq(readonly=False) - - # store for specific values - if not store: + if not store: # store for specific values store = {} -# store['_raw'] = [] - - # stack of productions - prods = [productions] - + prods = [productions] # stack of productions wellformed = True - for token in tokens: + + # while no real token is found any S are ignored + started = False + prod = None + # flag if default S handling should be done + defaultS = True + while True: + try: + token = tokens.next() + except StopIteration: + break type_, val, line, col = token -# store['_raw'].append(val) # default productions - if type_ == self.types.S: - # always append S? - seq.append(val, type_, line, col) - elif type_ == self.types.COMMENT: + if type_ == self.types.COMMENT: # always append COMMENT - seq.append(val, type_, line, col) + seq.append(cssutils.css.CSSComment(val), + cssutils.css.CSSComment, line, col) + elif defaultS and type_ == self.types.S: + # append S (but ignore starting ones) + if started: + seq.append(val, type_, line, col) + else: + continue # elif type_ == self.types.ATKEYWORD: # # @rule # r = cssutils.css.CSSUnknownRule(cssText=val) # seq.append(r, type(r), line, col) - elif type_ == self.types.EOF: - # do nothing + elif type_ == self.types.INVALID: + # invalidate parse + wellformed = False + self._log.error(u'Invalid token: %r' % (token,)) + break + elif type_ == 'EOF': + # do nothing? (self.types.EOF == True!) pass -# next = 'EOF' else: - # check prods + started = True # check S now + nextSor = False # reset + try: while True: # find next matching production try: prod = prods[-1].nextProd(token) - except (NoMatch, Exhausted), e: + except (Exhausted, NoMatch), e: # try next prod = None - if isinstance(prod, Prod): + # found actual Prod, not a Choice or Sequence break - elif not prod: - if len(prods) > 1: - # nested exhausted, next in parent - prods.pop() - else: - raise Exhausted('Extra token') - else: + elif prod: # nested Sequence, Choice prods.append(prod) - + else: + # nested exhausted, try in parent + if len(prods) > 1: + prods.pop() + else: + raise ParseError('No match') except ParseError, e: wellformed = False self._log.error(u'%s: %s: %r' % (name, e, token)) - + break else: # process prod if prod.toSeq: - seq.append(prod.toSeq(val), type_, line, col) - else: + type_, val = prod.toSeq(token, tokens) + if val is not None: seq.append(val, type_, line, col) - if prod.toStore: prod.toStore(store, seq[-1]) - -# if 'STOP' == next: # EOF? -# # stop here and ignore following tokens -# break + if prod.stop: # EOF? + # stop here and ignore following tokens + break + if prod.nextSor: + # following is S or other token (e.g. ",")? + # remove S if + tokens = self._SorTokens(tokens, ',/') + defaultS = False + else: + defaultS = True + lastprod = prod while True: # all productions exhausted? try: prod = prods[-1].nextProd(token=None) - except Exhausted, e: - prod = None # ok - except (MissingToken, NoMatch), e: - wellformed = False - self._log.error(u'%s: %s' - % (name, e)) - else: - try: - if prod.optional: - # ignore optional ones - continue - except AttributeError: - pass + except Done, e: + # ok + prod = None - if prod: + except Missing, e: + prod = None + # last was a S operator which may End a Sequence, then ok + if hasattr(lastprod, 'mayEnd') and not lastprod.mayEnd: + wellformed = False + self._log.error(u'%s: %s' % (name, e)) + + except ParseError, e: + prod = None + wellformed = False + self._log.error(u'%s: %s' % (name, e)) + + else: + if prods[-1].optional: + prod = None + elif prod and prod.optional: + # ignore optional + continue + + if prod and not prod.optional: wellformed = False self._log.error(u'%s: Missing token for production %r' - % (name, prod.name)) + % (name, str(prod))) + break elif len(prods) > 1: # nested exhausted, next in parent prods.pop() else: break - # bool, Seq, None or generator + # trim S from end + seq.rstrip() return wellformed, seq, store, tokens class PreDef(object): """Predefined Prod definition for use in productions definition for ProdParser instances. - """ - @staticmethod - def comma(): - "," - return Prod(name=u'comma', match=lambda t, v: v == u',') + """ + types = cssutils.cssproductions.CSSProductions @staticmethod - def funcEnd(): - ")" - return Prod(name=u'end FUNC ")"', match=lambda t, v: v == u')') - + def CHAR(name='char', char=u',', toSeq=None, toStore=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 unary(): + def comma(toStore=None): + return PreDef.CHAR(u'comma', u',', toStore=toStore) + + @staticmethod + def dimension(toStore=None, 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): + 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): + ")" + return PreDef.CHAR(u'end FUNC ")"', u')', + toStore=toStore, + stop=stop, + nextSor=nextSor) + + @staticmethod + def ident(toStore=None, 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): + return Prod(name=u'number', + match=lambda t, v: t == PreDef.types.NUMBER, + toStore=toStore, + nextSor=nextSor) + + @staticmethod + def string(toStore=None, 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): + 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, + match=lambda t, v: t == PreDef.types.S, + optional=optional, + toSeq=toSeq, + toStore=toStore, + mayEnd=mayEnd) + + @staticmethod + def unary(optional=True, toStore=None): "+ or -" - return Prod(name=u'unary +-', match=lambda t, v: v in u'+-', - optional=True) + return Prod(name=u'unary +-', match=lambda t, v: v in (u'+', u'-'), + optional=optional, + toStore=toStore) + + @staticmethod + def uri(toStore=None, 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): + 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, + nextSor=nextSor + ) diff --git a/src/cssutils/profiles.py b/src/cssutils/profiles.py index 3d8131f349..2392da6161 100644 --- a/src/cssutils/profiles.py +++ b/src/cssutils/profiles.py @@ -1,6 +1,15 @@ -"""CSS profiles. Predefined are: +"""CSS profiles. -- 'CSS level 2' +css2 is based on cssvalues + contributed by Kevin D. Smith, thanks! + + "cssvalues" is used as a property validator. + it is an importable object that contains a dictionary of compiled regular + expressions. The keys of this dictionary are all of the valid CSS property + names. The values are compiled regular expressions that can be used to + validate the values for that property. (Actually, the values are references + to the 'match' method of a compiled regular expression, so that they are + simply called like functions.) """ __all__ = ['profiles'] @@ -10,6 +19,7 @@ __version__ = '$Id: cssproperties.py 1116 2008-03-05 13:52:23Z cthedot $' import cssutils import re +properties = {} """ Define some regular expression fragments that will be used as macros within the CSS property value regular expressions. @@ -56,12 +66,13 @@ css2macros = { 'font-attrs': r'{font-style}|{font-variant}|{font-weight}', 'outline-attrs': r'{outline-color}|{outline-style}|{outline-width}', 'text-attrs': r'underline|overline|line-through|blink', + 'overflow': r'visible|hidden|scroll|auto|inherit', } """ Define the regular expressions for validation all CSS values """ -css2 = { +properties['css2'] = { 'azimuth': r'{angle}|(behind\s+)?(left-side|far-left|left|center-left|center|center-right|right|far-right|right-side)(\s+behind)?|behind|leftwards|rightwards|inherit', 'background-attachment': r'{background-attachment}', 'background-color': r'{background-color}', @@ -137,7 +148,7 @@ css2 = { 'outline-style': r'{outline-style}', 'outline-width': r'{outline-width}', 'outline': r'{outline-attrs}(\s+{outline-attrs})*|inherit', - 'overflow': r'visible|hidden|scroll|auto|inherit', + 'overflow': r'{overflow}', 'padding-top': r'{padding-width}|inherit', 'padding-right': r'{padding-width}|inherit', 'padding-bottom': r'{padding-width}|inherit', @@ -185,16 +196,24 @@ css3colormacros = { # orange and transparent in CSS 2.1 'namedcolor': r'(currentcolor|transparent|orange|black|green|silver|lime|gray|olive|white|yellow|maroon|navy|red|blue|purple|teal|fuchsia|aqua)', # orange? - 'rgbacolor': r'rgba\({w}{int}{w},{w}{int}{w},{w}{int}{w},{w}{int}{w}\)|rgba\({w}{num}%{w},{w}{num}%{w},{w}{num}%{w},{w}{num}{w}\)', - 'hslcolor': r'hsl\({w}{int}{w},{w}{num}%{w},{w}{num}%{w}\)|hsla\({w}{int}{w},{w}{num}%{w},{w}{num}%{w},{w}{num}{w}\)', + 'rgbacolor': r'rgba\({w}{int}{w},{w}{int}{w},{w}{int}{w},{w}{int}{w}\)|rgba\({w}{num}%{w},{w}{num}%{w},{w}{num}%{w},{w}{num}{w}\)', + 'hslcolor': r'hsl\({w}{int}{w},{w}{num}%{w},{w}{num}%{w}\)|hsla\({w}{int}{w},{w}{num}%{w},{w}{num}%{w},{w}{num}{w}\)', + 'x11color': r'aliceblue|antiquewhite|aqua|aquamarine|azure|beige|bisque|black|blanchedalmond|blue|blueviolet|brown|burlywood|cadetblue|chartreuse|chocolate|coral|cornflowerblue|cornsilk|crimson|cyan|darkblue|darkcyan|darkgoldenrod|darkgray|darkgreen|darkgrey|darkkhaki|darkmagenta|darkolivegreen|darkorange|darkorchid|darkred|darksalmon|darkseagreen|darkslateblue|darkslategray|darkslategrey|darkturquoise|darkviolet|deeppink|deepskyblue|dimgray|dimgrey|dodgerblue|firebrick|floralwhite|forestgreen|fuchsia|gainsboro|ghostwhite|gold|goldenrod|gray|green|greenyellow|grey|honeydew|hotpink|indianred|indigo|ivory|khaki|lavender|lavenderblush|lawngreen|lemonchiffon|lightblue|lightcoral|lightcyan|lightgoldenrodyellow|lightgray|lightgreen|lightgrey|lightpink|lightsalmon|lightseagreen|lightskyblue|lightslategray|lightslategrey|lightsteelblue|lightyellow|lime|limegreen|linen|magenta|maroon|mediumaquamarine|mediumblue|mediumorchid|mediumpurple|mediumseagreen|mediumslateblue|mediumspringgreen|mediumturquoise|mediumvioletred|midnightblue|mintcream|mistyrose|moccasin|navajowhite|navy|oldlace|olive|olivedrab|orange|orangered|orchid|palegoldenrod|palegreen|paleturquoise|palevioletred|papayawhip|peachpuff|peru|pink|plum|powderblue|purple|red|rosybrown|royalblue|saddlebrown|salmon|sandybrown|seagreen|seashell|sienna|silver|skyblue|slateblue|slategray|slategrey|snow|springgreen|steelblue|tan|teal|thistle|tomato|turquoise|violet|wheat|white|whitesmoke|yellow|yellowgreen', + 'uicolor': r'(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)', + } - - -css3color = { +properties['css3color'] = { 'color': r'{namedcolor}|{hexcolor}|{rgbcolor}|{rgbacolor}|{hslcolor}|inherit', 'opacity': r'{num}|inherit' } +# CSS Box Module Level 3 +properties['css3box'] = { + 'overflow': '{overflow}\s?{overflow}?', + 'overflow-x': '{overflow}', + 'overflow-y': '{overflow}' + } + class NoSuchProfileException(Exception): """Raised if no profile with given name is found""" pass @@ -202,17 +221,25 @@ class NoSuchProfileException(Exception): class Profiles(object): """ - A dictionary of:: - - profilename: { - propname: propvalue_regex* - } - - Predefined profiles are: - - - 'CSS level 2': Properties defined by CSS2 - + All profiles used for validation. ``cssutils.profiles.profiles`` is a + preset object of this class and used by all properties for validation. + + Predefined profiles are (use + :meth:`~cssutils.profiles.Profiles.propertiesByProfile` to + get a list of defined properties): + + :attr:`~cssutils.profiles.Profiles.Profiles.CSS_LEVEL_2` + Properties defined by CSS2.1 + :attr:`~cssutils.profiles.Profiles.Profiles.CSS_COLOR_LEVEL_3` + CSS 3 color properties + :attr:`~cssutils.profiles.Profiles.Profiles.CSS_BOX_LEVEL_3` + Currently overflow related properties only + """ + CSS_LEVEL_2 = 'CSS Level 2.1' + CSS_COLOR_LEVEL_3 = 'CSS Color Module Level 3' + CSS_BOX_LEVEL_3 = 'CSS Box Module Level 3' + basicmacros = { 'ident': r'[-]?{nmstart}{nmchar}*', 'name': r'{nmchar}+', @@ -246,60 +273,74 @@ class Profiles(object): 'frequency': r'0|{num}k?Hz', 'percentage': r'{num}%', } - - CSS_LEVEL_2 = 'CSS Level 2.1' - CSS_COLOR_LEVEL_3 = 'CSS Color Module Level 3' - + def __init__(self): + """A few known profiles are predefined.""" self._log = cssutils.log - self._profilenames = [] # to keep order, REFACTOR! - self._profiles = {} - self.addProfile(self.CSS_LEVEL_2, css2, css2macros) - self.addProfile(self.CSS_COLOR_LEVEL_3, css3color, css3colormacros) + self._profilenames = [] # to keep order, REFACTOR! + self._profiles = {} + + self.addProfile(self.CSS_LEVEL_2, properties['css2'], css2macros) + self.addProfile(self.CSS_COLOR_LEVEL_3, properties['css3color'], css3colormacros) + self.addProfile(self.CSS_BOX_LEVEL_3, properties['css3box']) + + self.__update_knownnames() def _expand_macros(self, dictionary, macros): """Expand macros in token dictionary""" def macro_value(m): return '(?:%s)' % macros[m.groupdict()['macro']] for key, value in dictionary.items(): - if not callable(value): + if not hasattr(value, '__call__'): while re.search(r'{[a-z][a-z0-9-]*}', value): value = re.sub(r'{(?P[a-z][a-z0-9-]*)}', macro_value, value) dictionary[key] = value return dictionary - + def _compile_regexes(self, dictionary): """Compile all regular expressions into callable objects""" for key, value in dictionary.items(): - if not callable(value): + if not hasattr(value, '__call__'): value = re.compile('^(?:%s)$' % value, re.I).match dictionary[key] = value - + return dictionary + def __update_knownnames(self): + self._knownnames = [] + for properties in self._profiles.values(): + self._knownnames.extend(properties.keys()) + profiles = property(lambda self: sorted(self._profiles.keys()), doc=u'Names of all profiles.') + knownnames = property(lambda self: self._knownnames, + doc="All known property names of all profiles.") + def addProfile(self, profile, properties, macros=None): - """Add a new profile with name ``profile`` (e.g. 'CSS level 2') - and the given ``properties``. ``macros`` are - - ``profile`` - The new profile's name - ``properties`` - A dictionary of ``{ property-name: propery-value }`` items where - property-value is a regex which may use macros defined in given + """Add a new profile with name `profile` (e.g. 'CSS level 2') + and the given `properties`. + + :param profile: + the new `profile`'s name + :param properties: + a dictionary of ``{ property-name: propery-value }`` items where + property-value is a regex which may use macros defined in given ``macros`` or the standard macros Profiles.tokens and Profiles.generalvalues. - - ``propery-value`` may also be a function which takes a single - argument which is the value to validate and which should return - True or False. - Any exceptions which may be raised during this custom validation + + ``propery-value`` may also be a function which takes a single + argument which is the value to validate and which should return + True or False. + Any exceptions which may be raised during this custom validation are reported or raised as all other cssutils exceptions depending on cssutils.log.raiseExceptions which e.g during parsing normally is False so the exceptions would be logged only. + :param macros: + may be used in the given properties definitions. There are some + predefined basic macros which may always be used in + :attr:`Profiles.basicmacros` and :attr:`Profiles.generalmacros`. """ if not macros: macros = {} @@ -307,31 +348,67 @@ class Profiles(object): m.update(self.generalmacros) m.update(macros) properties = self._expand_macros(properties, m) - self._profilenames.append(profile) + self._profilenames.append(profile) self._profiles[profile] = self._compile_regexes(properties) + + self.__update_knownnames() + + def removeProfile(self, profile=None, all=False): + """Remove `profile` or remove `all` profiles. + + :param profile: + profile name to remove + :param all: + if ``True`` removes all profiles to start with a clean state + :exceptions: + - :exc:`cssutils.profiles.NoSuchProfileException`: + If given `profile` cannot be found. + """ + if all: + self._profiles.clear() + else: + try: + del self._profiles[profile] + except KeyError: + raise NoSuchProfileException(u'No profile %r.' % profile) + + self.__update_knownnames() def propertiesByProfile(self, profiles=None): - """Generator: Yield property names, if no profile(s) is given all - profile's properties are used.""" + """Generator: Yield property names, if no `profiles` is given all + profile's properties are used. + + :param profiles: + a single profile name or a list of names. + """ if not profiles: profiles = self.profiles elif isinstance(profiles, basestring): profiles = (profiles, ) - try: for profile in sorted(profiles): for name in sorted(self._profiles[profile].keys()): yield name except KeyError, e: - raise NoSuchProfileException(e) + raise NoSuchProfileException(e) def validate(self, name, value): - """Check if value is valid for given property name using any profile.""" + """Check if `value` is valid for given property `name` using **any** + profile. + + :param name: + a property name + :param value: + a CSS value (string) + :returns: + if the `value` is valid for the given property `name` in any + profile + """ for profile in self.profiles: if name in self._profiles[profile]: try: # custom validation errors are caught - r = bool(self._profiles[profile][name](value)) + r = bool(self._profiles[profile][name](value)) except Exception, e: self._log.error(e, error=Exception) return False @@ -339,29 +416,63 @@ class Profiles(object): return r return False - def validateWithProfile(self, name, value): - """Check if value is valid for given property name returning - (valid, valid_in_profile). - - You may want to check if valid_in_profile is what you expected. - + def validateWithProfile(self, name, value, profiles=None): + """Check if `value` is valid for given property `name` returning + ``(valid, profile)``. + + :param name: + a property name + :param value: + a CSS value (string) + :returns: + ``valid, profiles`` where ``valid`` is if the `value` is valid for + the given property `name` in any profile of given `profiles` + and ``profiles`` the profile names for which the value is valid + (or ``[]`` if not valid at all) + Example: You might expect a valid Profiles.CSS_LEVEL_2 value but - e.g. ``validateWithProfile('color', 'rgba(1,1,1,1)')`` returns + e.g. ``validateWithProfile('color', 'rgba(1,1,1,1)')`` returns (True, Profiles.CSS_COLOR_LEVEL_3) """ - for profilename in self._profilenames: - if name in self._profiles[profilename]: - try: - # custom validation errors are caught - r = (bool(self._profiles[profilename][name](value)), - profilename) - except Exception, e: - self._log.error(e, error=Exception) - r = False, None - if r[0]: - return r - return False, None - - + if name not in self.knownnames: + return False, [] + else: + if not profiles: + profiles = self._profilenames + elif isinstance(profiles, basestring): + profiles = (profiles, ) + + for profilename in profiles: + # check given profiles + if name in self._profiles[profilename]: + validate = self._profiles[profilename][name] + try: + if validate(value): + return True, [profilename] + except Exception, e: + self._log.error(e, error=Exception) + + for profilename in (p for p in self._profilenames if p not in profiles): + # check remaining profiles as well + if name in self._profiles[profilename]: + validate = self._profiles[profilename][name] + try: + if validate(value): + return True, [profilename] + except Exception, e: + self._log.error(e, error=Exception) + + names = [] + for profilename, properties in self._profiles.items(): + # return profile to which name belongs + if name in properties.keys(): + names.append(profilename) + names.sort() + return False, names + +# used by profiles = Profiles() +# set for validation to e.g.``Profiles.CSS_LEVEL_2`` +defaultprofile = None + diff --git a/src/cssutils/script.py b/src/cssutils/script.py index b016ead9a0..12d79bd5d8 100644 --- a/src/cssutils/script.py +++ b/src/cssutils/script.py @@ -4,16 +4,16 @@ __all__ = ['CSSCapture', 'csscombine'] __docformat__ = 'restructuredtext' __version__ = '$Id: parse.py 1323 2008-07-06 18:13:57Z cthedot $' -import codecs -import errno import HTMLParser +import codecs +import cssutils +import errno import logging import os import sys import urllib2 import urlparse -import cssutils try: import cssutils.encutils as encutils except ImportError: @@ -307,65 +307,47 @@ class CSSCapture(object): sf.write(sheet.cssText) sf.close() - -def csscombine(proxypath, sourceencoding=None, targetencoding='utf-8', +def csscombine(path=None, url=None, + sourceencoding=None, targetencoding=None, minify=True): """Combine sheets referred to by @import rules in given CSS proxy sheet -into a single new sheet. + into a single new sheet. :returns: combined cssText, normal or minified :Parameters: - `proxypath` - url or path to a CSSStyleSheet which imports other sheets which + `path` or `url` + path or URL to a CSSStyleSheet which imports other sheets which are then combined into one sheet - `sourceencoding` - encoding of the source sheets including the proxy sheet `targetencoding` encoding of the combined stylesheet, default 'utf-8' `minify` defines if the combined sheet should be minified, default True """ - log = cssutils.log - - log.info('Combining files in proxy %r' % proxypath, neverraise=True) - + cssutils.log.info(u'Combining files from %r' % url, + neverraise=True) if sourceencoding is not None: - log.info('Using source encoding %r' % sourceencoding, - neverraise=True) + cssutils.log.info(u'Using source encoding %r' % sourceencoding, + neverraise=True) + if path: + src = cssutils.parseFile(path, encoding=sourceencoding) + elif url: + src = cssutils.parseUrl(url, encoding=sourceencoding) + else: + sys.exit('Path or URL must be given') - src = cssutils.parseFile(proxypath, encoding=sourceencoding) - srcpath = os.path.dirname(proxypath) - combined = cssutils.css.CSSStyleSheet() - for rule in src.cssRules: - if rule.type == rule.IMPORT_RULE: - fn = os.path.join(srcpath, rule.href) - log.info('Processing @import %r' % fn, - neverraise=True) - importsheet = cssutils.parseFile(fn, encoding=sourceencoding) - importsheet.encoding = None # remove @charset - combined.add(cssutils.css.CSSComment(cssText=u'/* %s */' % - rule.cssText)) - for x in importsheet.cssRules: - if x.type == x.IMPORT_RULE: - log.info('Nested @imports are not combined: %s' % x.cssText, - neverraise=True) - - combined.add(x) - - else: - combined.add(rule) - - log.info('Setting target encoding %r' % targetencoding, neverraise=True) - combined.encoding = targetencoding + result = cssutils.resolveImports(src) + result.encoding = targetencoding + cssutils.log.info(u'Using target encoding: %r' % targetencoding, neverraise=True) if minify: # save old setting and use own serializer oldser = cssutils.ser cssutils.setSerializer(cssutils.serialize.CSSSerializer()) cssutils.ser.prefs.useMinified() - cssText = combined.cssText + cssText = result.cssText cssutils.setSerializer(oldser) else: - cssText = combined.cssText + cssText = result.cssText return cssText + diff --git a/src/cssutils/serialize.py b/src/cssutils/serialize.py index a21eb6aed1..0533901a05 100644 --- a/src/cssutils/serialize.py +++ b/src/cssutils/serialize.py @@ -1,15 +1,15 @@ #!/usr/bin/env python # -*- coding: utf-8 -*- -"""serializer classes for CSS classes - -""" +"""cssutils serializer""" __all__ = ['CSSSerializer', 'Preferences'] __docformat__ = 'restructuredtext' -__version__ = '$Id: serialize.py 1472 2008-09-15 21:14:54Z cthedot $' +__version__ = '$Id: serialize.py 1606 2009-01-03 20:32:17Z cthedot $' + import codecs +import cssutils +import helper import re import xml.dom -import cssutils def _escapecss(e): """ @@ -26,8 +26,7 @@ codecs.register_error('escapecss', _escapecss) class Preferences(object): - """ - controls output of CSSSerializer + """Control output of CSSSerializer. defaultAtKeyword = True Should the literal @keyword from src CSS be used or the default @@ -39,11 +38,12 @@ class Preferences(object): Only used if ``keepAllProperties==False``. defaultPropertyPriority = True - Should the normalized or literal priority be used, e.g. '!important' - or u'!Im\portant' + Should the normalized or literal priority be used, e.g. ``!important`` + or ``!Im\portant`` importHrefFormat = None - Uses hreftype if ``None`` or explicit ``'string'`` or ``'uri'`` + Uses hreftype if ``None`` or format ``"URI"`` if ``'string'`` or + format ``url(URI)`` if ``'uri'`` indent = 4 * ' ' Indentation of e.g Properties inside a CSSStyleDeclaration indentSpecificities = False @@ -87,21 +87,28 @@ class Preferences(object): A Property is valid if it is a known Property with a valid value. Currently CSS 2.1 values as defined in cssproperties.py would be - valid. - + valid. """ def __init__(self, **initials): - """ - Always use named instead of positional parameters - """ + """Always use named instead of positional parameters.""" self.useDefaults() - for key, value in initials.items(): if value: self.__setattr__(key, value) + def __repr__(self): + return u"cssutils.css.%s(%s)" % (self.__class__.__name__, + u', '.join(['\n %s=%r' % (p, self.__getattribute__(p)) for p in self.__dict__] + )) + + def __str__(self): + return u"~,:{;)]/': self._remove_last_if_S() - + # APPEND if indent: self.out.append(self.ser._indentblock(val, self.ser._level+1)) @@ -243,8 +244,11 @@ class Out(object): self.out.append(self.ser.prefs.lineSeparator) elif u';' == val: # end or prop or block self.out.append(self.ser.prefs.lineSeparator) - elif val not in u'}[]()/' and typ not in ('FUNCTION',) and space: + elif val not in u'}[]()/' and typ != 'FUNCTION' and space: self.out.append(self.ser.prefs.spacer) + if typ != 'STRING' and not self.ser.prefs.spacer and \ + self.out and not self.out[-1].endswith(u' '): + self.out.append(u' ') def value(self, delim=u'', end=None, keepS=False): "returns all items joined by delim" @@ -256,19 +260,14 @@ class Out(object): class CSSSerializer(object): - """ - Methods to serialize a CSSStylesheet and its parts + """Serialize a CSSStylesheet and its parts. To use your own serializing method the easiest is to subclass CSS Serializer and overwrite the methods you like to customize. """ - # chars not in URI without quotes around as problematic with other stuff - # really ","? - __forbidden_in_uri_matcher = re.compile(ur'''.*?[\(\)\s\;,]''', re.U).match - def __init__(self, prefs=None): """ - prefs + :param prefs: instance of Preferences """ if not prefs: @@ -319,23 +318,17 @@ class CSSSerializer(object): text = self.prefs.lineSeparator.join(out) return text - def _string(self, s): + def _hash(self, val, type_=None): """ - returns s encloded between "..." and escaped delim charater ", - escape line breaks \\n \\r and \\f + Short form of hash, e.g. #123 instead of #112233 """ - # \n = 0xa, \r = 0xd, \f = 0xc - s = s.replace('\n', '\\a ').replace( - '\r', '\\d ').replace( - '\f', '\\c ') - return u'"%s"' % s.replace('"', u'\\"') - - def _uri(self, uri): - """returns uri enclosed in url() and "..." if necessary""" - if CSSSerializer.__forbidden_in_uri_matcher(uri): - return 'url(%s)' % self._string(uri) + # TODO: add pref for this! + if len(val) == 7 and val[1] == val[2] and\ + val[3] == val[4] and\ + val[5] == val[6]: + return u'#%s%s%s' % (val[1], val[3], val[5]) else: - return 'url(%s)' % uri + return val def _valid(self, x): "checks items valid property and prefs.validOnly" @@ -384,7 +377,7 @@ class CSSSerializer(object): no comments or other things allowed! """ if rule.wellformed: - return u'@charset %s;' % self._string(rule.encoding) + return u'@charset %s;' % helper.string(rule.encoding) else: return u'' @@ -438,8 +431,6 @@ class CSSSerializer(object): rule.hreftype == 'string'): out.append(val, 'STRING') else: - if not len(self.prefs.spacer): - out.append(u' ') out.append(val, 'URI') elif 'media' == typ: # media @@ -468,10 +459,7 @@ class CSSSerializer(object): """ if rule.wellformed: out = Out(self) - out.append(self._atkeyword(rule, u'@namespace')) - if not len(self.prefs.spacer): - out.append(u' ') - + out.append(self._atkeyword(rule, u'@namespace')) for item in rule.seq: typ, val = item.type, item.value if 'namespaceURI' == typ: @@ -509,7 +497,7 @@ class CSSSerializer(object): if rule.name: out.append(self.prefs.spacer) nameout = Out(self) - nameout.append(self._string(rule.name)) + nameout.append(helper.string(rule.name)) for item in rule.seq: nameout.append(item.value, item.type) out.append(nameout.value()) @@ -548,22 +536,26 @@ class CSSSerializer(object): + CSSComments """ styleText = self.do_css_CSSStyleDeclaration(rule.style) - if styleText and rule.wellformed: out = Out(self) out.append(self._atkeyword(rule, u'@page')) - if not len(self.prefs.spacer): - out.append(u' ') - - for item in rule.seq: - out.append(item.value, item.type) - + out.append(rule.selectorText) out.append(u'{') out.append(u'%s%s}' % (styleText, self.prefs.lineSeparator), indent=1) return out.value() else: return u'' + + def do_CSSPageRuleSelector(self, seq): + "Serialize selector of a CSSPageRule" + out = Out(self) + for item in seq: + if item.type == 'IDENT': + out.append(item.value, item.type, space=False) + else: + out.append(item.value, item.type) + return out.value() def do_CSSUnknownRule(self, rule): """ @@ -574,8 +566,6 @@ class CSSSerializer(object): if rule.wellformed: out = Out(self) out.append(rule.atkeyword) - if not len(self.prefs.spacer): - out.append(u' ') stacks = [] for item in rule.seq: @@ -751,7 +741,7 @@ class CSSSerializer(object): out.append(separator) elif isinstance(val, cssutils.css.Property): # PropertySimilarNameList - out.append(self.do_Property(val)) + out.append(val.cssText) if not (self.prefs.omitLastSemicolon and i==len(seq)-1): out.append(u';') out.append(separator) @@ -822,8 +812,7 @@ class CSSSerializer(object): """ a Properties priority "!" S* "important" """ - # TODO: use Out() - + # TODO: use Out() out = [] for part in priorityseq: if hasattr(part, 'cssText'): # comments @@ -836,72 +825,47 @@ class CSSSerializer(object): def do_css_CSSValue(self, cssvalue): """Serializes a CSSValue""" - # TODO: use self._valid(cssvalue)? if not cssvalue: return u'' else: out = Out(self) for item in cssvalue.seq: type_, val = item.type, item.value - if type_ in (cssutils.css.CSSColor, - cssutils.css.CSSValue): - # CSSColor or CSSValue if a CSSValueList - out.append(val.cssText, type_, space=False, keepS=True) + if hasattr(val, 'cssText'): + # RGBColor or CSSValue if a CSSValueList + out.append(val.cssText, type_) else: if val and val[0] == val[-1] and val[0] in '\'"': - val = self._string(val[1:-1]) + val = helper.string(val[1:-1]) # S must be kept! in between values but no extra space - out.append(val, type_, space=False, keepS=True) + out.append(val, type_) + return out.value() def do_css_CSSPrimitiveValue(self, cssvalue): """Serialize a CSSPrimitiveValue""" - # TODO: use self._valid(cssvalue)? if not cssvalue: return u'' else: out = Out(self) - - unary = None for item in cssvalue.seq: type_, val = item.type, item.value - if 'CHAR' == type_ and val in u'+-': - # save for next round - unary = val - continue - if cssutils.css.CSSColor == type_: - # Comment or CSSColor - val = val.cssText - elif type_ in ('DIMENSION', 'NUMBER', 'PERCENTAGE'): - # handle saved unary and add to number - try: - # NUMBER or DIMENSION and is it 0? - if 0 == cssvalue.getFloatValue(): + if type_ in ('DIMENSION', 'NUMBER', 'PERCENTAGE'): + n, d = cssvalue._getNumDim(val) + if 0 == n: + if cssvalue.primitiveType in cssvalue._lengthtypes: + # 0 if zero value val = u'0' else: - # add unary to val if not 0 - # TODO: only for lengths! - if u'-' == unary: - val = unary + val - except xml.dom.InvalidAccessErr, e: - pass - unary = None - elif unary: - # or simple add - out.append(unary, 'CHAR', space=False, keepS=True) - unary = None - + val = u'0' + d + else: + val = unicode(n) + d out.append(val, type_) -# if hasattr(val, 'cssText'): -# # comments or CSSValue if a CSSValueList -# out.append(val.cssText, type_) -# else: -# out.append(val, type_) #? - + return out.value() - def do_css_CSSColor(self, cssvalue): - """Serialize a CSSColor value""" + def do_css_RGBColor(self, cssvalue): + """Serialize a RGBColor value""" if not cssvalue: return u'' else: @@ -910,21 +874,16 @@ class CSSSerializer(object): for item in cssvalue.seq: type_, val = item.type, item.value - # prepare - if 'HASH' == type_: - # TODO: add pref for this! - if len(val) == 7 and val[1] == val[2] and \ - val[3] == val[4] and val[5] == val[6]: - val = u'#%s%s%s' % (val[1], val[3], val[5]) - elif 'CHAR' == type_ and val in u'+-': - # save - for next round - if u'-' == val: - # omit + - unary = val - continue - elif unary: - val = unary + val.cssText - unary = None +# # prepare +# if 'CHAR' == type_ and val in u'+-': +# # save - for next round +# if u'-' == val: +# # omit + +# unary = val +# continue +# elif unary: +# val = unary + val.cssText +# unary = None out.append(val, type_) @@ -960,3 +919,29 @@ class CSSSerializer(object): return u' '.join(out) else: return u'' + + + def do_css_ExpressionValue(self, cssvalue): + """Serialize an ExpressionValue (IE only), + should at least keep the original syntax""" + if not cssvalue: + return u'' + else: + out = Out(self) + for item in cssvalue.seq: + type_, val = item.type, item.value + if type_ in ('DIMENSION', 'NUMBER', 'PERCENTAGE'): + n, d = cssvalue._getNumDim(val) + if 0 == n: + if cssvalue.primitiveType in cssvalue._lengthtypes: + # 0 if zero value + val = u'0' + else: + val = u'0' + d + else: + val = unicode(n) + d + # do no send type_ so no special cases! + out.append(val, None, space=False) + + return out.value() + \ No newline at end of file diff --git a/src/cssutils/stylesheets/__init__.py b/src/cssutils/stylesheets/__init__.py index 07717fd40c..9a51ba7844 100644 --- a/src/cssutils/stylesheets/__init__.py +++ b/src/cssutils/stylesheets/__init__.py @@ -1,16 +1,9 @@ -""" -Document Object Model Level 2 Style Sheets +"""Implements Document Object Model Level 2 Style Sheets http://www.w3.org/TR/2000/PR-DOM-Level-2-Style-20000927/stylesheets.html - -currently implemented: - - MediaList - - MediaQuery (http://www.w3.org/TR/css3-mediaqueries/) - - StyleSheet - - StyleSheetList """ __all__ = ['MediaList', 'MediaQuery', 'StyleSheet', 'StyleSheetList'] __docformat__ = 'restructuredtext' -__version__ = '$Id: __init__.py 1116 2008-03-05 13:52:23Z cthedot $' +__version__ = '$Id: __init__.py 1588 2009-01-01 20:16:13Z cthedot $' from medialist import * from mediaquery import * diff --git a/src/cssutils/stylesheets/medialist.py b/src/cssutils/stylesheets/medialist.py index 815071ae2c..cb4d615204 100644 --- a/src/cssutils/stylesheets/medialist.py +++ b/src/cssutils/stylesheets/medialist.py @@ -1,5 +1,4 @@ -""" -MediaList implements DOM Level 2 Style Sheets MediaList. +"""MediaList implements DOM Level 2 Style Sheets MediaList. TODO: - delete: maybe if deleting from all, replace *all* with all others? @@ -7,49 +6,36 @@ TODO: """ __all__ = ['MediaList'] __docformat__ = 'restructuredtext' -__version__ = '$Id: medialist.py 1423 2008-08-11 12:43:22Z cthedot $' +__version__ = '$Id: medialist.py 1605 2009-01-03 18:27:32Z cthedot $' -import xml.dom -import cssutils from cssutils.css import csscomment from mediaquery import MediaQuery +import cssutils +import xml.dom class MediaList(cssutils.util.Base, cssutils.util.ListSeq): - """ - Provides the abstraction of an ordered collection of media, + """Provides the abstraction of an ordered collection of media, without defining or constraining how this collection is implemented. - A media is always an instance of MediaQuery. - + A single media in the list is an instance of :class:`MediaQuery`. An empty list is the same as a list that contains the medium "all". - Properties - ========== - length: - The number of MediaQuery objects in the list. - mediaText: of type DOMString - The parsable textual representation of this MediaList - self: a list (cssutils) - All MediaQueries in this MediaList - wellformed: - if this list is wellformed - - Format - ====== - :: + Format from CSS2.1:: medium [ COMMA S* medium ]* - New:: + New format with :class:`MediaQuery`:: [, ]* """ def __init__(self, mediaText=None, readonly=False): """ - mediaText - unicodestring of parsable comma separared media - or a list of media + :param mediaText: + Unicodestring of parsable comma separared media + or a (Python) list of media. + :param readonly: + Not used yet. """ super(MediaList, self).__init__() self._wellformed = False @@ -62,27 +48,31 @@ class MediaList(cssutils.util.Base, cssutils.util.ListSeq): self._readonly = readonly + def __repr__(self): + return "cssutils.stylesheets.%s(mediaText=%r)" % ( + self.__class__.__name__, self.mediaText) + + def __str__(self): + return "" % ( + self.__class__.__name__, self.mediaText, id(self)) + length = property(lambda self: len(self), - doc="(DOM readonly) The number of media in the list.") + doc="The number of media in the list (DOM readonly).") def _getMediaText(self): - """ - returns serialized property mediaText - """ return cssutils.ser.do_stylesheets_medialist(self) def _setMediaText(self, mediaText): """ - mediaText + :param mediaText: simple value or comma-separated list of media - DOMException - - - SYNTAX_ERR: (MediaQuery) - Raised if the specified string value has a syntax error and is - unparsable. - - NO_MODIFICATION_ALLOWED_ERR: (self) - Raised if this media list is readonly. + :exceptions: + - - :exc:`~xml.dom.SyntaxErr`: + Raised if the specified string value has a syntax error and is + unparsable. + - - :exc:`~xml.dom.NoModificationAllowedErr`: + Raised if this media list is readonly. """ self._checkReadonly() wellformed = True @@ -121,55 +111,46 @@ class MediaList(cssutils.util.Base, cssutils.util.ListSeq): self._wellformed = True mediaText = property(_getMediaText, _setMediaText, - doc="""(DOM) The parsable textual representation of the media list. - This is a comma-separated list of media.""") - - wellformed = property(lambda self: self._wellformed) + doc="The parsable textual representation of the media list.") def __prepareset(self, newMedium): # used by appendSelector and __setitem__ self._checkReadonly() - + if not isinstance(newMedium, MediaQuery): newMedium = MediaQuery(newMedium) if newMedium.wellformed: return newMedium - + def __setitem__(self, index, newMedium): - """ - overwrites ListSeq.__setitem__ - - Any duplicate items are **not** removed. + """Overwriting ListSeq.__setitem__ + + Any duplicate items are **not yet** removed. """ newMedium = self.__prepareset(newMedium) if newMedium: self.seq[index] = newMedium - # TODO: remove duplicates? - + # TODO: remove duplicates? + def appendMedium(self, newMedium): - """ - (DOM) - Adds the medium newMedium to the end of the list. If the newMedium - is already used, it is first removed. - - newMedium - a string or a MediaQuery object - - returns if newMedium is wellformed - - DOMException - - - INVALID_CHARACTER_ERR: (self) - If the medium contains characters that are invalid in the - underlying style language. - - INVALID_MODIFICATION_ERR (self) - If mediaText is "all" and a new medium is tried to be added. - Exception is "handheld" which is set in any case (Opera does handle - "all, handheld" special, this special case might be removed in the - future). - - NO_MODIFICATION_ALLOWED_ERR: (self) - Raised if this list is readonly. + """Add the `newMedium` to the end of the list. + If the `newMedium` is already used, it is first removed. + + :param newMedium: + a string or a :class:`~cssutils.stylesheets.MediaQuery` + :returns: Wellformedness of `newMedium`. + :exceptions: + - :exc:`~xml.dom.InvalidCharacterErr`: + If the medium contains characters that are invalid in the + underlying style language. + - :exc:`~xml.dom.InvalidModificationErr`: + If mediaText is "all" and a new medium is tried to be added. + Exception is "handheld" which is set in any case (Opera does handle + "all, handheld" special, this special case might be removed in the + future). + - :exc:`~xml.dom.NoModificationAllowedErr`: + Raised if this list is readonly. """ newMedium = self.__prepareset(newMedium) @@ -207,20 +188,19 @@ class MediaList(cssutils.util.Base, cssutils.util.ListSeq): return False def append(self, newMedium): - "overwrites ListSeq.append" + "Same as :meth:`appendMedium`." self.appendMedium(newMedium) - + def deleteMedium(self, oldMedium): - """ - (DOM) - Deletes the medium indicated by oldMedium from the list. + """Delete a medium from the list. - DOMException - - - NO_MODIFICATION_ALLOWED_ERR: (self) - Raised if this list is readonly. - - NOT_FOUND_ERR: (self) - Raised if oldMedium is not in the list. + :param oldMedium: + delete this medium from the list. + :exceptions: + - :exc:`~xml.dom.NotFoundErr`: + Raised if `oldMedium` is not in the list. + - :exc:`~xml.dom.NoModificationAllowedErr`: + Raised if this list is readonly. """ self._checkReadonly() oldMedium = self._normalize(oldMedium) @@ -232,25 +212,15 @@ class MediaList(cssutils.util.Base, cssutils.util.ListSeq): else: self._log.error(u'"%s" not in this MediaList' % oldMedium, error=xml.dom.NotFoundErr) -# raise xml.dom.NotFoundErr( -# u'"%s" not in this MediaList' % oldMedium) def item(self, index): - """ - (DOM) - Returns the mediaType of the index'th element in the list. - If index is greater than or equal to the number of media in the - list, returns None. + """Return the mediaType of the `index`'th element in the list. + If `index` is greater than or equal to the number of media in the + list, returns ``None``. """ try: return self[index].mediaType except IndexError: return None - def __repr__(self): - return "cssutils.stylesheets.%s(mediaText=%r)" % ( - self.__class__.__name__, self.mediaText) - - def __str__(self): - return "" % ( - self.__class__.__name__, self.mediaText, id(self)) + wellformed = property(lambda self: self._wellformed) diff --git a/src/cssutils/stylesheets/mediaquery.py b/src/cssutils/stylesheets/mediaquery.py index 46841a1764..b75ec285cf 100644 --- a/src/cssutils/stylesheets/mediaquery.py +++ b/src/cssutils/stylesheets/mediaquery.py @@ -1,41 +1,22 @@ -""" -MediaQuery, see http://www.w3.org/TR/css3-mediaqueries/ +"""Implements a DOM for MediaQuery, see +http://www.w3.org/TR/css3-mediaqueries/. -A cssutils own implementation, not defined in official DOM - -TODO: - add possibility to - -part of a media_query_list: [, ]* -see stylesheets.MediaList +A cssutils implementation, not defined in official DOM. """ __all__ = ['MediaQuery'] __docformat__ = 'restructuredtext' -__version__ = '$Id: mediaquery.py 1363 2008-07-13 18:14:26Z cthedot $' +__version__ = '$Id: mediaquery.py 1638 2009-01-13 20:39:33Z cthedot $' +import cssutils import re import xml.dom -import cssutils class MediaQuery(cssutils.util.Base): """ - A Media Query consists of a media type and one or more - expressions involving media features. + A Media Query consists of one of :const:`MediaQuery.MEDIA_TYPES` + and one or more expressions involving media features. - Properties - ========== - mediaText: of type DOMString - The parsable textual representation of this MediaQuery - mediaType: of type DOMString - one of MEDIA_TYPES like e.g. 'print' - seq: a list (cssutils) - All parts of this MediaQuery including CSSComments - wellformed: - if this query is wellformed - - Format - ====== - :: + Format:: media_query: [[only | not]? [ and ]*] | [ and ]* @@ -65,7 +46,7 @@ class MediaQuery(cssutils.util.Base): def __init__(self, mediaText=None, readonly=False): """ - mediaText + :param mediaText: unicodestring of parsable media """ super(MediaQuery, self).__init__() @@ -77,26 +58,30 @@ class MediaQuery(cssutils.util.Base): self._readonly = readonly + def __repr__(self): + return "cssutils.stylesheets.%s(mediaText=%r)" % ( + self.__class__.__name__, self.mediaText) + + def __str__(self): + return "" % ( + self.__class__.__name__, self.mediaText, id(self)) + def _getMediaText(self): - """ - returns serialized property mediaText - """ return cssutils.ser.do_stylesheets_mediaquery(self) def _setMediaText(self, mediaText): """ - mediaText - a single media query string, e.g. "print and (min-width: 25cm)" + :param mediaText: + a single media query string, e.g. ``print and (min-width: 25cm)`` - DOMException - - - SYNTAX_ERR: (self) - Raised if the specified string value has a syntax error and is - unparsable. - - INVALID_CHARACTER_ERR: (self) - Raised if the given mediaType is unknown. - - NO_MODIFICATION_ALLOWED_ERR: (self) - Raised if this media query is readonly. + :exceptions: + - :exc:`~xml.dom.SyntaxErr`: + Raised if the specified string value has a syntax error and is + unparsable. + - :exc:`~xml.dom.InvalidCharacterErr`: + Raised if the given mediaType is unknown. + - :exc:`~xml.dom.NoModificationAllowedErr`: + Raised if this media query is readonly. """ self._checkReadonly() tokenizer = self._tokenize2(mediaText) @@ -171,29 +156,21 @@ class MediaQuery(cssutils.util.Base): self.seq = newseq mediaText = property(_getMediaText, _setMediaText, - doc="""(DOM) The parsable textual representation of the media list. - This is a comma-separated list of media.""") - - def _getMediaType(self): - """ - returns serialized property mediaText - """ - return self._mediaType + doc="The parsable textual representation of the media list.") def _setMediaType(self, mediaType): """ - mediaType - one of MEDIA_TYPES + :param mediaType: + one of :attr:`MEDIA_TYPES` - DOMException - - - SYNTAX_ERR: (self) - Raised if the specified string value has a syntax error and is - unparsable. - - INVALID_CHARACTER_ERR: (self) - Raised if the given mediaType is unknown. - - NO_MODIFICATION_ALLOWED_ERR: (self) - Raised if this media query is readonly. + :exceptions: + - :exc:`~xml.dom.SyntaxErr`: + Raised if the specified string value has a syntax error and is + unparsable. + - :exc:`~xml.dom.InvalidCharacterErr`: + Raised if the given mediaType is unknown. + - :exc:`~xml.dom.NoModificationAllowedErr`: + Raised if this media query is readonly. """ self._checkReadonly() nmediaType = self._normalize(mediaType) @@ -223,15 +200,8 @@ class MediaQuery(cssutils.util.Base): else: self.seq.insert(0, mediaType) - mediaType = property(_getMediaType, _setMediaType, - doc="""(DOM) media type (one of MediaQuery.MEDIA_TYPES) of this MediaQuery.""") + mediaType = property(lambda self: self._mediaType, _setMediaType, + doc="The media type of this MediaQuery (one of " + ":attr:`MEDIA_TYPES`).") wellformed = property(lambda self: bool(len(self.seq))) - - def __repr__(self): - return "cssutils.stylesheets.%s(mediaText=%r)" % ( - self.__class__.__name__, self.mediaText) - - def __str__(self): - return "" % ( - self.__class__.__name__, self.mediaText, id(self)) diff --git a/src/cssutils/stylesheets/stylesheet.py b/src/cssutils/stylesheets/stylesheet.py index 487c4f7948..ea8abafabe 100644 --- a/src/cssutils/stylesheets/stylesheet.py +++ b/src/cssutils/stylesheets/stylesheet.py @@ -1,12 +1,10 @@ -""" -StyleSheet implements DOM Level 2 Style Sheets StyleSheet. -""" +"""StyleSheet implements DOM Level 2 Style Sheets StyleSheet.""" __all__ = ['StyleSheet'] __docformat__ = 'restructuredtext' -__version__ = '$Id: stylesheet.py 1284 2008-06-05 16:29:17Z cthedot $' +__version__ = '$Id: stylesheet.py 1629 2009-01-04 18:58:47Z cthedot $' -import urlparse import cssutils +import urlparse class StyleSheet(cssutils.util.Base2): """ @@ -16,7 +14,7 @@ class StyleSheet(cssutils.util.Base2): In HTML, the StyleSheet interface represents either an external style sheet, included via the HTML LINK element, - or an inline STYLE element (-ch: also an @import stylesheet?). + or an inline STYLE element (also an @import stylesheet?). In XML, this interface represents an external style sheet, included via a style sheet @@ -30,14 +28,8 @@ class StyleSheet(cssutils.util.Base2): ownerNode=None, parentStyleSheet=None): """ - type: readonly - This specifies the style sheet language for this - style sheet. The style sheet language is specified - as a content type (e.g. "text/css"). The content - type is often specified in the ownerNode. Also see - the type attribute definition for the LINK element - in HTML 4.0, and the type pseudo-attribute for the - XML style sheet processing instruction. + type + readonly href: readonly If the style sheet is a linked style sheet, the value of this attribute is its location. For inline style @@ -74,12 +66,6 @@ class StyleSheet(cssutils.util.Base2): included by other style sheets, the value of this attribute is None. parentStyleSheet: of type StyleSheet, readonly - For style sheet languages that support the concept - of style sheet inclusion, this attribute represents - the including style sheet, if one exists. If the style - sheet is a top-level style sheet, or the style sheet - language does not support inclusion, the value of this - attribute is None. """ super(StyleSheet, self).__init__() @@ -92,10 +78,31 @@ class StyleSheet(cssutils.util.Base2): self.media = media self.title = title - href = property(lambda self: self._href) + href = property(lambda self: self._href, + doc="If the style sheet is a linked style sheet, the value " + "of this attribute is its location. For inline style " + "sheets, the value of this attribute is None. See the " + "href attribute definition for the LINK element in HTML " + "4.0, and the href pseudo-attribute for the XML style " + "sheet processing instruction.") - ownerNode = property(lambda self: self._ownerNode) + ownerNode = property(lambda self: self._ownerNode, + doc="Not used in cssutils yet.") - parentStyleSheet = property(lambda self: self._parentStyleSheet) + parentStyleSheet = property(lambda self: self._parentStyleSheet, + doc="For style sheet languages that support the concept " + "of style sheet inclusion, this attribute represents " + "the including style sheet, if one exists. If the style " + "sheet is a top-level style sheet, or the style sheet " + "language does not support inclusion, the value of this " + "attribute is None.") - type = property(lambda self: self._type, doc=u'Default: "ext/css"') + type = property(lambda self: self._type, + doc="This specifies the style sheet language for this " + "style sheet. The style sheet language is specified " + "as a content type (e.g. ``text/css``). The content " + "type is often specified in the ownerNode. Also see " + "the type attribute definition for the LINK element " + "in HTML 4.0, and the type pseudo-attribute for the " + "XML style sheet processing instruction. " + "For CSS this is always ``text/css``.") diff --git a/src/cssutils/stylesheets/stylesheetlist.py b/src/cssutils/stylesheets/stylesheetlist.py index f2428641ee..09dc1e573e 100644 --- a/src/cssutils/stylesheets/stylesheetlist.py +++ b/src/cssutils/stylesheets/stylesheetlist.py @@ -1,18 +1,15 @@ -""" -StyleSheetList implements DOM Level 2 Style Sheets StyleSheetList. -""" +"""StyleSheetList implements DOM Level 2 Style Sheets StyleSheetList.""" __all__ = ['StyleSheetList'] __docformat__ = 'restructuredtext' -__version__ = '$Id: stylesheetlist.py 1116 2008-03-05 13:52:23Z cthedot $' +__version__ = '$Id: stylesheetlist.py 1629 2009-01-04 18:58:47Z cthedot $' class StyleSheetList(list): - """ - Interface StyleSheetList (introduced in DOM Level 2) + """Interface `StyleSheetList` (introduced in DOM Level 2) - The StyleSheetList interface provides the abstraction of an ordered - collection of style sheets. + The `StyleSheetList` interface provides the abstraction of an ordered + collection of :class:`~cssutils.stylesheets.StyleSheet` objects. - The items in the StyleSheetList are accessible via an integral index, + The items in the `StyleSheetList` are accessible via an integral index, starting from 0. This Python implementation is based on a standard Python list so e.g. @@ -20,9 +17,9 @@ class StyleSheetList(list): """ def item(self, index): """ - Used to retrieve a style sheet by ordinal index. If index is + Used to retrieve a style sheet by ordinal `index`. If `index` is greater than or equal to the number of style sheets in the list, - this returns None. + this returns ``None``. """ try: return self[index] @@ -30,6 +27,6 @@ class StyleSheetList(list): return None length = property(lambda self: len(self), - doc="""The number of StyleSheets in the list. The range of valid - child stylesheet indices is 0 to length-1 inclusive.""") + doc="The number of :class:`StyleSheet` objects in the list. The range" + " of valid child stylesheet indices is 0 to length-1 inclusive.") diff --git a/src/cssutils/tokenize2.py b/src/cssutils/tokenize2.py index 24607305bc..0abd7c7677 100644 --- a/src/cssutils/tokenize2.py +++ b/src/cssutils/tokenize2.py @@ -4,11 +4,11 @@ """ __all__ = ['Tokenizer', 'CSSProductions'] __docformat__ = 'restructuredtext' -__version__ = '$Id: tokenize2.py 1420 2008-08-09 19:28:34Z cthedot $' +__version__ = '$Id: tokenize2.py 1547 2008-12-10 20:42:26Z cthedot $' -import re -from helper import normalize from cssproductions import * +from helper import normalize +import re class Tokenizer(object): """ @@ -23,6 +23,8 @@ class Tokenizer(object): u'@page': CSSProductions.PAGE_SYM } _linesep = u'\n' + unicodesub = re.compile(r'\\[0-9a-fA-F]{1,6}(?:\r\n|[\t|\r|\n|\f|\x20])?').sub + cleanstring = re.compile(r'\\((\r\n)|[\n|\r|\f])').sub def __init__(self, macros=None, productions=None): """ @@ -38,7 +40,6 @@ class Tokenizer(object): productions)) self.commentmatcher = [x[1] for x in self.tokenmatches if x[0] == 'COMMENT'][0] self.urimatcher = [x[1] for x in self.tokenmatches if x[0] == 'URI'][0] - self.unicodesub = re.compile(r'\\[0-9a-fA-F]{1,6}(?:\r\n|[\t|\r|\n|\f|\x20])?').sub def _expand_macros(self, macros, productions): """returns macro expanded productions, order of productions is kept""" @@ -150,6 +151,9 @@ class Tokenizer(object): # may contain unicode escape, replace with normal char # but do not _normalize (?) value = self.unicodesub(_repl, found) + if name in ('STRING', 'INVALID'): #'URI'? + # remove \ followed by nl (so escaped) from string + value = self.cleanstring('', found) else: if 'ATKEYWORD' == name: diff --git a/src/cssutils/util.py b/src/cssutils/util.py index 7b5b9b3857..81335910ac 100644 --- a/src/cssutils/util.py +++ b/src/cssutils/util.py @@ -2,25 +2,85 @@ """ __all__ = [] __docformat__ = 'restructuredtext' -__version__ = '$Id: util.py 1453 2008-09-08 20:57:19Z cthedot $' - -import codecs -from itertools import ifilter -import types -import urllib2 -import xml.dom +__version__ = '$Id: util.py 1654 2009-02-03 20:16:20Z cthedot $' from helper import normalize +from itertools import ifilter +import css +import codec +import codecs +import errorhandler import tokenize2 -import cssutils -import encutils +import types +import xml.dom -class Base(object): +try: + from _fetchgae import _defaultFetcher +except ImportError, e: + from _fetch import _defaultFetcher + +log = errorhandler.ErrorHandler() + +class _BaseClass(object): """ - Base class for most CSS and StyleSheets classes + Base class for Base, Base2 and _NewBase. + + **Base and Base2 will be removed in the future!** + """ + _log = errorhandler.ErrorHandler() + _prods = tokenize2.CSSProductions + + def _checkReadonly(self): + "Raise xml.dom.NoModificationAllowedErr if rule/... is readonly" + if hasattr(self, '_readonly') and self._readonly: + raise xml.dom.NoModificationAllowedErr( + u'%s is readonly.' % self.__class__) + return True + return False + + def _valuestr(self, t): + """ + Return string value of t (t may be a string, a list of token tuples + or a single tuple in format (type, value, line, col). + Mainly used to get a string value of t for error messages. + """ + if not t: + return u'' + elif isinstance(t, basestring): + return t + else: + return u''.join([x[1] for x in t]) + + +class _NewBase(_BaseClass): + """ + New base class for classes using ProdParser. + + **Currently CSSValue and related ones only.** + """ + def __init__(self): + self._seq = Seq() + + def _setSeq(self, newseq): + """Set value of ``seq`` which is readonly.""" + newseq._readonly = True + self._seq = newseq + + def _tempSeq(self, readonly=False): + "Get a writeable Seq() which is used to set ``seq`` later" + return Seq(readonly=readonly) + + seq = property(lambda self: self._seq, + doc="Internal readonly attribute, **DO NOT USE**!") + + +class Base(_BaseClass): + """ + **Superceded by _NewBase** **Superceded by Base2 which is used for new seq handling class.** - See cssutils.util.Base2 + + Base class for most CSS and StyleSheets classes Contains helper methods for inheriting classes helping parsing @@ -28,9 +88,6 @@ class Base(object): """ __tokenizer2 = tokenize2.Tokenizer() - _log = cssutils.log - _prods = tokenize2.CSSProductions - # for more on shorthand properties see # http://www.dustindiaz.com/css-shorthand/ # format: shorthand: [(propname, mandatorycheck?)*] @@ -66,14 +123,6 @@ class Base(object): """ return normalize(x) - def _checkReadonly(self): - "raises xml.dom.NoModificationAllowedErr if rule/... is readonly" - if hasattr(self, '_readonly') and self._readonly: - raise xml.dom.NoModificationAllowedErr( - u'%s is readonly.' % self.__class__) - return True - return False - def _splitNamespacesOff(self, text_namespaces_tuple): """ returns tuple (text, dict-of-namespaces) or if no namespaces are @@ -141,7 +190,7 @@ class Base(object): """ if token: value = token[1] - return value.replace('\\'+value[0], value[0])[1:-1] + return value.replace('\\' + value[0], value[0])[1: - 1] else: return None @@ -153,10 +202,10 @@ class Base(object): url("\"") => " """ if token: - value = token[1][4:-1].strip() - if value and (value[0] in '\'"') and (value[0] == value[-1]): + value = token[1][4: - 1].strip() + if value and (value[0] in '\'"') and (value[0] == value[ - 1]): # a string "..." or '...' - value = value.replace('\\'+value[0], value[0])[1:-1] + value = value.replace('\\' + value[0], value[0])[1: - 1] return value else: return None @@ -190,7 +239,7 @@ class Base(object): if blockstartonly: # { ends = u'{' - brace = -1 # set to 0 with first { + brace = - 1 # set to 0 with first { elif blockendonly: # } ends = u'}' brace = 1 @@ -205,7 +254,7 @@ class Base(object): # end of mediaquery which may be { or STRING # special case, see below ends = u'{' - brace = -1 # set to 0 with first { + brace = - 1 # set to 0 with first { endtypes = ('STRING',) elif semicolon: ends = u';' @@ -254,7 +303,7 @@ class Base(object): if (brace == bracket == parant == 0) and ( val in ends or typ in endtypes): break - elif mediaqueryendonly and brace == -1 and ( + elif mediaqueryendonly and brace == - 1 and ( bracket == parant == 0) and typ in endtypes: # mediaqueryendonly with STRING break @@ -262,25 +311,12 @@ class Base(object): if separateEnd: # TODO: use this method as generator, then this makes sense if resulttokens: - return resulttokens[:-1], resulttokens[-1] + return resulttokens[: - 1], resulttokens[ - 1] else: return resulttokens, None else: return resulttokens - def _valuestr(self, t): - """ - returns string value of t (t may be a string, a list of token tuples - or a single tuple in format (type, value, line, col). - Mainly used to get a string value of t for error messages. - """ - if not t: - return u'' - elif isinstance(t, basestring): - return t - else: - return u''.join([x[1] for x in t]) - def _adddefaultproductions(self, productions, new=None): """ adds default productions if not already present, used by @@ -295,7 +331,7 @@ class Base(object): "default impl for unexpected @rule" if expected != 'EOF': # TODO: parentStyleSheet=self - rule = cssutils.css.CSSUnknownRule() + rule = css.CSSUnknownRule() rule.cssText = self._tokensupto2(tokenizer, token) if rule.wellformed: seq.append(rule) @@ -307,7 +343,7 @@ class Base(object): def COMMENT(expected, seq, token, tokenizer=None): "default implementation for COMMENT token adds CSSCommentRule" - seq.append(cssutils.css.CSSComment([token])) + seq.append(css.CSSComment([token])) return expected def S(expected, seq, token, tokenizer=None): @@ -351,7 +387,7 @@ class Base(object): returns (wellformed, expected) which the last prod might have set """ wellformed = True - + if initialtoken: # add initialtoken to tokenizer def tokens(): @@ -362,7 +398,7 @@ class Base(object): fulltokenizer = (t for t in tokens()) else: fulltokenizer = tokenizer - + if fulltokenizer: prods = self._adddefaultproductions(productions, new) for token in fulltokenizer: @@ -375,26 +411,15 @@ class Base(object): return wellformed, expected -class Base2(Base): +class Base2(Base, _NewBase): """ - Base class for new seq handling, used by Selector for now only + **Superceded by _NewBase.** + + Base class for new seq handling. """ def __init__(self): self._seq = Seq() - def _setSeq(self, newseq): - """ - sets newseq and makes it readonly - """ - newseq._readonly = True - self._seq = newseq - - seq = property(lambda self: self._seq, doc="seq for most classes") - - def _tempSeq(self, readonly=False): - "get a writeable Seq() which is added later" - return Seq(readonly=readonly) - def _adddefaultproductions(self, productions, new=None): """ adds default productions if not already present, used by @@ -409,10 +434,10 @@ class Base2(Base): "default impl for unexpected @rule" if expected != 'EOF': # TODO: parentStyleSheet=self - rule = cssutils.css.CSSUnknownRule() + rule = css.CSSUnknownRule() rule.cssText = self._tokensupto2(tokenizer, token) if rule.wellformed: - seq.append(rule, cssutils.css.CSSRule.UNKNOWN_RULE, + seq.append(rule, css.CSSRule.UNKNOWN_RULE, line=token[2], col=token[3]) return expected else: @@ -425,7 +450,7 @@ class Base2(Base): if expected == 'EOF': new['wellformed'] = False self._log.error(u'Expected EOF but found comment.', token=token) - seq.append(cssutils.css.CSSComment([token]), 'COMMENT') + seq.append(css.CSSComment([token]), 'COMMENT') return expected def S(expected, seq, token, tokenizer=None): @@ -493,7 +518,7 @@ class Seq(object): else: self._seq.append(item) - def replace(self, index=-1, val=None, typ=None, line=None, col=None): + def replace(self, index= - 1, val=None, typ=None, line=None, col=None): """ if not readonly replace Item at index with new Item or simply replace value or type @@ -503,7 +528,13 @@ class Seq(object): else: self._seq[index] = Item(val, typ, line, col) - def appendToVal(self, val=None, index=-1): + def rstrip(self): + "trims S items from end of Seq" + while self._seq and self._seq[ - 1].type == tokenize2.CSSProductions.S: + # TODO: removed S before CSSComment /**/ /**/ + del self._seq[ - 1] + + def appendToVal(self, val=None, index= - 1): """ if not readonly append to Item's value at index """ @@ -511,15 +542,16 @@ class Seq(object): raise AttributeError('Seq is readonly.') else: old = self._seq[index] - self._seq[index] = Item(old.value + val, old.type, + self._seq[index] = Item(old.value + val, old.type, old.line, old.col) def __repr__(self): "returns a repr same as a list of tuples of (value, type)" - return u'cssutils.%s.%s([\n %s])' % (self.__module__, + return u'cssutils.%s.%s([\n %s], readonly=%r)' % (self.__module__, self.__class__.__name__, u',\n '.join([u'%r' % item for item in self._seq] - )) + ), self._readonly) + def __str__(self): vals = [] for v in self: @@ -529,10 +561,10 @@ class Seq(object): vals.append(v.value[1]) else: vals.append(str(v)) - - return "" % ( - self.__module__, self.__class__.__name__, len(self), - u''.join(vals), id(self)) + + return "" % ( + self.__module__, self.__class__.__name__, len(self), + u''.join(vals), self._readonly, id(self)) class Item(object): """ @@ -671,7 +703,7 @@ class _Namespaces(object): prefix = u'' # None or '' rule = self.__findrule(prefix) if not rule: - self.parentStyleSheet.insertRule(cssutils.css.CSSNamespaceRule( + self.parentStyleSheet.insertRule(css.CSSNamespaceRule( prefix=prefix, namespaceURI=namespaceURI), inOrder=True) @@ -688,7 +720,12 @@ class _Namespaces(object): if rule.prefix == prefix: return rule - def __getNamespaces(self): + @property + def namespaces(self): + """ + A property holding only effective @namespace rules in + self.parentStyleSheets. + """ namespaces = {} for rule in ifilter(lambda r: r.type == r.NAMESPACE_RULE, reversed(self.parentStyleSheet.cssRules)): @@ -696,10 +733,6 @@ class _Namespaces(object): namespaces[rule.prefix] = rule.namespaceURI return namespaces - namespaces = property(__getNamespaces, - doc=u'Holds only effective @namespace rules in self.parentStyleSheets' - '@namespace rules.') - def get(self, prefix, default): return self.namespaces.get(prefix, default) @@ -751,35 +784,6 @@ class _SimpleNamespaces(_Namespaces): self.namespaces) -def _defaultFetcher(url): - """Retrieve data from ``url``. cssutils default implementation of fetch - URL function. - - Returns ``(encoding, string)`` or ``None`` - """ - try: - res = urllib2.urlopen(url) - except OSError, e: - # e.g if file URL and not found - cssutils.log.warn(e, error=OSError) - except (OSError, ValueError), e: - # invalid url, e.g. "1" - cssutils.log.warn(u'ValueError, %s' % e.message, error=ValueError) - except urllib2.HTTPError, e: - # http error, e.g. 404, e can be raised - cssutils.log.warn(u'HTTPError opening url=%r: %s %s' % - (url, e.code, e.msg), error=e) - except urllib2.URLError, e: - # URLError like mailto: or other IO errors, e can be raised - cssutils.log.warn(u'URLError, %s' % e.reason, error=e) - else: - if res: - mimeType, encoding = encutils.getHTTPInfo(res) - if mimeType != u'text/css': - cssutils.log.error(u'Expected "text/css" mime type for url=%r but found: %r' % - (url, mimeType), error=ValueError) - return encoding, res.read() - def _readUrl(url, fetcher=None, overrideEncoding=None, parentEncoding=None): """ Read cssText from url and decode it using all relevant methods (HTTP @@ -824,9 +828,14 @@ def _readUrl(url, fetcher=None, overrideEncoding=None, parentEncoding=None): elif httpEncoding: enctype = 1 # 1. HTTP encoding = httpEncoding - else: - # check content - contentEncoding, explicit = cssutils.codec.detectencoding_str(content) + else: + if isinstance(content, unicode): + # no need to check content as unicode so no BOM + explicit = False + else: + # check content + contentEncoding, explicit = codec.detectencoding_str(content) + if explicit: enctype = 2 # 2. BOM/@charset: explicitly encoding = contentEncoding @@ -838,18 +847,17 @@ def _readUrl(url, fetcher=None, overrideEncoding=None, parentEncoding=None): enctype = 5 # 5. assume UTF-8 encoding = 'utf-8' - try: - # encoding may still be wrong if encoding *is lying*! - if isinstance(content, unicode): - decodedCssText = content - elif content is not None: + if isinstance(content, unicode): + decodedCssText = content + else: + try: + # encoding may still be wrong if encoding *is lying*! decodedCssText = codecs.lookup("css")[1](content, encoding=encoding)[0] - else: + except UnicodeDecodeError, e: + log.warn(e, neverraise=True) decodedCssText = None - except UnicodeDecodeError, e: - cssutils.log.warn(e, neverraise=True) - decodedCssText = None return encoding, enctype, decodedCssText else: return None, None, None +