This commit is contained in:
Kovid Goyal
2008-09-19 10:01:15 -07:00
parent 308b128089
commit fc934f1f66
46 changed files with 10981 additions and 9996 deletions
+63
View File
@@ -0,0 +1,63 @@
"""
Document Object Model Level 2 CSS
http://www.w3.org/TR/2000/PR-DOM-Level-2-Style-20000927/css.html
currently implemented
- CSSStyleSheet
- CSSRuleList
- CSSRule
- CSSComment (cssutils addon)
- CSSCharsetRule
- CSSFontFaceRule
- CSSImportRule
- CSSMediaRule
- CSSNamespaceRule (WD)
- CSSPageRule
- CSSStyleRule
- CSSUnkownRule
- Selector and SelectorList
- CSSStyleDeclaration
- CSS2Properties
- CSSValue
- CSSPrimitiveValue
- CSSValueList
todo
- RGBColor, Rect, Counter
"""
__all__ = [
'CSSStyleSheet',
'CSSRuleList',
'CSSRule',
'CSSComment',
'CSSCharsetRule',
'CSSFontFaceRule'
'CSSImportRule',
'CSSMediaRule',
'CSSNamespaceRule',
'CSSPageRule',
'CSSStyleRule',
'CSSUnknownRule',
'Selector', 'SelectorList',
'CSSStyleDeclaration', 'Property',
'CSSValue', 'CSSPrimitiveValue', 'CSSValueList'
]
__docformat__ = 'restructuredtext'
__version__ = '$Id: __init__.py 1116 2008-03-05 13:52:23Z cthedot $'
from cssstylesheet import *
from cssrulelist import *
from cssrule import *
from csscomment import *
from csscharsetrule import *
from cssfontfacerule import *
from cssimportrule import *
from cssmediarule import *
from cssnamespacerule import *
from csspagerule import *
from cssstylerule import *
from cssunknownrule import *
from selector import *
from selectorlist import *
from cssstyledeclaration import *
from cssvalue import *
+165
View File
@@ -0,0 +1,165 @@
"""CSSCharsetRule implements DOM Level 2 CSS CSSCharsetRule.
TODO:
- check encoding syntax and not codecs.lookup?
"""
__all__ = ['CSSCharsetRule']
__docformat__ = 'restructuredtext'
__version__ = '$Id: csscharsetrule.py 1170 2008-03-20 17:42:07Z cthedot $'
import codecs
import xml.dom
import cssrule
import cssutils
class CSSCharsetRule(cssrule.CSSRule):
"""
The CSSCharsetRule interface represents an @charset rule in a CSS style
sheet. The value of the encoding attribute does not affect the encoding
of text data in the DOM objects; this encoding is always UTF-16
(also in Python?). After a stylesheet is loaded, the value of the
encoding attribute is the value found in the @charset rule. If there
was no @charset in the original document, then no CSSCharsetRule is
created. The value of the encoding attribute may also be used as a hint
for the encoding used on serialization of the style sheet.
The value of the @charset rule (and therefore of the CSSCharsetRule)
may not correspond to the encoding the document actually came in;
character encoding information e.g. in an HTTP header, has priority
(see CSS document representation) but this is not reflected in the
CSSCharsetRule.
Properties
==========
cssText: of type DOMString
The parsable textual representation of this rule
encoding: of type DOMString
The encoding information used in this @charset rule.
Inherits properties from CSSRule
Format
======
charsetrule:
CHARSET_SYM S* STRING S* ';'
BUT: Only valid format is:
@charset "ENCODING";
"""
type = property(lambda self: cssrule.CSSRule.CHARSET_RULE)
def __init__(self, encoding=None, parentRule=None,
parentStyleSheet=None, readonly=False):
"""
encoding:
a valid character encoding
readonly:
defaults to False, not used yet
if readonly allows setting of properties in constructor only
"""
super(CSSCharsetRule, self).__init__(parentRule=parentRule,
parentStyleSheet=parentStyleSheet)
self._atkeyword = '@charset'
self._encoding = None
if encoding:
self.encoding = encoding
self._readonly = readonly
def _getCssText(self):
"""returns serialized property cssText"""
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.
"""
super(CSSCharsetRule, self)._setCssText(cssText)
wellformed = True
tokenizer = self._tokenize2(cssText)
if self._type(self._nexttoken(tokenizer)) != self._prods.CHARSET_SYM:
wellformed = False
self._log.error(u'CSSCharsetRule must start with "@charset "',
error=xml.dom.InvalidModificationErr)
encodingtoken = self._nexttoken(tokenizer)
encodingtype = self._type(encodingtoken)
encoding = self._stringtokenvalue(encodingtoken)
if self._prods.STRING != encodingtype or not encoding:
wellformed = False
self._log.error(u'CSSCharsetRule: no encoding found; %r.' %
self._valuestr(cssText))
semicolon = self._tokenvalue(self._nexttoken(tokenizer))
EOFtype = self._type(self._nexttoken(tokenizer))
if u';' != semicolon or EOFtype not in ('EOF', None):
wellformed = False
self._log.error(u'CSSCharsetRule: Syntax Error: %r.' %
self._valuestr(cssText))
if wellformed:
self.encoding = encoding
cssText = property(fget=_getCssText, fset=_setCssText,
doc="(DOM) The parsable textual representation.")
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.
"""
self._checkReadonly()
tokenizer = self._tokenize2(encoding)
encodingtoken = self._nexttoken(tokenizer)
unexpected = self._nexttoken(tokenizer)
valid = True
if not encodingtoken or unexpected or\
self._prods.IDENT != self._type(encodingtoken):
valid = False
self._log.error(
'CSSCharsetRule: Syntax Error in encoding value %r.' %
encoding)
else:
try:
codecs.lookup(encoding)
except LookupError:
valid = False
self._log.error('CSSCharsetRule: Unknown (Python) encoding %r.' %
encoding)
else:
self._encoding = encoding.lower()
encoding = property(lambda self: self._encoding, _setEncoding,
doc="(DOM)The encoding information used in this @charset rule.")
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 "<cssutils.css.%s object encoding=%r at 0x%x>" % (
self.__class__.__name__, self.encoding, id(self))
+92
View File
@@ -0,0 +1,92 @@
"""CSSComment is not defined in DOM Level 2 at all but a cssutils defined
class only.
Implements CSSRule which is also extended for a CSSComment rule type
"""
__all__ = ['CSSComment']
__docformat__ = 'restructuredtext'
__version__ = '$Id: csscomment.py 1170 2008-03-20 17:42:07Z cthedot $'
import xml.dom
import cssrule
import cssutils
class CSSComment(cssrule.CSSRule):
"""
(cssutils) a CSS comment
Properties
==========
cssText: of type DOMString
The comment text including comment delimiters
Inherits properties from CSSRule
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,
parentStyleSheet=parentStyleSheet)
self._cssText = None
if cssText:
self._setCssText(cssText)
self._readonly = readonly
def _getCssText(self):
"""returns serialized property cssText"""
return cssutils.ser.do_CSSComment(self)
def _setCssText(self, cssText):
"""
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.
"""
super(CSSComment, self)._setCssText(cssText)
tokenizer = self._tokenize2(cssText)
commenttoken = self._nexttoken(tokenizer)
unexpected = self._nexttoken(tokenizer)
if not commenttoken or\
self._type(commenttoken) != self._prods.COMMENT or\
unexpected:
self._log.error(u'CSSComment: Not a CSSComment: %r' %
self._valuestr(cssText),
error=xml.dom.InvalidModificationErr)
else:
self._cssText = self._tokenvalue(commenttoken)
cssText = property(_getCssText, _setCssText,
doc=u"(cssutils) Textual representation of this comment")
def __repr__(self):
return "cssutils.css.%s(cssText=%r)" % (
self.__class__.__name__, self.cssText)
def __str__(self):
return "<cssutils.css.%s object cssText=%r at 0x%x>" % (
self.__class__.__name__, self.cssText, id(self))
+163
View File
@@ -0,0 +1,163 @@
"""CSSFontFaceRule implements DOM Level 2 CSS CSSFontFaceRule.
"""
__all__ = ['CSSFontFaceRule']
__docformat__ = 'restructuredtext'
__version__ = '$Id: cssfontfacerule.py 1284 2008-06-05 16:29:17Z cthedot $'
import xml.dom
import cssrule
import cssutils
from cssstyledeclaration import CSSStyleDeclaration
class CSSFontFaceRule(cssrule.CSSRule):
"""
The CSSFontFaceRule interface represents a @font-face rule in a CSS
style sheet. The @font-face rule is used to hold a set of font
descriptions.
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
======
::
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
style
CSSStyleDeclaration for this CSSStyleRule
"""
super(CSSFontFaceRule, self).__init__(parentRule=parentRule,
parentStyleSheet=parentStyleSheet)
self._atkeyword = u'@font-face'
if style:
self.style = style
else:
self._style = CSSStyleDeclaration(parentRule=self)
self._readonly = readonly
def _getCssText(self):
"""
returns 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.
"""
super(CSSFontFaceRule, self)._setCssText(cssText)
tokenizer = self._tokenize2(cssText)
attoken = self._nexttoken(tokenizer, None)
if self._type(attoken) != self._prods.FONT_FACE_SYM:
self._log.error(u'CSSFontFaceRule: No CSSFontFaceRule found: %s' %
self._valuestr(cssText),
error=xml.dom.InvalidModificationErr)
else:
wellformed = True
beforetokens, brace = self._tokensupto2(tokenizer,
blockstartonly=True,
separateEnd=True)
if self._tokenvalue(brace) != u'{':
wellformed = False
self._log.error(
u'CSSFontFaceRule: No start { of style declaration found: %r' %
self._valuestr(cssText), brace)
# parse stuff before { which should be comments and S only
new = {'wellformed': True}
newseq = self._tempSeq()#[]
beforewellformed, expected = self._parse(expected=':',
seq=newseq, tokenizer=self._tokenize2(beforetokens),
productions={})
wellformed = wellformed and beforewellformed and new['wellformed']
styletokens, braceorEOFtoken = self._tokensupto2(tokenizer,
blockendonly=True,
separateEnd=True)
val, typ = self._tokenvalue(braceorEOFtoken), self._type(braceorEOFtoken)
if val != u'}' and typ != 'EOF':
wellformed = False
self._log.error(
u'CSSFontFaceRule: No "}" after style declaration found: %r' %
self._valuestr(cssText))
nonetoken = self._nexttoken(tokenizer)
if nonetoken:
wellformed = False
self._log.error(u'CSSFontFaceRule: Trailing content found.',
token=nonetoken)
newstyle = CSSStyleDeclaration()
if 'EOF' == typ:
# add again as style needs it
styletokens.append(braceorEOFtoken)
newstyle.cssText = styletokens
if wellformed:
self.style = newstyle
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
def _setStyle(self, style):
"""
style
StyleDeclaration or string
"""
self._checkReadonly()
if isinstance(style, basestring):
self._style = CSSStyleDeclaration(parentRule=self, cssText=style)
else:
self._style._seq = style.seq
style = property(_getStyle, _setStyle,
doc="(DOM) The declaration-block of this rule set.")
def __repr__(self):
return "cssutils.css.%s(style=%r)" % (
self.__class__.__name__, self.style.cssText)
def __str__(self):
return "<cssutils.css.%s object style=%r at 0x%x>" % (
self.__class__.__name__, self.style.cssText, id(self))
+399
View File
@@ -0,0 +1,399 @@
"""CSSImportRule implements DOM Level 2 CSS CSSImportRule.
plus:
``name`` property
http://www.w3.org/TR/css3-cascade/#cascading
"""
__all__ = ['CSSImportRule']
__docformat__ = 'restructuredtext'
__version__ = '$Id: cssimportrule.py 1401 2008-07-29 21:07:54Z cthedot $'
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).
Inherits properties from CSSRule
Format
======
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):
"""
if readonly allows setting of properties in constructor only
Do not use as positional but as keyword attributes only!
href
location of the style sheet to be imported.
mediaText
A list of media types for which this style sheet may be used
as a string
"""
super(CSSImportRule, self).__init__(parentRule=parentRule,
parentStyleSheet=parentStyleSheet)
self._atkeyword = u'@import'
self.hreftype = None
self._styleSheet = None
self._href = None
self.href = href
self._media = cssutils.stylesheets.MediaList()
if mediaText:
self._media.mediaText = mediaText
self._name = name
seq = self._tempSeq()
seq.append(self.href, 'href')
seq.append(self.media, 'media')
seq.append(self.name, 'name')
self._setSeq(seq)
self._readonly = readonly
_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 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.
"""
super(CSSImportRule, self)._setCssText(cssText)
tokenizer = self._tokenize2(cssText)
attoken = self._nexttoken(tokenizer, None)
if self._type(attoken) != self._prods.IMPORT_SYM:
self._log.error(u'CSSImportRule: No CSSImportRule found: %s' %
self._valuestr(cssText),
error=xml.dom.InvalidModificationErr)
else:
# for closures: must be a mutable
new = {'keyword': self._tokenvalue(attoken),
'href': None,
'hreftype': None,
'media': None,
'name': None,
'wellformed': True
}
def __doname(seq, token):
# called by _string or _ident
new['name'] = self._stringtokenvalue(token)
seq.append(new['name'], 'name')
return ';'
def _string(expected, seq, token, tokenizer=None):
if 'href' == expected:
# href
new['href'] = self._stringtokenvalue(token)
new['hreftype'] = 'string'
seq.append(new['href'], 'href')
return 'media name ;'
elif 'name' in expected:
# name
return __doname(seq, token)
else:
new['wellformed'] = False
self._log.error(
u'CSSImportRule: Unexpected string.', token)
return expected
def _uri(expected, seq, token, tokenizer=None):
# href
if 'href' == expected:
uri = self._uritokenvalue(token)
new['hreftype'] = 'uri'
new['href'] = uri
seq.append(new['href'], 'href')
return 'media name ;'
else:
new['wellformed'] = False
self._log.error(
u'CSSImportRule: Unexpected URI.', token)
return expected
def _ident(expected, seq, token, tokenizer=None):
# medialist ending with ; which is checked upon too
if expected.startswith('media'):
mediatokens = self._tokensupto2(
tokenizer, importmediaqueryendonly=True)
mediatokens.insert(0, token) # push found token
last = mediatokens.pop() # retrieve ;
lastval, lasttyp = self._tokenvalue(last), self._type(last)
if lastval != u';' and lasttyp not in ('EOF', self._prods.STRING):
new['wellformed'] = False
self._log.error(u'CSSImportRule: No ";" found: %s' %
self._valuestr(cssText), token=token)
media = cssutils.stylesheets.MediaList()
media.mediaText = mediatokens
if media.wellformed:
new['media'] = media
seq.append(media, 'media')
else:
new['wellformed'] = False
self._log.error(u'CSSImportRule: Invalid MediaList: %s' %
self._valuestr(cssText), token=token)
if lasttyp == self._prods.STRING:
# name
return __doname(seq, last)
else:
return 'EOF' # ';' is token "last"
else:
new['wellformed'] = False
self._log.error(
u'CSSImportRule: Unexpected ident.', token)
return expected
def _char(expected, seq, token, tokenizer=None):
# final ;
val = self._tokenvalue(token)
if expected.endswith(';') and u';' == val:
return 'EOF'
else:
new['wellformed'] = False
self._log.error(
u'CSSImportRule: Unexpected char.', token)
return expected
# import : IMPORT_SYM S* [STRING|URI]
# S* [ medium [ ',' S* medium]* ]? ';' S*
# STRING? # see http://www.w3.org/TR/css3-cascade/#cascading
# ;
newseq = self._tempSeq()
wellformed, expected = self._parse(expected='href',
seq=newseq, tokenizer=tokenizer,
productions={'STRING': _string,
'URI': _uri,
'IDENT': _ident,
'CHAR': _char},
new=new)
# wellformed set by parse
wellformed = wellformed and new['wellformed']
# post conditions
if not new['href']:
wellformed = False
self._log.error(u'CSSImportRule: No href found: %s' %
self._valuestr(cssText))
if expected != 'EOF':
wellformed = False
self._log.error(u'CSSImportRule: No ";" found: %s' %
self._valuestr(cssText))
# set all
if wellformed:
self.atkeyword = new['keyword']
self.hreftype = new['hreftype']
if new['media']:
# use same object
self.media.mediaText = new['media'].mediaText
# put it in newseq too
for index, x in enumerate(newseq):
if x.type == 'media':
newseq.replace(index, self.media,
x.type, x.line, x.col)
break
else:
# reset media
self.media.mediaText = u'all'
newseq.append(self.media, 'media')
self.name = new['name']
self._setSeq(newseq)
self.href = new['href']
if self.styleSheet:
# title is set by href
#self.styleSheet._href = self.href
self.styleSheet._parentStyleSheet = self.parentStyleSheet
cssText = property(fget=_getCssText, fset=_setCssText,
doc="(DOM attribute) The parsable textual representation.")
def _setHref(self, href):
# update seq
for i, item in enumerate(self.seq):
val, typ = item.value, item.type
if 'href' == typ:
self._seq[i] = (href, typ, item.line, item.col)
break
else:
seq = self._tempSeq()
seq.append(self.href, 'href')
self._setSeq(seq)
# set new href
self._href = href
if not self.styleSheet:
# set only if not set before
self.__setStyleSheet()
href = property(lambda self: self._href, _setHref,
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")
def _setName(self, name):
"""raises xml.dom.SyntaxErr if name is not a string"""
if isinstance(name, basestring) or name is None:
# "" or ''
if not name:
name = None
# update seq
for i, item in enumerate(self.seq):
val, typ = item.value, item.type
if 'name' == typ:
self._seq[i] = (name, typ, item.line, item.col)
break
else:
# append
seq = self._tempSeq()
for item in self.seq:
# copy current seq
seq.append(item.value, item.type, item.line, item.col)
seq.append(name, 'name')
self._setSeq(seq)
self._name = name
# set title of referred sheet
if self.styleSheet:
self.styleSheet.title = name
else:
self._log.error(u'CSSImportRule: Not a valid name: %s' % name)
name = property(lambda self: self._name, _setName,
doc=u"An optional name for the imported sheet")
def __setStyleSheet(self):
"""Read new CSSStyleSheet cssText from href using parentStyleSheet.href
Indirectly called if setting ``href``. In case of any error styleSheet
is set to ``None``.
"""
# should simply fail so all errors are catched!
if self.parentStyleSheet and self.href:
# relative href
parentHref = self.parentStyleSheet.href
if parentHref is None:
# use cwd instead
parentHref = u'file:' + urllib.pathname2url(os.getcwd()) + '/'
href = urlparse.urljoin(parentHref, self.href)
# all possible exceptions are ignored (styleSheet is None then)
try:
usedEncoding, enctype, cssText = self.parentStyleSheet._resolveImport(href)
if cssText is None:
# catched in next except below!
raise IOError('Cannot read Stylesheet.')
styleSheet = cssutils.css.CSSStyleSheet(href=href,
media=self.media,
ownerRule=self,
title=self.name)
# inherit fetcher for @imports in styleSheet
styleSheet._setFetcher(self.parentStyleSheet._fetcher)
# contentEncoding with parentStyleSheet.overrideEncoding,
# HTTP or parent
encodingOverride, encoding = None, None
if enctype == 0:
encodingOverride = usedEncoding
elif 5 > enctype > 0:
encoding = usedEncoding
styleSheet._setCssTextWithEncodingOverride(cssText,
encodingOverride=encodingOverride,
encoding=encoding)
except (OSError, IOError, ValueError), e:
self._log.warn(u'CSSImportRule: While processing imported style sheet href=%r: %r'
% (self.href, e), neverraise=True)
else:
self._styleSheet = styleSheet
styleSheet = property(lambda self: self._styleSheet,
doc="(readonly) The style sheet referred to by this rule.")
def _getWellformed(self):
"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 "<cssutils.css.%s object href=%r mediaText=%r name=%r at 0x%x>" % (
self.__class__.__name__, self.href, mediaText, self.name, id(self))
+349
View File
@@ -0,0 +1,349 @@
"""CSSMediaRule implements DOM Level 2 CSS CSSMediaRule.
"""
__all__ = ['CSSMediaRule']
__docformat__ = 'restructuredtext'
__version__ = '$Id: cssmediarule.py 1370 2008-07-14 20:15:03Z cthedot $'
import xml.dom
import cssrule
import cssutils
class CSSMediaRule(cssrule.CSSRule):
"""
Objects implementing the CSSMediaRule interface can be identified by the
MEDIA_RULE constant. On these objects the type attribute must return the
value of that constant.
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
======
media
: MEDIA_SYM S* medium [ COMMA S* medium ]*
STRING? # the name
LBRACE S* ruleset* '}' S*;
"""
# CONSTANT
type = property(lambda self: cssrule.CSSRule.MEDIA_RULE)
def __init__(self, mediaText='all', name=None,
parentRule=None, parentStyleSheet=None, readonly=False):
"""constructor"""
super(CSSMediaRule, self).__init__(parentRule=parentRule,
parentStyleSheet=parentStyleSheet)
self._atkeyword = u'@media'
self._media = cssutils.stylesheets.MediaList(mediaText,
readonly=readonly)
self.name = name
self.cssRules = cssutils.css.cssrulelist.CSSRuleList()
self.cssRules.append = self.insertRule
self.cssRules.extend = self.insertRule
self.cssRules.__delitem__ == self.deleteRule
self._readonly = readonly
def __iter__(self):
"""generator which iterates over cssRules."""
for rule in self.cssRules:
yield rule
def _getCssText(self):
"""return serialized property cssText"""
return cssutils.ser.do_CSSMediaRule(self)
def _setCssText(self, cssText):
"""
:param cssText:
a parseable string or a tuple of (cssText, dict-of-namespaces)
:Exceptions:
- `NAMESPACE_ERR`: (Selector)
Raised if a specified selector uses an unknown namespace
prefix.
- `SYNTAX_ERR`: (self, StyleDeclaration, etc)
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.
"""
super(CSSMediaRule, self)._setCssText(cssText)
# might be (cssText, namespaces)
cssText, namespaces = self._splitNamespacesOff(cssText)
try:
# use parent style sheet ones if available
namespaces = self.parentStyleSheet.namespaces
except AttributeError:
pass
tokenizer = self._tokenize2(cssText)
attoken = self._nexttoken(tokenizer, None)
if self._type(attoken) != self._prods.MEDIA_SYM:
self._log.error(u'CSSMediaRule: No CSSMediaRule found: %s' %
self._valuestr(cssText),
error=xml.dom.InvalidModificationErr)
else:
# media "name"? { cssRules }
# media
wellformed = True
mediatokens, end = self._tokensupto2(tokenizer,
mediaqueryendonly=True,
separateEnd=True)
if u'{' == self._tokenvalue(end) or self._prods.STRING == self._type(end):
newmedia = cssutils.stylesheets.MediaList()
newmedia.mediaText = mediatokens
# name (optional)
name = None
nameseq = self._tempSeq()
if self._prods.STRING == self._type(end):
name = self._stringtokenvalue(end)
# TODO: for now comments are lost after name
nametokens, end = self._tokensupto2(tokenizer,
blockstartonly=True,
separateEnd=True)
wellformed, expected = self._parse(None, nameseq, nametokens, {})
if not wellformed:
self._log.error(u'CSSMediaRule: Syntax Error: %s' %
self._valuestr(cssText))
# check for {
if u'{' != self._tokenvalue(end):
self._log.error(u'CSSMediaRule: No "{" found: %s' %
self._valuestr(cssText))
return
# cssRules
cssrulestokens, braceOrEOF = self._tokensupto2(tokenizer,
mediaendonly=True,
separateEnd=True)
nonetoken = self._nexttoken(tokenizer, None)
if (u'}' != self._tokenvalue(braceOrEOF) and
'EOF' != self._type(braceOrEOF)):
self._log.error(u'CSSMediaRule: No "}" found.',
token=braceOrEOF)
elif nonetoken:
self._log.error(u'CSSMediaRule: Trailing content found.',
token=nonetoken)
else:
# for closures: must be a mutable
newcssrules = [] #cssutils.css.CSSRuleList()
new = {'wellformed': True }
def ruleset(expected, seq, token, tokenizer):
rule = cssutils.css.CSSStyleRule(parentRule=self)
rule.cssText = (self._tokensupto2(tokenizer, token),
namespaces)
if rule.wellformed:
rule._parentStyleSheet=self.parentStyleSheet
seq.append(rule)
return expected
def atrule(expected, seq, token, tokenizer):
# TODO: get complete rule!
tokens = self._tokensupto2(tokenizer, token)
atval = self._tokenvalue(token)
if atval in ('@charset ', '@font-face', '@import', '@namespace',
'@page', '@media'):
self._log.error(
u'CSSMediaRule: This rule is not allowed in CSSMediaRule - ignored: %s.'
% self._valuestr(tokens),
token = token,
error=xml.dom.HierarchyRequestErr)
else:
rule = cssutils.css.CSSUnknownRule(parentRule=self,
parentStyleSheet=self.parentStyleSheet)
rule.cssText = tokens
if rule.wellformed:
seq.append(rule)
return expected
def COMMENT(expected, seq, token, tokenizer=None):
seq.append(cssutils.css.CSSComment([token]))
return expected
tokenizer = (t for t in cssrulestokens) # TODO: not elegant!
wellformed, expected = self._parse(braceOrEOF,
newcssrules,
tokenizer, {
'COMMENT': COMMENT,
'CHARSET_SYM': atrule,
'FONT_FACE_SYM': atrule,
'IMPORT_SYM': atrule,
'NAMESPACE_SYM': atrule,
'PAGE_SYM': atrule,
'MEDIA_SYM': atrule,
'ATKEYWORD': atrule
},
default=ruleset,
new=new)
# no post condition
if newmedia.wellformed and wellformed:
# keep reference
self._media.mediaText = newmedia.mediaText
self.name = name
self._setSeq(nameseq)
del self.cssRules[:]
for r in newcssrules:
self.cssRules.append(r)
cssText = property(_getCssText, _setCssText,
doc="(DOM attribute) The parsable textual representation.")
def _setName(self, name):
if isinstance(name, basestring) or name is None:
# "" or ''
if not name:
name = None
self._name = name
else:
self._log.error(u'CSSImportRule: Not a valid name: %s' % name)
name = property(lambda self: self._name, _setName,
doc=u"An optional name for the media rules")
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)
def deleteRule(self, index):
"""
index
within the media block's rule collection of the rule to remove.
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.
"""
self._checkReadonly()
try:
self.cssRules[index]._parentRule = None # detach
del self.cssRules[index] # remove from @media
except IndexError:
raise xml.dom.IndexSizeErr(
u'CSSMediaRule: %s is not a valid index in the rulelist of length %i' % (
index, self.cssRules.length))
def add(self, rule):
"""Add rule to end of this mediarule. Same as ``.insertRule(rule)``."""
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
content.
cssutils also allows rule to be a valid **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
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.
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.
"""
self._checkReadonly()
# check position
if index is None:
index = len(self.cssRules)
elif index < 0 or index > self.cssRules.length:
raise xml.dom.IndexSizeErr(
u'CSSMediaRule: Invalid index %s for CSSRuleList with a length of %s.' % (
index, self.cssRules.length))
# parse
if isinstance(rule, basestring):
tempsheet = cssutils.css.CSSStyleSheet()
tempsheet.cssText = rule
if len(tempsheet.cssRules) != 1 or (tempsheet.cssRules and
not isinstance(tempsheet.cssRules[0], cssutils.css.CSSRule)):
self._log.error(u'CSSMediaRule: Invalid Rule: %s' % rule)
return
rule = tempsheet.cssRules[0]
elif not isinstance(rule, cssutils.css.CSSRule):
self._log.error(u'CSSMediaRule: Not a CSSRule: %s' % rule)
return
# CHECK HIERARCHY
# @charset @import @page @namespace @media
if isinstance(rule, cssutils.css.CSSCharsetRule) or \
isinstance(rule, cssutils.css.CSSFontFaceRule) or \
isinstance(rule, cssutils.css.CSSImportRule) or \
isinstance(rule, cssutils.css.CSSNamespaceRule) or \
isinstance(rule, cssutils.css.CSSPageRule) or \
isinstance(rule, CSSMediaRule):
self._log.error(u'CSSMediaRule: This type of rule is not allowed here: %s' %
rule.cssText,
error=xml.dom.HierarchyRequestErr)
return
self.cssRules.insert(index, rule)
rule._parentRule = self
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 "<cssutils.css.%s object mediaText=%r at 0x%x>" % (
self.__class__.__name__, self.media.mediaText, id(self))
+306
View File
@@ -0,0 +1,306 @@
"""CSSNamespaceRule currently implements
http://dev.w3.org/csswg/css3-namespace/
(until 0.9.5a2: http://www.w3.org/TR/2006/WD-css3-namespace-20060828/)
"""
__all__ = ['CSSNamespaceRule']
__docformat__ = 'restructuredtext'
__version__ = '$Id: cssnamespacerule.py 1305 2008-06-22 18:42:51Z cthedot $'
import xml.dom
import cssrule
import cssutils
from cssutils.helper import Deprecated
class CSSNamespaceRule(cssrule.CSSRule):
"""
Represents an @namespace rule within a CSS style sheet.
The @namespace at-rule declares a namespace prefix and associates
it with a given namespace (a string). This namespace prefix can then be
used in namespace-qualified names such as those described in the
Selectors Module [SELECT] or the Values and Units module [CSS3VAL].
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.
Inherits properties from CSSRule
Format
======
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):
"""
:Parameters:
namespaceURI
The namespace URI (a simple string!) which is bound to the
given prefix. If no prefix is set
(``CSSNamespaceRule.prefix==''``) the namespace defined by
namespaceURI is set as the default namespace
prefix
The prefix used in the stylesheet for the given
``CSSNamespaceRule.uri``.
cssText
if no namespaceURI is given cssText must be given to set
a namespaceURI as this is readonly later on
parentStyleSheet
sheet where this rule belongs to
Do not use as positional but as keyword parameters only!
If readonly allows setting of properties in constructor only
format namespace::
namespace
: NAMESPACE_SYM S* [namespace_prefix S*]? [STRING|URI] S* ';' S*
;
namespace_prefix
: IDENT
;
"""
super(CSSNamespaceRule, self).__init__(parentRule=parentRule,
parentStyleSheet=parentStyleSheet)
self._atkeyword = u'@namespace'
self._prefix = u''
self._namespaceURI = None
if namespaceURI:
self.namespaceURI = namespaceURI
self.prefix = prefix
tempseq = self._tempSeq()
tempseq.append(self.prefix, 'prefix')
tempseq.append(self.namespaceURI, 'namespaceURI')
self._setSeq(tempseq)
elif cssText is not None:
self.cssText = cssText
if parentStyleSheet:
self._parentStyleSheet = parentStyleSheet
self._readonly = readonly
def _getCssText(self):
"""
returns 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)
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.
"""
super(CSSNamespaceRule, self)._setCssText(cssText)
tokenizer = self._tokenize2(cssText)
attoken = self._nexttoken(tokenizer, None)
if self._type(attoken) != self._prods.NAMESPACE_SYM:
self._log.error(u'CSSNamespaceRule: No CSSNamespaceRule found: %s' %
self._valuestr(cssText),
error=xml.dom.InvalidModificationErr)
else:
# for closures: must be a mutable
new = {'keyword': self._tokenvalue(attoken),
'prefix': u'',
'uri': None,
'wellformed': True
}
def _ident(expected, seq, token, tokenizer=None):
# the namespace prefix, optional
if 'prefix or uri' == expected:
new['prefix'] = self._tokenvalue(token)
seq.append(new['prefix'], 'prefix')
return 'uri'
else:
new['wellformed'] = False
self._log.error(
u'CSSNamespaceRule: Unexpected ident.', token)
return expected
def _string(expected, seq, token, tokenizer=None):
# the namespace URI as a STRING
if expected.endswith('uri'):
new['uri'] = self._stringtokenvalue(token)
seq.append(new['uri'], 'namespaceURI')
return ';'
else:
new['wellformed'] = False
self._log.error(
u'CSSNamespaceRule: Unexpected string.', token)
return expected
def _uri(expected, seq, token, tokenizer=None):
# the namespace URI as URI which is DEPRECATED
if expected.endswith('uri'):
uri = self._uritokenvalue(token)
new['uri'] = uri
seq.append(new['uri'], 'namespaceURI')
return ';'
else:
new['wellformed'] = False
self._log.error(
u'CSSNamespaceRule: Unexpected URI.', token)
return expected
def _char(expected, seq, token, tokenizer=None):
# final ;
val = self._tokenvalue(token)
if ';' == expected and u';' == val:
return 'EOF'
else:
new['wellformed'] = False
self._log.error(
u'CSSNamespaceRule: Unexpected char.', token)
return expected
# "NAMESPACE_SYM S* [namespace_prefix S*]? [STRING|URI] S* ';' S*"
newseq = self._tempSeq()
wellformed, expected = self._parse(expected='prefix or uri',
seq=newseq, tokenizer=tokenizer,
productions={'IDENT': _ident,
'STRING': _string,
'URI': _uri,
'CHAR': _char},
new=new)
# wellformed set by parse
wellformed = wellformed and new['wellformed']
# post conditions
if new['uri'] is None:
wellformed = False
self._log.error(u'CSSNamespaceRule: No namespace URI found: %s' %
self._valuestr(cssText))
if expected != 'EOF':
wellformed = False
self._log.error(u'CSSNamespaceRule: No ";" found: %s' %
self._valuestr(cssText))
# set all
if wellformed:
self.atkeyword = new['keyword']
self._prefix = new['prefix']
self.namespaceURI = new['uri']
self._setSeq(newseq)
cssText = property(fget=_getCssText, fset=_setCssText,
doc="(DOM attribute) The parsable textual representation.")
def _setNamespaceURI(self, namespaceURI):
"""
DOMException on setting
:param namespaceURI: the initial value for this rules namespaceURI
:Exceptions:
- `NO_MODIFICATION_ALLOWED_ERR`:
(CSSRule) Raised if this rule is readonly or a namespaceURI is
already set in this rule.
"""
self._checkReadonly()
if not self._namespaceURI:
# initial setting
self._namespaceURI = namespaceURI
tempseq = self._tempSeq()
tempseq.append(namespaceURI, 'namespaceURI')
self._setSeq(tempseq) # makes seq readonly!
elif self._namespaceURI != namespaceURI:
self._log.error(u'CSSNamespaceRule: namespaceURI is readonly.',
error=xml.dom.NoModificationAllowedErr)
namespaceURI = property(lambda self: self._namespaceURI, _setNamespaceURI,
doc="URI (string!) of the defined namespace.")
def _setPrefix(self, prefix=None):
"""
DOMException on setting
:param prefix: the new prefix
:Exceptions:
- `SYNTAX_ERR`: (TODO)
Raised if the specified CSS string value has a syntax error and
is unparsable.
- `NO_MODIFICATION_ALLOWED_ERR`: CSSRule)
Raised if this rule is readonly.
"""
self._checkReadonly()
if not prefix:
prefix = u''
else:
tokenizer = self._tokenize2(prefix)
prefixtoken = self._nexttoken(tokenizer, None)
if not prefixtoken or self._type(prefixtoken) != self._prods.IDENT:
self._log.error(u'CSSNamespaceRule: No valid prefix "%s".' %
self._valuestr(prefix),
error=xml.dom.SyntaxErr)
return
else:
prefix = self._tokenvalue(prefixtoken)
# update seg
for i, x in enumerate(self._seq):
if x == self._prefix:
self._seq[i] = (prefix, 'prefix', None, None)
break
else:
# put prefix at the beginning!
self._seq[0] = (prefix, 'prefix', None, None)
# set new prefix
self._prefix = prefix
prefix = property(lambda self: self._prefix, _setPrefix,
doc="Prefix used for the defined namespace.")
# def _setParentStyleSheet(self, parentStyleSheet):
# self._parentStyleSheet = parentStyleSheet
#
# parentStyleSheet = property(lambda self: self._parentStyleSheet,
# _setParentStyleSheet,
# doc=u"Containing CSSStyleSheet.")
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 "<cssutils.css.%s object namespaceURI=%r prefix=%r at 0x%x>" % (
self.__class__.__name__, self.namespaceURI, self.prefix, id(self))
+286
View File
@@ -0,0 +1,286 @@
"""CSSPageRule implements DOM Level 2 CSS CSSPageRule.
"""
__all__ = ['CSSPageRule']
__docformat__ = 'restructuredtext'
__version__ = '$Id: csspagerule.py 1284 2008-06-05 16:29:17Z cthedot $'
import xml.dom
import cssrule
import cssutils
from selectorlist import SelectorList
from cssstyledeclaration import CSSStyleDeclaration
class CSSPageRule(cssrule.CSSRule):
"""
The CSSPageRule interface represents a @page rule within a CSS style
sheet. The @page rule is used to specify the dimensions, orientation,
margins, etc. of a page box for paged media.
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
======
::
page
: PAGE_SYM S* pseudo_page? S*
LBRACE S* declaration [ ';' S* declaration ]* '}' S*
;
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
selectorText
type string
style
CSSStyleDeclaration for this CSSStyleRule
"""
super(CSSPageRule, self).__init__(parentRule=parentRule,
parentStyleSheet=parentStyleSheet)
self._atkeyword = u'@page'
tempseq = self._tempSeq()
if selectorText:
self.selectorText = selectorText
tempseq.append(self.selectorText, 'selectorText')
else:
self._selectorText = u''
if style:
self.style = style
tempseq.append(self.style, 'style')
else:
self._style = CSSStyleDeclaration(parentRule=self)
self._setSeq(tempseq)
self._readonly = readonly
def __parseSelectorText(self, selectorText):
"""
parses 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}
def _char(expected, seq, token, tokenizer=None):
# pseudo_page, :left, :right or :first
val = self._tokenvalue(token)
if ':' == expected and u':' == val:
try:
identtoken = tokenizer.next()
except StopIteration:
self._log.error(
u'CSSPageRule selectorText: No IDENT found.', token)
else:
ival, ityp = self._tokenvalue(identtoken), self._type(identtoken)
if self._prods.IDENT != ityp:
self._log.error(
u'CSSPageRule selectorText: Expected IDENT but found: %r' %
ival, token)
else:
new['selector'] = val + ival
seq.append(new['selector'], 'selector')
return 'EOF'
return expected
else:
new['wellformed'] = False
self._log.error(
u'CSSPageRule selectorText: Unexpected CHAR: %r' % val, token)
return expected
def S(expected, seq, token, tokenizer=None):
"Does not raise if EOF is found."
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=':',
seq=newseq, tokenizer=self._tokenize2(selectorText),
productions={'CHAR': _char,
'COMMENT': COMMENT,
'S': S},
new=new)
wellformed = wellformed and new['wellformed']
newselector = new['selector']
# post conditions
if expected == 'ident':
self._log.error(
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)
return newselector, newseq
def _getCssText(self):
"""
returns 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.
"""
super(CSSPageRule, self)._setCssText(cssText)
tokenizer = self._tokenize2(cssText)
if self._type(self._nexttoken(tokenizer)) != self._prods.PAGE_SYM:
self._log.error(u'CSSPageRule: No CSSPageRule found: %s' %
self._valuestr(cssText),
error=xml.dom.InvalidModificationErr)
else:
wellformed = True
selectortokens, startbrace = self._tokensupto2(tokenizer,
blockstartonly=True,
separateEnd=True)
styletokens, braceorEOFtoken = self._tokensupto2(tokenizer,
blockendonly=True,
separateEnd=True)
nonetoken = self._nexttoken(tokenizer)
if self._tokenvalue(startbrace) != u'{':
wellformed = False
self._log.error(
u'CSSPageRule: No start { of style declaration found: %r' %
self._valuestr(cssText), startbrace)
elif nonetoken:
wellformed = False
self._log.error(
u'CSSPageRule: Trailing content found.', token=nonetoken)
newselector, newselectorseq = self.__parseSelectorText(selectortokens)
newstyle = CSSStyleDeclaration()
val, typ = self._tokenvalue(braceorEOFtoken), self._type(braceorEOFtoken)
if val != u'}' and typ != 'EOF':
wellformed = False
self._log.error(
u'CSSPageRule: No "}" after style declaration found: %r' %
self._valuestr(cssText))
else:
if 'EOF' == typ:
# add again as style needs it
styletokens.append(braceorEOFtoken)
newstyle.cssText = styletokens
if wellformed:
self._selectorText = newselector # 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.")
def _getSelectorText(self):
"""
wrapper for cssutils Selector object
"""
return self._selectorText
def _setSelectorText(self, selectorText):
"""
wrapper for cssutils Selector object
selector: 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.
"""
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
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
"""
self._checkReadonly()
if isinstance(style, basestring):
self._style.cssText = style
else:
# cssText would be serialized with optional preferences
# so use seq!
self._style._seq = style.seq
style = property(_getStyle, _setStyle,
doc="(DOM) The declaration-block of this rule set.")
def __repr__(self):
return "cssutils.css.%s(selectorText=%r, style=%r)" % (
self.__class__.__name__, self.selectorText, self.style.cssText)
def __str__(self):
return "<cssutils.css.%s object selectorText=%r style=%r at 0x%x>" % (
self.__class__.__name__, self.selectorText, self.style.cssText,
id(self))
+349
View File
@@ -0,0 +1,349 @@
"""CSS2Properties (partly!) implements DOM Level 2 CSS CSS2Properties used
by CSSStyleDeclaration
TODO: CSS2Properties
If an implementation does implement this interface, it is expected to
understand the specific syntax of the shorthand properties, and apply
their semantics; when the margin property is set, for example, the
marginTop, marginRight, marginBottom and marginLeft properties are
actually being set by the underlying implementation.
When dealing with CSS "shorthand" properties, the shorthand properties
should be decomposed into their component longhand properties as
appropriate, and when querying for their value, the form returned
should be the shortest form exactly equivalent to the declarations made
in the ruleset. However, if there is no shorthand declaration that
could be added to the ruleset without changing in any way the rules
already declared in the ruleset (i.e., by adding longhand rules that
were previously not declared in the ruleset), then the empty string
should be returned for the shorthand property.
For example, querying for the font property should not return
"normal normal normal 14pt/normal Arial, sans-serif", when
"14pt Arial, sans-serif" suffices. (The normals are initial values, and
are implied by use of the longhand property.)
If the values for all the longhand properties that compose a particular
string are the initial values, then a string consisting of all the
initial values should be returned (e.g. a border-width value of
"medium" should be returned as such, not as "").
For some shorthand properties that take missing values from other
sides, such as the margin, padding, and border-[width|style|color]
properties, the minimum number of sides possible should be used; i.e.,
"0px 10px" will be returned instead of "0px 10px 0px 10px".
If the value of a shorthand property can not be decomposed into its
component longhand properties, as is the case for the font property
with a value of "menu", querying for the values of the component
longhand properties should return the empty string.
TODO: CSS2Properties DOMImplementation
The interface found within this section are not mandatory. A DOM
application can use the hasFeature method of the DOMImplementation
interface to determine whether it is supported or not. The feature
string for this extended interface listed in this section is "CSS2"
and the version is "2.0".
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']
__docformat__ = 'restructuredtext'
__version__ = '$Id: cssproperties.py 1469 2008-09-15 19:06:00Z cthedot $'
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<macro>[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
for retrieving and setting properties within a CSSStyleDeclaration.
The attributes of this interface correspond to all the properties
specified in CSS2. Getting an attribute of this interface is
equivalent to calling the getPropertyValue method of the
CSSStyleDeclaration interface. Setting an attribute of this
interface is equivalent to calling the setProperty method of the
CSSStyleDeclaration interface.
cssutils actually also allows usage of ``del`` to remove a CSS property
from a CSSStyleDeclaration.
This is an abstract class, the following functions need to be present
in inheriting class:
- ``_getP``
- ``_setP``
- ``_delP``
"""
# actual properties are set after the class definition!
def _getP(self, CSSname): pass
def _setP(self, CSSname, value): pass
def _delP(self, CSSname): pass
# 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()]
# add CSS2Properties to CSSStyleDeclaration:
def __named_property_def(DOMname):
"""
closure to keep name known in each properties accessor function
DOMname is converted to CSSname here, so actual calls use CSSname
"""
CSSname = _toCSSname(DOMname)
def _get(self): return self._getP(CSSname)
def _set(self, value): self._setP(CSSname, value)
def _del(self): self._delP(CSSname)
return _get, _set, _del
# add all CSS2Properties to CSSStyleDeclaration
for DOMname in CSS2Properties._properties:
setattr(CSS2Properties, DOMname,
property(*__named_property_def(DOMname)))
+134
View File
@@ -0,0 +1,134 @@
"""CSSRule implements DOM Level 2 CSS CSSRule."""
__all__ = ['CSSRule']
__docformat__ = 'restructuredtext'
__version__ = '$Id: cssrule.py 1177 2008-03-20 17:47:23Z cthedot $'
import xml.dom
import cssutils
class CSSRule(cssutils.util.Base2):
"""
Abstract base interface for any type of CSS statement. This includes
both rule sets and at-rules. An implementation is expected to preserve
all rules specified in a CSS style sheet, even if the rule is not
recognized by the parser. Unrecognized rules are represented using the
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
"""
"""
CSSRule type constants.
An integer indicating which type of rule this is.
"""
COMMENT = -1 # cssutils only
UNKNOWN_RULE = 0 #u
STYLE_RULE = 1 #s
CHARSET_RULE = 2 #c
IMPORT_RULE = 3 #i
MEDIA_RULE = 4 #m
FONT_FACE_RULE = 5 #f
PAGE_RULE = 6 #p
NAMESPACE_RULE = 7 # CSSOM
_typestrings = ['UNKNOWN_RULE', 'STYLE_RULE', 'CHARSET_RULE', 'IMPORT_RULE',
'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
"""
super(CSSRule, self).__init__()
self._parentRule = parentRule
self._parentStyleSheet = parentStyleSheet
self._setSeq(self._tempSeq())
# 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"""
if not self.atkeyword or (self._normalize(akw) ==
self._normalize(self.atkeyword)):
self._atkeyword = akw
else:
self._log.error(u'%s: Invalid atkeyword for this rule: %r' %
(self._normalize(self.atkeyword), akw),
error=xml.dom.InvalidModificationErr)
atkeyword = property(lambda self: self._atkeyword, _setAtkeyword,
doc=u"@keyword for @rules")
parentRule = property(lambda self: self._parentRule,
doc=u"READONLY")
parentStyleSheet = property(lambda self: self._parentStyleSheet,
doc=u"READONLY")
wellformed = property(lambda self: False,
doc=u"READONLY")
typeString = property(lambda self: CSSRule._typestrings[self.type],
doc="Name of this rules type.")
+60
View File
@@ -0,0 +1,60 @@
"""
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 $'
class CSSRuleList(list):
"""
The CSSRuleList object represents an (ordered) list of statements.
The items in the CSSRuleList are accessible via an integral index,
starting from 0.
Subclasses a standard Python list so theoretically all standard list
methods are available. Setting methods like ``__init__``, ``append``,
``extend`` or ``__setslice__`` are added later on instances of this
class if so desired.
E.g. CSSStyleSheet adds ``append`` which is not available in a simple
instance of this class!
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"
pass
def __notimplemented(self, *ignored):
"no direct setting possible"
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
collection represents the order of the rules in the CSS style
sheet. If index is greater than or equal to the number of rules in
the list, this returns None.
Returns CSSRule, the style rule at the index position in the
CSSRuleList, or None if that is not a valid index.
"""
try:
return self[index]
except IndexError:
return None
length = property(lambda self: len(self),
doc="(DOM) The number of CSSRules in the list.")
+651
View File
@@ -0,0 +1,651 @@
"""CSSStyleDeclaration implements DOM Level 2 CSS CSSStyleDeclaration and
extends CSS2Properties
see
http://www.w3.org/TR/1998/REC-CSS2-19980512/syndata.html#parsing-errors
Unknown properties
------------------
User agents must ignore a declaration with an unknown property.
For example, if the style sheet is::
H1 { color: red; rotation: 70minutes }
the user agent will treat this as if the style sheet had been::
H1 { color: red }
Cssutils gives a message about any unknown properties but
keeps any property (if syntactically correct).
Illegal values
--------------
User agents must ignore a declaration with an illegal value. For example::
IMG { float: left } /* correct CSS2 */
IMG { float: left here } /* "here" is not a value of 'float' */
IMG { background: "red" } /* keywords cannot be quoted in CSS2 */
IMG { border-width: 3 } /* a unit must be specified for length values */
A CSS2 parser would honor the first rule and ignore the rest, as if the
style sheet had been::
IMG { float: left }
IMG { }
IMG { }
IMG { }
Cssutils again will issue a message (WARNING in this case) about invalid
CSS2 property values.
TODO:
This interface is also used to provide a read-only access to the
computed values of an element. See also the ViewCSS interface.
- return computed values and not literal values
- simplify unit pairs/triples/quadruples
2px 2px 2px 2px -> 2px for border/padding...
- normalize compound properties like:
background: no-repeat left url() #fff
-> background: #fff url() no-repeat left
"""
__all__ = ['CSSStyleDeclaration', 'Property']
__docformat__ = 'restructuredtext'
__version__ = '$Id: cssstyledeclaration.py 1284 2008-06-05 16:29:17Z cthedot $'
import xml.dom
import cssutils
from cssproperties import CSS2Properties
from property import Property
class CSSStyleDeclaration(CSS2Properties, cssutils.util.Base2):
"""
The CSSStyleDeclaration class represents a single CSS declaration
block. This class may be used to determine the style properties
currently set in a block or to set style properties explicitly
within the block.
While an implementation may not recognize all CSS properties within
a CSS declaration block, it is expected to provide access to all
specified properties in the style sheet through the
CSSStyleDeclaration interface.
Furthermore, implementations that support a specific level of CSS
should correctly handle CSS shorthand properties for that level. For
a further discussion of shorthand properties, see the CSS2Properties
interface.
Additionally the CSS2Properties interface is implemented.
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
DOM name, so e.g. ``fontStyle`` for property 'font-style'.
These may be used as::
>>> style = CSSStyleDeclaration(cssText='color: red')
>>> style.color = 'green'
>>> print style.color
green
>>> del style.color
>>> print style.color # print empty string
Format
======
[Property: Value Priority?;]* [Property: Value Priority?]?
"""
def __init__(self, cssText=u'', parentRule=None, readonly=False):
"""
cssText
Shortcut, sets CSSStyleDeclaration.cssText
parentRule
The CSS rule that contains this declaration block or
None if this CSSStyleDeclaration is not attached to a CSSRule.
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
name
a string or Property, uses normalized name and not literalname
"""
if isinstance(nameOrProperty, Property):
name = nameOrProperty.name
else:
name = self._normalize(nameOrProperty)
return name in self.__nnames()
def __iter__(self):
"""
iterator of set Property objects with different normalized names.
"""
def properties():
for name in self.__nnames():
yield self.getProperty(name)
return properties()
def __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.
``CSSName`` will be always normalized.
"""
return self.getPropertyValue(CSSName)
def __setitem__(self, CSSName, value):
"""Set value of property ``CSSName``. ``value`` may also be a tuple of
(value, priority), e.g. style['color'] = ('red', 'important')
``CSSName`` will be always normalized.
"""
priority = None
if type(value) == tuple:
value, priority = value
return self.setProperty(CSSName, value, priority)
def __delitem__(self, CSSName):
"""Delete property ``CSSName`` from this declaration.
If property is not in this declaration return u'' just like
removeProperty.
``CSSName`` will be always normalized.
"""
return self.removeProperty(CSSName)
# overwritten accessor functions for CSS2Properties' properties
def _getP(self, CSSName):
"""
(DOM CSS2Properties)
Overwritten here and effectively the same as
``self.getPropertyValue(CSSname)``.
Parameter is in CSSname format ('font-style'), see CSS2Properties.
Example::
>>> style = CSSStyleDeclaration(cssText='font-style:italic;')
>>> print style.fontStyle
italic
"""
return self.getPropertyValue(CSSName)
def _setP(self, CSSName, value):
"""
(DOM CSS2Properties)
Overwritten here and effectively the same as
``self.setProperty(CSSname, value)``.
Only known CSS2Properties may be set this way, otherwise an
AttributeError is raised.
For these unknown properties ``setPropertyValue(CSSname, value)``
has to be called explicitly.
Also setting the priority of properties needs to be done with a
call like ``setPropertyValue(CSSname, value, priority)``.
Example::
>>> style = CSSStyleDeclaration()
>>> style.fontStyle = 'italic'
>>> # or
>>> style.setProperty('font-style', 'italic', '!important')
"""
self.setProperty(CSSName, value)
# TODO: Shorthand ones
def _delP(self, CSSName):
"""
(cssutils only)
Overwritten here and effectively the same as
``self.removeProperty(CSSname)``.
Example::
>>> style = CSSStyleDeclaration(cssText='font-style:italic;')
>>> del style.fontStyle
>>> print style.fontStyle # prints u''
"""
self.removeProperty(CSSName)
def _getCssText(self):
"""
returns serialized property cssText
"""
return cssutils.ser.do_css_CSSStyleDeclaration(self)
def _setCssText(self, cssText):
"""
Setting this attribute will result in the parsing of the new value
and resetting of all the properties in the declaration block
including the removal or addition of properties.
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.
"""
self._checkReadonly()
tokenizer = self._tokenize2(cssText)
# for closures: must be a mutable
new = {'wellformed': True}
def ident(expected, seq, token, tokenizer=None):
# a property
tokens = self._tokensupto2(tokenizer, starttoken=token,
semicolon=True)
if self._tokenvalue(tokens[-1]) == u';':
tokens.pop()
property = Property()
property.cssText = tokens
if property.wellformed:
seq.append(property, 'Property')
else:
self._log.error(u'CSSStyleDeclaration: Syntax Error in Property: %s'
% self._valuestr(tokens))
# does not matter in this case
return expected
def unexpected(expected, seq, token, tokenizer=None):
# error, find next ; or } to omit upto next property
ignored = self._tokenvalue(token) + self._valuestr(
self._tokensupto2(tokenizer, propertyvalueendonly=True))
self._log.error(u'CSSStyleDeclaration: Unexpected token, ignoring upto %r.' %
ignored,token)
# does not matter in this case
return expected
# [Property: Value;]* Property: Value?
newseq = self._tempSeq()
wellformed, expected = self._parse(expected=None,
seq=newseq, tokenizer=tokenizer,
productions={'IDENT': ident},#, 'CHAR': char},
default=unexpected)
# wellformed set by parse
# post conditions
# do not check wellformed as invalid things are removed anyway
#if wellformed:
self._setSeq(newseq)
cssText = property(_getCssText, _setCssText,
doc="(DOM) A parsable textual representation of the declaration\
block excluding the surrounding curly braces.")
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!
"""
return cssutils.ser.do_css_CSSStyleDeclaration(self, separator)
def _getParentRule(self):
return self._parentRule
def _setParentRule(self, parentRule):
self._parentRule = parentRule
parentRule = property(_getParentRule, _setParentRule,
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.
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.
The order of the properties is fully kept as in the original
stylesheet.
"""
if name and not all:
# single prop but list
p = self.getProperty(name)
if p:
return [p]
else:
return []
elif not all:
# effective Properties in name order
return [self.getProperty(name)for name in self.__nnames()]
else:
# all properties or all with this name
nname = self._normalize(name)
properties = []
for item in self.seq:
val = item.value
if isinstance(val, Property) and (
(bool(nname) == False) or (val.name == nname)):
properties.append(val)
return properties
def getProperty(self, name, normalize=True):
"""
Returns the effective Property object.
name
of the CSS property, always lowercase (even if not normalized)
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.
"""
nname = self._normalize(name)
found = None
for item in reversed(self.seq):
val = item.value
if isinstance(val, Property):
if (normalize and nname == val.name) or name == val.literalname:
if val.priority:
return val
elif not found:
found = val
return found
def getPropertyCSSValue(self, name, normalize=True):
"""
Returns CSSValue, the value of the effective property if it has been
explicitly set for this declaration block.
name
of the CSS property, always lowercase (even if not normalized)
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.
(DOM)
Used to retrieve the object representation of the value of a CSS
property if it has been explicitly set within this declaration
block. Returns None if the property has not been set.
(This method returns None if the property is a shorthand
property. Shorthand property values can only be accessed and
modified as strings, using the getPropertyValue and setProperty
methods.)
**cssutils currently always returns a CSSValue if the property is
set.**
for more on shorthand properties see
http://www.dustindiaz.com/css-shorthand/
"""
nname = self._normalize(name)
if nname in self._SHORTHANDPROPERTIES:
self._log.info(
u'CSSValue for shorthand property "%s" should be None, this may be implemented later.' %
nname, neverraise=True)
p = self.getProperty(name, normalize)
if p:
return p.cssValue
else:
return None
def getPropertyValue(self, name, normalize=True):
"""
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
of the CSS property, always lowercase (even if not normalized)
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.
"""
p = self.getProperty(name, normalize)
if p:
return p.value
else:
return u''
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
of the CSS property, always lowercase (even if not normalized)
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.
"""
p = self.getProperty(name, normalize)
if p:
return p.priority
else:
return u''
def removeProperty(self, name, normalize=True):
"""
(DOM)
Used to remove a CSS property if it has been explicitly set within
this declaration block.
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
of the CSS property
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.
raises DOMException
- NO_MODIFICATION_ALLOWED_ERR: (self)
Raised if this declaration is readonly or the property is
readonly.
"""
self._checkReadonly()
r = self.getPropertyValue(name, normalize=normalize)
newseq = self._tempSeq()
if normalize:
# remove all properties with name == nname
nname = self._normalize(name)
for item in self.seq:
if not (isinstance(item.value, Property) and item.value.name == nname):
newseq.appendItem(item)
else:
# remove all properties with literalname == name
for item in self.seq:
if not (isinstance(item.value, Property) and item.value.literalname == name):
newseq.appendItem(item)
self._setSeq(newseq)
return r
def setProperty(self, name, value=None, priority=u'', normalize=True):
"""
(DOM)
Used to set a property value and priority within this declaration
block.
name
of the CSS property to set (in W3C DOM the parameter is called
"propertyName"), always lowercase (even if not normalized)
If a property with this name is present it will be reset
cssutils also allowed name to be a 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
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.
"""
self._checkReadonly()
if isinstance(name, Property):
newp = name
name = newp.literalname
else:
newp = Property(name, value, priority)
if not newp.wellformed:
self._log.warn(u'Invalid Property: %s: %s %s'
% (name, value, priority))
else:
nname = self._normalize(name)
properties = self.getProperties(name, all=(not normalize))
for property in reversed(properties):
if normalize and property.name == nname:
property.cssValue = newp.cssValue.cssText
property.priority = newp.priority
break
elif property.literalname == name:
property.cssValue = newp.cssValue.cssText
property.priority = newp.priority
break
else:
self.seq._readonly = False
self.seq.append(newp, 'Property')
self.seq._readonly = True
def item(self, index):
"""
(DOM)
Used to 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
of the property to retrieve, negative values behave like
negative indexes on Python lists, so -1 is the last element
returns the name of the property at this ordinal position. The
empty string if no property exists at this position.
ATTENTION:
Only properties with a different name 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.
"""
names = list(self.__nnames())
try:
return names[index]
except IndexError:
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 "<cssutils.css.%s object length=%r (all: %r) at 0x%x>" % (
self.__class__.__name__, self.length,
len(self.getProperties(all=True)), id(self))
+242
View File
@@ -0,0 +1,242 @@
"""CSSStyleRule implements DOM Level 2 CSS CSSStyleRule.
"""
__all__ = ['CSSStyleRule']
__docformat__ = 'restructuredtext'
__version__ = '$Id: cssstylerule.py 1284 2008-06-05 16:29:17Z cthedot $'
import xml.dom
import cssrule
import cssutils
from selectorlist import SelectorList
from cssstyledeclaration import CSSStyleDeclaration
class CSSStyleRule(cssrule.CSSRule):
"""
The CSSStyleRule object represents a ruleset specified (if any) in a CSS
style sheet. It provides access to a declaration block as well as to the
associated group of selectors.
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::
: 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):
"""
:Parameters:
selectorText
string parsed into selectorList
style
string parsed into CSSStyleDeclaration for this CSSStyleRule
readonly
if True allows setting of properties in constructor only
"""
super(CSSStyleRule, self).__init__(parentRule=parentRule,
parentStyleSheet=parentStyleSheet)
self._selectorList = SelectorList(parentRule=self)
self._style = CSSStyleDeclaration(parentRule=self)
if selectorText:
self.selectorText = selectorText
if style:
self.style = style
self._readonly = readonly
def _getCssText(self):
"""
returns 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)
Raised if the specified selector uses an unknown namespace
prefix.
- `SYNTAX_ERR`: (self, StyleDeclaration, etc)
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.
"""
super(CSSStyleRule, self)._setCssText(cssText)
# might be (cssText, namespaces)
cssText, namespaces = self._splitNamespacesOff(cssText)
try:
# use parent style sheet ones if available
namespaces = self.parentStyleSheet.namespaces
except AttributeError:
pass
tokenizer = self._tokenize2(cssText)
selectortokens = self._tokensupto2(tokenizer, blockstartonly=True)
styletokens = self._tokensupto2(tokenizer, blockendonly=True)
trail = self._nexttoken(tokenizer)
if trail:
self._log.error(u'CSSStyleRule: Trailing content: %s' %
self._valuestr(cssText), token=trail)
elif not selectortokens:
self._log.error(u'CSSStyleRule: No selector found: %r' %
self._valuestr(cssText))
elif self._tokenvalue(selectortokens[0]).startswith(u'@'):
self._log.error(u'CSSStyleRule: No style rule: %r' %
self._valuestr(cssText),
error=xml.dom.InvalidModificationErr)
else:
wellformed = True
bracetoken = selectortokens.pop()
if self._tokenvalue(bracetoken) != u'{':
wellformed = False
self._log.error(
u'CSSStyleRule: No start { of style declaration found: %r' %
self._valuestr(cssText), bracetoken)
elif not selectortokens:
wellformed = False
self._log.error(u'CSSStyleRule: No selector found: %r.' %
self._valuestr(cssText), bracetoken)
newselectorlist = SelectorList(selectorText=(selectortokens,
namespaces),
parentRule=self)
newstyle = CSSStyleDeclaration()
if not styletokens:
wellformed = False
self._log.error(
u'CSSStyleRule: No style declaration or "}" found: %r' %
self._valuestr(cssText))
else:
braceorEOFtoken = styletokens.pop()
val, typ = self._tokenvalue(braceorEOFtoken), self._type(braceorEOFtoken)
if val != u'}' and typ != 'EOF':
wellformed = False
self._log.error(
u'CSSStyleRule: No "}" after style declaration found: %r' %
self._valuestr(cssText))
else:
if 'EOF' == typ:
# add again as style needs it
styletokens.append(braceorEOFtoken)
newstyle.cssText = styletokens
if wellformed:
self._selectorList = newselectorlist
self.style = newstyle
cssText = property(_getCssText, _setCssText,
doc="(DOM) The parsable textual representation of the rule.")
def __getNamespaces(self):
"uses children namespaces if not attached to a sheet, else the sheet's ones"
try:
return self.parentStyleSheet.namespaces
except AttributeError:
return self.selectorList._namespaces
_namespaces = property(__getNamespaces, doc=u"""if this Rule is
attached to a CSSStyleSheet the namespaces of that sheet are mirrored
here. While the Rule is not attached the namespaces of selectorList
are used.""")
def _setSelectorList(self, selectorList):
"""
:param selectorList: selectorList, only content is used, not the actual
object
"""
self._checkReadonly()
self.selectorText = selectorList.selectorText
selectorList = property(lambda self: self._selectorList, _setSelectorList,
doc="The SelectorList of this rule.")
def _setSelectorText(self, selectorText):
"""
wrapper for cssutils SelectorList object
:param selectorText: of type string, might also be a comma separated list
of selectors
:Exceptions:
- `NAMESPACE_ERR`: (Selector)
Raised if the specified selector uses an unknown namespace
prefix.
- `SYNTAX_ERR`: (SelectorList, Selector)
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()
self._selectorList.selectorText = selectorText
selectorText = property(lambda self: self._selectorList.selectorText,
_setSelectorText,
doc="""(DOM) The textual representation of the selector for the
rule set.""")
def _setStyle(self, style):
"""
:param style: CSSStyleDeclaration or string, only the cssText of a
declaration is used, not the actual object
"""
self._checkReadonly()
if isinstance(style, basestring):
self._style.cssText = style
else:
# cssText would be serialized with optional preferences
# so use _seq!
self._style._seq = style._seq
style = property(lambda self: self._style, _setStyle,
doc="(DOM) The declaration-block of this rule set.")
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 "<cssutils.css.%s object selector=%r style=%r _namespaces=%r at 0x%x>" % (
self.__class__.__name__, self.selectorText, self.style.cssText,
self._namespaces, id(self))
+674
View File
@@ -0,0 +1,674 @@
"""
CSSStyleSheet implements DOM Level 2 CSS CSSStyleSheet.
Partly also:
- http://dev.w3.org/csswg/cssom/#the-cssstylesheet
- http://www.w3.org/TR/2006/WD-css3-namespace-20060828/
TODO:
- ownerRule and ownerNode
"""
__all__ = ['CSSStyleSheet']
__docformat__ = 'restructuredtext'
__version__ = '$Id: cssstylesheet.py 1429 2008-08-11 19:01:52Z cthedot $'
import xml.dom
import cssutils.stylesheets
from cssutils.util import _Namespaces, _SimpleNamespaces, _readUrl
from cssutils.helper import Deprecated
class CSSStyleSheet(cssutils.stylesheets.StyleSheet):
"""
The CSSStyleSheet interface 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.
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]* ]*
"""
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
"""
super(CSSStyleSheet, self).__init__(
'text/css', href, media, title, disabled,
ownerNode, parentStyleSheet)
self._ownerRule = ownerRule
self.cssRules = cssutils.css.CSSRuleList()
self.cssRules.append = self.insertRule
self.cssRules.extend = self.insertRule
self._namespaces = _Namespaces(parentStyleSheet=self, log=self._log)
self._readonly = readonly
# used only during setting cssText by parse*()
self.__encodingOverride = None
self._fetcher = None
def __iter__(self):
"generator which iterates over cssRules."
for rule in self.cssRules:
yield rule
def _cleanNamespaces(self):
"removes all namespace rules with same namespaceURI but last one set"
rules = self.cssRules
namespaceitems = self.namespaces.items()
i = 0
while i < len(rules):
rule = rules[i]
if rule.type == rule.NAMESPACE_RULE and \
(rule.prefix, rule.namespaceURI) not in namespaceitems:
self.deleteRule(i)
else:
i += 1
def _getUsedURIs(self):
"returns set of URIs used in the sheet"
useduris = set()
for r1 in self:
if r1.STYLE_RULE == r1.type:
useduris.update(r1.selectorList._getUsedUris())
elif r1.MEDIA_RULE == r1.type:
for r2 in r1:
if r2.type == r2.STYLE_RULE:
useduris.update(r2.selectorList._getUsedUris())
return useduris
def _getCssText(self):
return cssutils.ser.do_CSSStyleSheet(self)
def _setCssText(self, cssText):
"""
(cssutils)
Parses ``cssText`` and overwrites the whole stylesheet.
:param cssText:
a parseable string or a tuple of (cssText, dict-of-namespaces)
:Exceptions:
- `NAMESPACE_ERR`:
If a namespace prefix is found which is not declared.
- `NO_MODIFICATION_ALLOWED_ERR`: (self)
Raised if the rule is readonly.
- `SYNTAX_ERR`:
Raised if the specified CSS string value has a syntax error and
is unparsable.
"""
self._checkReadonly()
cssText, namespaces = self._splitNamespacesOff(cssText)
if not namespaces:
namespaces = _SimpleNamespaces(log=self._log)
tokenizer = self._tokenize2(cssText)
newseq = [] #cssutils.css.CSSRuleList()
# for closures: must be a mutable
new = {'encoding': None, # needed for setting encoding of @import rules
'namespaces': namespaces}
def S(expected, seq, token, tokenizer=None):
# @charset must be at absolute beginning of style sheet
if expected == 0:
return 1
else:
return expected
def COMMENT(expected, seq, token, tokenizer=None):
"special: sets parent*"
comment = cssutils.css.CSSComment([token],
parentStyleSheet=self.parentStyleSheet)
seq.append(comment)
return expected
def charsetrule(expected, seq, token, tokenizer):
rule = cssutils.css.CSSCharsetRule(parentStyleSheet=self)
rule.cssText = self._tokensupto2(tokenizer, token)
if expected > 0 or len(seq) > 0:
self._log.error(
u'CSSStylesheet: CSSCharsetRule only allowed at beginning of stylesheet.',
token, xml.dom.HierarchyRequestErr)
else:
if rule.wellformed:
seq.append(rule)
new['encoding'] = rule.encoding
return 1
def importrule(expected, seq, token, tokenizer):
if new['encoding']:
# set temporarily as used by _resolveImport
# save newEncoding which have been set by resolveImport
self.__newEncoding = new['encoding']
rule = cssutils.css.CSSImportRule(parentStyleSheet=self)
rule.cssText = self._tokensupto2(tokenizer, token)
if expected > 1:
self._log.error(
u'CSSStylesheet: CSSImportRule not allowed here.',
token, xml.dom.HierarchyRequestErr)
else:
if rule.wellformed:
#del rule._parentEncoding # remove as later it is read from this sheet!
seq.append(rule)
try:
# remove as only used temporarily but may not be set at all
del self.__newEncoding
except AttributeError, e:
pass
return 1
def namespacerule(expected, seq, token, tokenizer):
rule = cssutils.css.CSSNamespaceRule(
cssText=self._tokensupto2(tokenizer, token),
parentStyleSheet=self)
if expected > 2:
self._log.error(
u'CSSStylesheet: CSSNamespaceRule not allowed here.',
token, xml.dom.HierarchyRequestErr)
else:
if rule.wellformed:
seq.append(rule)
# temporary namespaces given to CSSStyleRule and @media
new['namespaces'][rule.prefix] = rule.namespaceURI
return 2
def fontfacerule(expected, seq, token, tokenizer):
rule = cssutils.css.CSSFontFaceRule(parentStyleSheet=self)
rule.cssText = self._tokensupto2(tokenizer, token)
if rule.wellformed:
seq.append(rule)
return 3
def mediarule(expected, seq, token, tokenizer):
rule = cssutils.css.CSSMediaRule()
rule.cssText = (self._tokensupto2(tokenizer, token),
new['namespaces'])
if rule.wellformed:
rule._parentStyleSheet=self
for r in rule:
r._parentStyleSheet=self
seq.append(rule)
return 3
def pagerule(expected, seq, token, tokenizer):
rule = cssutils.css.CSSPageRule(parentStyleSheet=self)
rule.cssText = self._tokensupto2(tokenizer, token)
if rule.wellformed:
seq.append(rule)
return 3
def unknownrule(expected, seq, token, tokenizer):
self._log.warn(
u'CSSStylesheet: Unknown @rule found.',
token, neverraise=True)
rule = cssutils.css.CSSUnknownRule(parentStyleSheet=self)
rule.cssText = self._tokensupto2(tokenizer, token)
if rule.wellformed:
seq.append(rule)
return expected
def ruleset(expected, seq, token, tokenizer):
rule = cssutils.css.CSSStyleRule()
rule.cssText = (self._tokensupto2(tokenizer, token),
new['namespaces'])
if rule.wellformed:
rule._parentStyleSheet=self
seq.append(rule)
return 3
# expected:
# ['CHARSET', 'IMPORT', 'NAMESPACE', ('PAGE', 'MEDIA', ruleset)]
wellformed, expected = self._parse(0, newseq, tokenizer,
{'S': S,
'COMMENT': COMMENT,
'CDO': lambda *ignored: None,
'CDC': lambda *ignored: None,
'CHARSET_SYM': charsetrule,
'FONT_FACE_SYM': fontfacerule,
'IMPORT_SYM': importrule,
'NAMESPACE_SYM': namespacerule,
'PAGE_SYM': pagerule,
'MEDIA_SYM': mediarule,
'ATKEYWORD': unknownrule
},
default=ruleset)
if wellformed:
del self.cssRules[:]
for rule in newseq:
self.insertRule(rule, _clean=False)
self._cleanNamespaces()
cssText = property(_getCssText, _setCssText,
"(cssutils) a textual representation of the stylesheet")
def _resolveImport(self, url):
"""Read (encoding, enctype, decodedContent) from ``url`` for @import
sheets."""
try:
# only available during parse of a complete sheet
selfAsParentEncoding = self.__newEncoding
except AttributeError:
try:
# explicit @charset
selfAsParentEncoding = self.cssRules[0].encoding
except (IndexError, AttributeError):
# default not UTF-8 but None!
selfAsParentEncoding = None
return _readUrl(url, fetcher=self._fetcher,
overrideEncoding=self.__encodingOverride,
parentEncoding=selfAsParentEncoding)
def _setCssTextWithEncodingOverride(self, cssText, encodingOverride=None,
encoding=None):
"""Set cssText but use ``encodingOverride`` to overwrite detected
encoding. This is used by parse and @import during setting of cssText.
If ``encoding`` is given use this but do not save it as encodingOverride"""
if encodingOverride:
# encoding during resolving of @import
self.__encodingOverride = encodingOverride
self.__newEncoding = encoding # save for nested @import
self.cssText = cssText
if encodingOverride:
# set encodingOverride explicit again!
self.encoding = self.__encodingOverride
# remove?
self.__encodingOverride = None
elif encoding:
# may e.g. be httpEncoding
self.encoding = encoding
def _setFetcher(self, fetcher=None):
"""sets @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.
"""
try:
rule = self.cssRules[0]
except IndexError:
rule = None
if rule and rule.CHARSET_RULE == rule.type:
if encoding:
rule.encoding = encoding
else:
self.deleteRule(0)
elif encoding:
self.insertRule(cssutils.css.CSSCharsetRule(encoding=encoding), 0)
def _getEncoding(self):
"return encoding if @charset rule if given or default of 'utf-8'"
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``")
namespaces = property(lambda self: self._namespaces,
doc="Namespaces used in this CSSStyleSheet.")
def add(self, rule):
"""
Adds rule to stylesheet at appropriate position.
Same as ``sheet.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.
: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)
Raised if the specified index does not correspond to a rule in
the style sheet's rule list.
- `NAMESPACE_ERR`: (self)
Raised if removing this rule would result in an invalid StyleSheet
- `NO_MODIFICATION_ALLOWED_ERR`: (self)
Raised if this style sheet is readonly.
"""
self._checkReadonly()
try:
rule = self.cssRules[index]
except IndexError:
raise xml.dom.IndexSizeErr(
u'CSSStyleSheet: %s is not a valid index in the rulelist of length %i' % (
index, self.cssRules.length))
else:
if rule.type == rule.NAMESPACE_RULE:
# check all namespacerules if used
uris = [r.namespaceURI for r in self if r.type == r.NAMESPACE_RULE]
useduris = self._getUsedURIs()
if rule.namespaceURI in useduris and\
uris.count(rule.namespaceURI) == 1:
raise xml.dom.NoModificationAllowedErr(
u'CSSStyleSheet: NamespaceURI defined in this rule is used, cannot remove.')
return
rule._parentStyleSheet = None # detach
del self.cssRules[index] # delete from StyleSheet
def insertRule(self, rule, index=None, inOrder=False, _clean=True):
"""
Used to insert a new rule into the style sheet. The new rule now
becomes part of the cascade.
: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
:Exceptions:
- `HIERARCHY_REQUEST_ERR`: (self)
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 style sheet is readonly.
- `SYNTAX_ERR`: (rule)
Raised if the specified rule has a syntax error and is
unparsable.
"""
self._checkReadonly()
# check position
if index is None:
index = len(self.cssRules)
elif index < 0 or index > self.cssRules.length:
raise xml.dom.IndexSizeErr(
u'CSSStyleSheet: Invalid index %s for CSSRuleList with a length of %s.' % (
index, self.cssRules.length))
return
if isinstance(rule, basestring):
# init a temp sheet which has the same properties as self
tempsheet = CSSStyleSheet(href=self.href,
media=self.media,
title=self.title,
parentStyleSheet=self.parentStyleSheet,
ownerRule=self.ownerRule)
tempsheet._ownerNode = self.ownerNode
tempsheet._fetcher = self._fetcher
# prepend encoding if in this sheet to be able to use it in
# @import rules encoding resolution
# do not add if new rule startswith "@charset" (which is exact!)
if not rule.startswith(u'@charset') and (self.cssRules and
self.cssRules[0].type == self.cssRules[0].CHARSET_RULE):
# rule 0 is @charset!
newrulescount, newruleindex = 2, 1
rule = self.cssRules[0].cssText + rule
else:
newrulescount, newruleindex = 1, 0
# parse the new rule(s)
tempsheet.cssText = (rule, self._namespaces)
if len(tempsheet.cssRules) != newrulescount or (not isinstance(
tempsheet.cssRules[newruleindex], cssutils.css.CSSRule)):
self._log.error(u'CSSStyleSheet: Not a CSSRule: %s' % rule)
return
rule = tempsheet.cssRules[newruleindex]
rule._parentStyleSheet = None # done later?
# TODO:
#tempsheet._namespaces = self._namespaces
elif isinstance(rule, cssutils.css.CSSRuleList):
# insert all rules
for i, r in enumerate(rule):
self.insertRule(r, index + i)
return index
if not rule.wellformed:
self._log.error(u'CSSStyleSheet: Invalid rules cannot be added.')
return
# CHECK HIERARCHY
# @charset
if rule.type == rule.CHARSET_RULE:
if inOrder:
index = 0
# always first and only
if (self.cssRules and self.cssRules[0].type == rule.CHARSET_RULE):
self.cssRules[0].encoding = rule.encoding
else:
self.cssRules.insert(0, rule)
elif index != 0 or (self.cssRules and
self.cssRules[0].type == rule.CHARSET_RULE):
self._log.error(
u'CSSStylesheet: @charset only allowed once at the beginning of a stylesheet.',
error=xml.dom.HierarchyRequestErr)
return
else:
self.cssRules.insert(index, rule)
# @unknown or comment
elif rule.type in (rule.UNKNOWN_RULE, rule.COMMENT) and not inOrder:
if index == 0 and self.cssRules and\
self.cssRules[0].type == rule.CHARSET_RULE:
self._log.error(
u'CSSStylesheet: @charset must be the first rule.',
error=xml.dom.HierarchyRequestErr)
return
else:
self.cssRules.insert(index, rule)
# @import
elif rule.type == rule.IMPORT_RULE:
if inOrder:
# automatic order
if rule.type in (r.type for r in self):
# find last of this type
for i, r in enumerate(reversed(self.cssRules)):
if r.type == rule.type:
index = len(self.cssRules) - i
break
else:
# find first point to insert
if self.cssRules and self.cssRules[0].type in (rule.CHARSET_RULE,
rule.COMMENT):
index = 1
else:
index = 0
else:
# after @charset
if index == 0 and self.cssRules and\
self.cssRules[0].type == rule.CHARSET_RULE:
self._log.error(
u'CSSStylesheet: Found @charset at index 0.',
error=xml.dom.HierarchyRequestErr)
return
# before @namespace, @page, @font-face, @media and stylerule
for r in self.cssRules[:index]:
if r.type in (r.NAMESPACE_RULE, r.MEDIA_RULE, r.PAGE_RULE,
r.STYLE_RULE, r.FONT_FACE_RULE):
self._log.error(
u'CSSStylesheet: Cannot insert @import here, found @namespace, @media, @page or CSSStyleRule before index %s.' %
index,
error=xml.dom.HierarchyRequestErr)
return
self.cssRules.insert(index, rule)
# @namespace
elif rule.type == rule.NAMESPACE_RULE:
if inOrder:
if rule.type in (r.type for r in self):
# find last of this type
for i, r in enumerate(reversed(self.cssRules)):
if r.type == rule.type:
index = len(self.cssRules) - i
break
else:
# find first point to insert
for i, r in enumerate(self.cssRules):
if r.type in (r.MEDIA_RULE, r.PAGE_RULE, r.STYLE_RULE,
r.FONT_FACE_RULE, r.UNKNOWN_RULE, r.COMMENT):
index = i # before these
break
else:
# after @charset and @import
for r in self.cssRules[index:]:
if r.type in (r.CHARSET_RULE, r.IMPORT_RULE):
self._log.error(
u'CSSStylesheet: Cannot insert @namespace here, found @charset or @import after index %s.' %
index,
error=xml.dom.HierarchyRequestErr)
return
# before @media and stylerule
for r in self.cssRules[:index]:
if r.type in (r.MEDIA_RULE, r.PAGE_RULE, r.STYLE_RULE,
r.FONT_FACE_RULE):
self._log.error(
u'CSSStylesheet: Cannot insert @namespace here, found @media, @page or CSSStyleRule before index %s.' %
index,
error=xml.dom.HierarchyRequestErr)
return
if not (rule.prefix in self.namespaces and
self.namespaces[rule.prefix] == rule.namespaceURI):
# no doublettes
self.cssRules.insert(index, rule)
if _clean:
self._cleanNamespaces()
# all other where order is not important
else:
if inOrder:
# simply add to end as no specific order
self.cssRules.append(rule)
index = len(self.cssRules) - 1
else:
for r in self.cssRules[index:]:
if r.type in (r.CHARSET_RULE, r.IMPORT_RULE, r.NAMESPACE_RULE):
self._log.error(
u'CSSStylesheet: Cannot insert rule here, found @charset, @import or @namespace before index %s.' %
index,
error=xml.dom.HierarchyRequestErr)
return
self.cssRules.insert(index, rule)
# post settings, TODO: for other rules which contain @rules
rule._parentStyleSheet = self
if rule.MEDIA_RULE == rule.type:
for r in rule:
r._parentStyleSheet = self
# ?
elif rule.IMPORT_RULE == rule.type:
rule.href = rule.href # try to reload 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)
def setSerializer(self, cssserializer):
"""
Sets the global Serializer used for output of all stylesheet
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
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 "<cssutils.css.%s object encoding=%r href=%r "\
"media=%r title=%r namespaces=%r at 0x%x>" % (
self.__class__.__name__, self.encoding, self.href,
mediaText, self.title, self.namespaces.namespaces,
id(self))
+208
View File
@@ -0,0 +1,208 @@
"""CSSUnknownRule implements DOM Level 2 CSS CSSUnknownRule.
"""
__all__ = ['CSSUnknownRule']
__docformat__ = 'restructuredtext'
__version__ = '$Id: cssunknownrule.py 1170 2008-03-20 17:42:07Z cthedot $'
import xml.dom
import cssrule
import cssutils
class CSSUnknownRule(cssrule.CSSRule):
"""
represents an at-rule not supported by this user agent.
Properties
==========
inherited from CSSRule
- cssText
- type
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
of type string
"""
super(CSSUnknownRule, self).__init__(parentRule=parentRule,
parentStyleSheet=parentStyleSheet)
self._atkeyword = None
if cssText:
self.cssText = cssText
self._readonly = readonly
def _getCssText(self):
""" returns 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.
"""
super(CSSUnknownRule, self)._setCssText(cssText)
tokenizer = self._tokenize2(cssText)
attoken = self._nexttoken(tokenizer, None)
if not attoken or self._type(attoken) != self._prods.ATKEYWORD:
self._log.error(u'CSSUnknownRule: No CSSUnknownRule found: %s' %
self._valuestr(cssText),
error=xml.dom.InvalidModificationErr)
else:
# for closures: must be a mutable
new = {'nesting': [], # {} [] or ()
'wellformed': True
}
def CHAR(expected, seq, token, tokenizer=None):
type_, val, line, col = token
if expected != 'EOF':
if val in u'{[(':
new['nesting'].append(val)
elif val in u'}])':
opening = {u'}': u'{', u']': u'[', u')': u'('}[val]
try:
if new['nesting'][-1] == opening:
new['nesting'].pop()
else:
raise IndexError()
except IndexError:
new['wellformed'] = False
self._log.error(u'CSSUnknownRule: Wrong nesting of {, [ or (.',
token=token)
if val in u'};' and not new['nesting']:
expected = 'EOF'
seq.append(val, type_, line=line, col=col)
return expected
else:
new['wellformed'] = False
self._log.error(u'CSSUnknownRule: Expected end of rule.',
token=token)
return expected
def EOF(expected, seq, token, tokenizer=None):
"close all blocks and return 'EOF'"
for x in reversed(new['nesting']):
closing = {u'{': u'}', u'[': u']', u'(': u')'}[x]
seq.append(closing, closing)
new['nesting'] = []
return 'EOF'
def INVALID(expected, seq, token, tokenizer=None):
# makes rule invalid
self._log.error(u'CSSUnknownRule: Bad syntax.',
token=token, error=xml.dom.SyntaxErr)
new['wellformed'] = False
return expected
def STRING(expected, seq, token, tokenizer=None):
type_, val, line, col = token
val = self._stringtokenvalue(token)
if expected != 'EOF':
seq.append(val, type_, line=line, col=col)
return expected
else:
new['wellformed'] = False
self._log.error(u'CSSUnknownRule: Expected end of rule.',
token=token)
return expected
def URI(expected, seq, token, tokenizer=None):
type_, val, line, col = token
val = self._uritokenvalue(token)
if expected != 'EOF':
seq.append(val, type_, line=line, col=col)
return expected
else:
new['wellformed'] = False
self._log.error(u'CSSUnknownRule: Expected end of rule.',
token=token)
return expected
def default(expected, seq, token, tokenizer=None):
type_, val, line, col = token
if expected != 'EOF':
seq.append(val, type_, line=line, col=col)
return expected
else:
new['wellformed'] = False
self._log.error(u'CSSUnknownRule: Expected end of rule.',
token=token)
return expected
# unknown : ATKEYWORD S* ... ; | }
newseq = self._tempSeq()
wellformed, expected = self._parse(expected=None,
seq=newseq, tokenizer=tokenizer,
productions={'CHAR': CHAR,
'EOF': EOF,
'INVALID': INVALID,
'STRING': STRING,
'URI': URI,
'S': default # overwrite default default!
},
default=default,
new=new)
# wellformed set by parse
wellformed = wellformed and new['wellformed']
# post conditions
if expected != 'EOF':
wellformed = False
self._log.error(
u'CSSUnknownRule: No ending ";" or "}" found: %r' %
self._valuestr(cssText))
elif new['nesting']:
wellformed = False
self._log.error(
u'CSSUnknownRule: Unclosed "{", "[" or "(": %r' %
self._valuestr(cssText))
# set all
if wellformed:
self.atkeyword = self._tokenvalue(attoken)
self._setSeq(newseq)
cssText = property(fget=_getCssText, fset=_setCssText,
doc="(DOM) The parsable textual representation.")
wellformed = property(lambda self: bool(self.atkeyword))
def __repr__(self):
return "cssutils.css.%s(cssText=%r)" % (
self.__class__.__name__, self.cssText)
def __str__(self):
return "<cssutils.css.%s object cssText=%r at 0x%x>" % (
self.__class__.__name__, self.cssText, id(self))
File diff suppressed because it is too large Load Diff
+415
View File
@@ -0,0 +1,415 @@
"""Property is a single CSS property in a CSSStyleDeclaration
Internal use only, may be removed in the future!
"""
__all__ = ['Property']
__docformat__ = 'restructuredtext'
__version__ = '$Id: property.py 1444 2008-08-31 18:45:35Z cthedot $'
import xml.dom
import cssutils
#import cssproperties
from cssutils.profiles import profiles
from cssvalue import CSSValue
from cssutils.helper import Deprecated
class Property(cssutils.util.Base):
"""
(cssutils) a CSS property in a StyleDeclaration of a CSSStyleRule
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
======
::
property = name
: IDENT S*
;
expr = value
: term [ operator term ]*
;
term
: unary_operator?
[ NUMBER S* | PERCENTAGE S* | LENGTH S* | EMS S* | EXS S* | ANGLE S* |
TIME S* | FREQ S* | function ]
| STRING S* | IDENT S* | URI S* | hexcolor
;
function
: FUNCTION S* expr ')' S*
;
/*
* There is a constraint on the color that it must
* have either 3 or 6 hex-digits (i.e., [0-9a-fA-F])
* after the "#"; e.g., "#000" is OK, but "#abcd" is not.
*/
hexcolor
: HASH S*
;
prio
: IMPORTANT_SYM S*
;
"""
def __init__(self, name=None, value=None, priority=u'', _mediaQuery=False):
"""
inits property
name
a property name string (will be normalized)
value
a property value string
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
"""
super(Property, self).__init__()
self.seqs = [[], None, []]
self.valid = False
self.wellformed = False
self._mediaQuery = _mediaQuery
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()
if priority:
self.priority = priority
else:
self._priority = u''
self._literalpriority = u''
def _getCssText(self):
"""
returns 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.
"""
# check and prepare tokenlists for setting
tokenizer = self._tokenize2(cssText)
nametokens = self._tokensupto2(tokenizer, propertynameendonly=True)
if nametokens:
wellformed = True
valuetokens = self._tokensupto2(tokenizer,
propertyvalueendonly=True)
prioritytokens = self._tokensupto2(tokenizer,
propertypriorityendonly=True)
if self._mediaQuery and not valuetokens:
# MediaQuery may consist of name only
self.name = nametokens
self.cssValue = None
self.priority = None
return
# remove colon from nametokens
colontoken = nametokens.pop()
if self._tokenvalue(colontoken) != u':':
wellformed = False
self._log.error(u'Property: No ":" after name found: %r' %
self._valuestr(cssText), colontoken)
elif not nametokens:
wellformed = False
self._log.error(u'Property: No property name found: %r.' %
self._valuestr(cssText), colontoken)
if valuetokens:
if self._tokenvalue(valuetokens[-1]) == u'!':
# priority given, move "!" to prioritytokens
prioritytokens.insert(0, valuetokens.pop(-1))
else:
wellformed = False
self._log.error(u'Property: No property value found: %r.' %
self._valuestr(cssText), colontoken)
if wellformed:
self.wellformed = True
self.name = nametokens
self.cssValue = valuetokens
self.priority = prioritytokens
else:
self._log.error(u'Property: No property name found: %r.' %
self._valuestr(cssText))
cssText = property(fget=_getCssText, fset=_setCssText,
doc="A parsable textual representation.")
def _setName(self, name):
"""
DOMException on setting
- SYNTAX_ERR: (self)
Raised if the specified name has a syntax error and is
unparsable.
"""
# for closures: must be a mutable
new = {'literalname': None,
'wellformed': True}
def _ident(expected, seq, token, tokenizer=None):
# name
if 'name' == expected:
new['literalname'] = self._tokenvalue(token).lower()
seq.append(new['literalname'])
return 'EOF'
else:
new['wellformed'] = False
self._log.error(u'Property: Unexpected ident.', token)
return expected
newseq = []
wellformed, expected = self._parse(expected='name',
seq=newseq,
tokenizer=self._tokenize2(name),
productions={'IDENT': _ident})
wellformed = wellformed and new['wellformed']
# post conditions
# define a token for error logging
if isinstance(name, list):
token = name[0]
else:
token = None
if not new['literalname']:
wellformed = False
self._log.error(u'Property: No name found: %r' %
self._valuestr(name), token=token)
if wellformed:
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)
else:
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")
literalname = property(lambda self: self._literalname,
doc="Readonly literal (not normalized) name of this property")
def _getCSSValue(self):
return self.seqs[1]
def _setCSSValue(self, cssText):
"""
see css.CSSValue
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.
"""
if self._mediaQuery and not cssText:
self.seqs[1] = CSSValue()
else:
if not self.seqs[1]:
self.seqs[1] = CSSValue()
cssvalue = self.seqs[1]
cssvalue._propertyName = self.name
cssvalue.cssText = cssText
if cssvalue._value and cssvalue.wellformed:
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
else:
return u''
def _setValue(self, value):
self.cssValue.cssText = value
self.valid = self.valid and self.cssValue.valid
self.wellformed = self.wellformed and self.cssValue.wellformed
value = property(_getValue, _setValue,
doc="The textual value of this Properties cssValue.")
def _setPriority(self, priority):
"""
priority
a string, currently either u'', u'!important' or u'important'
Format
======
::
prio
: IMPORTANT_SYM S*
;
"!"{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
"""
if self._mediaQuery:
self._priority = u''
self._literalpriority = u''
if priority:
self._log.error(u'Property: No priority in a MediaQuery - ignored.')
return
if isinstance(priority, basestring) and\
u'important' == self._normalize(priority):
priority = u'!%s' % priority
# for closures: must be a mutable
new = {'literalpriority': u'',
'wellformed': True}
def _char(expected, seq, token, tokenizer=None):
# "!"
val = self._tokenvalue(token)
if u'!' == expected == val:
seq.append(val)
return 'important'
else:
new['wellformed'] = False
self._log.error(u'Property: Unexpected char.', token)
return expected
def _ident(expected, seq, token, tokenizer=None):
# "important"
val = self._tokenvalue(token)
normalval = self._tokenvalue(token, normalize=True)
if 'important' == expected == normalval:
new['literalpriority'] = val
seq.append(val)
return 'EOF'
else:
new['wellformed'] = False
self._log.error(u'Property: Unexpected ident.', token)
return expected
newseq = []
wellformed, expected = self._parse(expected='!',
seq=newseq,
tokenizer=self._tokenize2(priority),
productions={'CHAR': _char,
'IDENT': _ident})
wellformed = wellformed and new['wellformed']
# post conditions
if priority and not new['literalpriority']:
wellformed = False
self._log.info(u'Property: Invalid priority: %r.' %
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
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)
priority = property(lambda self: self._priority, _setPriority,
doc="(cssutils) 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 __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))
@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")
+800
View File
@@ -0,0 +1,800 @@
"""Selector is a single Selector of a CSSStyleRule SelectorList.
Partly implements
http://www.w3.org/TR/css3-selectors/
TODO
- .contains(selector)
- .isSubselector(selector)
"""
__all__ = ['Selector']
__docformat__ = 'restructuredtext'
__version__ = '$Id: selector.py 1429 2008-08-11 19:01:52Z cthedot $'
import xml.dom
import cssutils
from cssutils.util import _SimpleNamespaces
class Selector(cssutils.util.Base2):
"""
(cssutils) a single selector in a SelectorList of a 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
======
::
# implemented in SelectorList
selectors_group
: selector [ COMMA S* selector ]*
;
selector
: simple_selector_sequence [ combinator simple_selector_sequence ]*
;
combinator
/* combinators can be surrounded by white space */
: PLUS S* | GREATER S* | TILDE S* | S+
;
simple_selector_sequence
: [ type_selector | universal ]
[ HASH | class | attrib | pseudo | negation ]*
| [ HASH | class | attrib | pseudo | negation ]+
;
type_selector
: [ namespace_prefix ]? element_name
;
namespace_prefix
: [ IDENT | '*' ]? '|'
;
element_name
: IDENT
;
universal
: [ namespace_prefix ]? '*'
;
class
: '.' IDENT
;
attrib
: '[' S* [ namespace_prefix ]? IDENT S*
[ [ PREFIXMATCH |
SUFFIXMATCH |
SUBSTRINGMATCH |
'=' |
INCLUDES |
DASHMATCH ] S* [ IDENT | STRING ] S*
]? ']'
;
pseudo
/* '::' starts a pseudo-element, ':' a pseudo-class */
/* Exceptions: :first-line, :first-letter, :before and :after. */
/* Note that pseudo-elements are restricted to one per selector and */
/* occur only in the last simple_selector_sequence. */
: ':' ':'? [ IDENT | functional_pseudo ]
;
functional_pseudo
: FUNCTION S* expression ')'
;
expression
/* In CSS3, the expressions are identifiers, strings, */
/* or of the form "an+b" */
: [ [ PLUS | '-' | DIMENSION | NUMBER | STRING | IDENT ] S* ]+
;
negation
: NOT S* negation_arg S* ')'
;
negation_arg
: type_selector | universal | HASH | class | attrib | pseudo
;
"""
def __init__(self, selectorText=None, parentList=None,
readonly=False):
"""
:Parameters:
selectorText
initial value of this selector
parentList
a SelectorList
readonly
default to False
"""
super(Selector, self).__init__()
self.__namespaces = _SimpleNamespaces(log=self._log)
self._element = None
self._parent = parentList
self._specificity = (0, 0, 0, 0)
if selectorText:
self.selectorText = selectorText
self._readonly = readonly
def __getNamespaces(self):
"uses 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
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.""")
element = property(lambda self: self._element,
doc=u"Effective element target of this selector.")
parentList = property(lambda self: self._parent,
doc="(DOM) The SelectorList that contains this Selector or\
None if this Selector is not attached to a SelectorList.")
def _getSelectorText(self):
"""
returns serialized format
"""
return cssutils.ser.do_css_Selector(self)
def _setSelectorText(self, selectorText):
"""
:param selectorText:
parsable string or a tuple of (selectorText, dict-of-namespaces).
Given namespaces are ignored if this object is attached to a
CSSStyleSheet!
: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)
selectorText, namespaces = self._splitNamespacesOff(selectorText)
try:
# uses parent stylesheets namespaces if available, otherwise given ones
namespaces = self.parentList.parentRule.parentStyleSheet.namespaces
except AttributeError:
pass
tokenizer = self._tokenize2(selectorText)
if not tokenizer:
self._log.error(u'Selector: No selectorText given.')
else:
# prepare tokenlist:
# "*" -> type "universal"
# "*"|IDENT + "|" -> combined to "namespace_prefix"
# "|" -> type "namespace_prefix"
# "." + IDENT -> combined to "class"
# ":" + IDENT, ":" + FUNCTION -> pseudo-class
# FUNCTION "not(" -> negation
# "::" + IDENT, "::" + FUNCTION -> pseudo-element
tokens = []
for t in tokenizer:
typ, val, lin, col = t
if val == u':' and tokens and\
self._tokenvalue(tokens[-1]) == ':':
# combine ":" and ":"
tokens[-1] = (typ, u'::', lin, col)
elif typ == 'IDENT' and tokens\
and self._tokenvalue(tokens[-1]) == u'.':
# class: combine to .IDENT
tokens[-1] = ('class', u'.'+val, lin, col)
elif typ == 'IDENT' and tokens and \
self._tokenvalue(tokens[-1]).startswith(u':') and\
not self._tokenvalue(tokens[-1]).endswith(u'('):
# pseudo-X: combine to :IDENT or ::IDENT but not ":a(" + "b"
if self._tokenvalue(tokens[-1]).startswith(u'::'):
t = 'pseudo-element'
else:
t = 'pseudo-class'
tokens[-1] = (t, self._tokenvalue(tokens[-1])+val, lin, col)
elif typ == 'FUNCTION' and val == u'not(' and tokens and \
u':' == self._tokenvalue(tokens[-1]):
tokens[-1] = ('negation', u':' + val, lin, tokens[-1][3])
elif typ == 'FUNCTION' and tokens\
and self._tokenvalue(tokens[-1]).startswith(u':'):
# pseudo-X: combine to :FUNCTION( or ::FUNCTION(
if self._tokenvalue(tokens[-1]).startswith(u'::'):
t = 'pseudo-element'
else:
t = 'pseudo-class'
tokens[-1] = (t, self._tokenvalue(tokens[-1])+val, lin, col)
elif val == u'*' and tokens and\
self._type(tokens[-1]) == 'namespace_prefix' and\
self._tokenvalue(tokens[-1]).endswith(u'|'):
# combine prefix|*
tokens[-1] = ('universal', self._tokenvalue(tokens[-1])+val,
lin, col)
elif val == u'*':
# universal: "*"
tokens.append(('universal', val, lin, col))
elif val == u'|' and tokens and\
self._type(tokens[-1]) in (self._prods.IDENT, 'universal') and\
self._tokenvalue(tokens[-1]).find(u'|') == -1:
# namespace_prefix: "IDENT|" or "*|"
tokens[-1] = ('namespace_prefix',
self._tokenvalue(tokens[-1])+u'|', lin, col)
elif val == u'|':
# namespace_prefix: "|"
tokens.append(('namespace_prefix', val, lin, col))
else:
tokens.append(t)
# TODO: back to generator but not elegant at all!
tokenizer = (t for t in tokens)
# for closures: must be a mutable
new = {'context': [''], # stack of: 'attrib', 'negation', 'pseudo'
'element': None,
'_PREFIX': None,
'specificity': [0, 0, 0, 0], # mutable, finally a tuple!
'wellformed': True
}
# used for equality checks and setting of a space combinator
S = u' '
def append(seq, val, typ=None, token=None):
"""
appends to seq
namespace_prefix, IDENT will be combined to a tuple
(prefix, name) where prefix might be None, the empty string
or a prefix.
Saved are also:
- specificity definition: style, id, class/att, type
- element: the element this Selector is for
"""
context = new['context'][-1]
if token:
line, col = token[2], token[3]
else:
line, col = None, None
if typ == '_PREFIX':
# SPECIAL TYPE: save prefix for combination with next
new['_PREFIX'] = val[:-1]
# handle next time
return
if new['_PREFIX'] is not None:
# as saved from before and reset to None
prefix, new['_PREFIX'] = new['_PREFIX'], None
elif typ == 'universal' and '|' in val:
# val == *|* or prefix|*
prefix, val = val.split('|')
else:
prefix = None
# namespace
if (typ.endswith('-selector') or typ == 'universal') and not (
'attribute-selector' == typ and not prefix):
# att **IS NOT** in default ns
if prefix == u'*':
# *|name: in ANY_NS
namespaceURI = cssutils._ANYNS
elif prefix is None:
# e or *: default namespace with prefix u'' or local-name()
namespaceURI = namespaces.get(u'', None)
elif prefix == u'':
# |name or |*: in no (or the empty) namespace
namespaceURI = u''
else:
# explicit namespace prefix
# does not raise KeyError, see _SimpleNamespaces
namespaceURI = namespaces[prefix]
if namespaceURI is None:
new['wellformed'] = False
self._log.error(
u'Selector: No namespaceURI found for prefix %r' %
prefix, token=token, error=xml.dom.NamespaceErr)
return
# val is now (namespaceprefix, name) tuple
val = (namespaceURI, val)
# specificity
if not context or context == 'negation':
if 'id' == typ:
new['specificity'][1] += 1
elif 'class' == typ or '[' == val:
new['specificity'][2] += 1
elif typ in ('type-selector', 'negation-type-selector',
'pseudo-element'):
new['specificity'][3] += 1
if not context and typ in ('type-selector', 'universal'):
# define element
new['element'] = val
seq.append(val, typ, line=line, col=col)
# expected constants
simple_selector_sequence = 'type_selector universal HASH class attrib pseudo negation '
simple_selector_sequence2 = 'HASH class attrib pseudo negation '
element_name = 'element_name'
negation_arg = 'type_selector universal HASH class attrib pseudo'
negationend = ')'
attname = 'prefix attribute'
attname2 = 'attribute'
attcombinator = 'combinator ]' # optional
attvalue = 'value' # optional
attend = ']'
expressionstart = 'PLUS - DIMENSION NUMBER STRING IDENT'
expression = expressionstart + ' )'
combinator = ' combinator'
def _COMMENT(expected, seq, token, tokenizer=None):
"special implementation for comment token"
append(seq, cssutils.css.CSSComment([token]), 'COMMENT',
token=token)
return expected
def _S(expected, seq, token, tokenizer=None):
# S
context = new['context'][-1]
if context.startswith('pseudo-'):
if seq and seq[-1].value not in u'+-':
# e.g. x:func(a + b)
append(seq, S, 'S', token=token)
return expected
elif context != 'attrib' and 'combinator' in expected:
append(seq, S, 'descendant', token=token)
return simple_selector_sequence + combinator
else:
return expected
def _universal(expected, seq, token, tokenizer=None):
# *|* or prefix|*
context = new['context'][-1]
val = self._tokenvalue(token)
if 'universal' in expected:
append(seq, val, 'universal', token=token)
if 'negation' == context:
return negationend
else:
return simple_selector_sequence2 + combinator
else:
new['wellformed'] = False
self._log.error(
u'Selector: Unexpected universal.', token=token)
return expected
def _namespace_prefix(expected, seq, token, tokenizer=None):
# prefix| => element_name
# or prefix| => attribute_name if attrib
context = new['context'][-1]
val = self._tokenvalue(token)
if 'attrib' == context and 'prefix' in expected:
# [PREFIX|att]
append(seq, val, '_PREFIX', token=token)
return attname2
elif 'type_selector' in expected:
# PREFIX|*
append(seq, val, '_PREFIX', token=token)
return element_name
else:
new['wellformed'] = False
self._log.error(
u'Selector: Unexpected namespace prefix.', token=token)
return expected
def _pseudo(expected, seq, token, tokenizer=None):
# pseudo-class or pseudo-element :a ::a :a( ::a(
"""
/* '::' starts a pseudo-element, ':' a pseudo-class */
/* Exceptions: :first-line, :first-letter, :before and :after. */
/* Note that pseudo-elements are restricted to one per selector and */
/* occur only in the last simple_selector_sequence. */
"""
context = new['context'][-1]
val, typ = self._tokenvalue(token, normalize=True), self._type(token)
if 'pseudo' in expected:
if val in (':first-line', ':first-letter', ':before', ':after'):
# always pseudo-element ???
typ = 'pseudo-element'
append(seq, val, typ, token=token)
if val.endswith(u'('):
# function
new['context'].append(typ) # "pseudo-" "class" or "element"
return expressionstart
elif 'negation' == context:
return negationend
elif 'pseudo-element' == typ:
# only one per element, check at ) also!
return combinator
else:
return simple_selector_sequence2 + combinator
else:
new['wellformed'] = False
self._log.error(
u'Selector: Unexpected start of pseudo.', token=token)
return expected
def _expression(expected, seq, token, tokenizer=None):
# [ [ PLUS | '-' | DIMENSION | NUMBER | STRING | IDENT ] S* ]+
context = new['context'][-1]
val, typ = self._tokenvalue(token), self._type(token)
if context.startswith('pseudo-'):
append(seq, val, typ, token=token)
return expression
else:
new['wellformed'] = False
self._log.error(
u'Selector: Unexpected %s.' % typ, token=token)
return expected
def _attcombinator(expected, seq, token, tokenizer=None):
# context: attrib
# PREFIXMATCH | SUFFIXMATCH | SUBSTRINGMATCH | INCLUDES |
# DASHMATCH
context = new['context'][-1]
val, typ = self._tokenvalue(token), self._type(token)
if 'attrib' == context and 'combinator' in expected:
# combinator in attrib
append(seq, val, typ.lower(), token=token)
return attvalue
else:
new['wellformed'] = False
self._log.error(
u'Selector: Unexpected %s.' % typ, token=token)
return expected
def _string(expected, seq, token, tokenizer=None):
# identifier
context = new['context'][-1]
typ, val = self._type(token), self._stringtokenvalue(token)
# context: attrib
if 'attrib' == context and 'value' in expected:
# attrib: [...=VALUE]
append(seq, val, typ, token=token)
return attend
# context: pseudo
elif context.startswith('pseudo-'):
# :func(...)
append(seq, val, typ, token=token)
return expression
else:
new['wellformed'] = False
self._log.error(
u'Selector: Unexpected STRING.', token=token)
return expected
def _ident(expected, seq, token, tokenizer=None):
# identifier
context = new['context'][-1]
val, typ = self._tokenvalue(token), self._type(token)
# context: attrib
if 'attrib' == context and 'attribute' in expected:
# attrib: [...|ATT...]
append(seq, val, 'attribute-selector', token=token)
return attcombinator
elif 'attrib' == context and 'value' in expected:
# attrib: [...=VALUE]
append(seq, val, 'attribute-value', token=token)
return attend
# context: negation
elif 'negation' == context:
# negation: (prefix|IDENT)
append(seq, val, 'negation-type-selector', token=token)
return negationend
# context: pseudo
elif context.startswith('pseudo-'):
# :func(...)
append(seq, val, typ, token=token)
return expression
elif 'type_selector' in expected or element_name == expected:
# element name after ns or complete type_selector
append(seq, val, 'type-selector', token=token)
return simple_selector_sequence2 + combinator
else:
new['wellformed'] = False
self._log.error(
u'Selector: Unexpected IDENT.',
token=token)
return expected
def _class(expected, seq, token, tokenizer=None):
# .IDENT
context = new['context'][-1]
val = self._tokenvalue(token)
if 'class' in expected:
append(seq, val, 'class', token=token)
if 'negation' == context:
return negationend
else:
return simple_selector_sequence2 + combinator
else:
new['wellformed'] = False
self._log.error(
u'Selector: Unexpected class.', token=token)
return expected
def _hash(expected, seq, token, tokenizer=None):
# #IDENT
context = new['context'][-1]
val = self._tokenvalue(token)
if 'HASH' in expected:
append(seq, val, 'id', token=token)
if 'negation' == context:
return negationend
else:
return simple_selector_sequence2 + combinator
else:
new['wellformed'] = False
self._log.error(
u'Selector: Unexpected HASH.', token=token)
return expected
def _char(expected, seq, token, tokenizer=None):
# + > ~ ) [ ] + -
context = new['context'][-1]
val = self._tokenvalue(token)
# context: attrib
if u']' == val and 'attrib' == context and ']' in expected:
# end of attrib
append(seq, val, 'attribute-end', token=token)
context = new['context'].pop() # attrib is done
context = new['context'][-1]
if 'negation' == context:
return negationend
else:
return simple_selector_sequence2 + combinator
elif u'=' == val and 'attrib' == context and 'combinator' in expected:
# combinator in attrib
append(seq, val, 'equals', token=token)
return attvalue
# context: negation
elif u')' == val and 'negation' == context and u')' in expected:
# not(negation_arg)"
append(seq, val, 'negation-end', token=token)
new['context'].pop() # negation is done
context = new['context'][-1]
return simple_selector_sequence + combinator
# context: pseudo (at least one expression)
elif val in u'+-' and context.startswith('pseudo-'):
# :func(+ -)"
_names = {'+': 'plus', '-': 'minus'}
if val == u'+' and seq and seq[-1].value == S:
seq.replace(-1, val, _names[val])
else:
append(seq, val, _names[val],
token=token)
return expression
elif u')' == val and context.startswith('pseudo-') and\
expression == expected:
# :func(expression)"
append(seq, val, 'function-end', token=token)
new['context'].pop() # pseudo is done
if 'pseudo-element' == context:
return combinator
else:
return simple_selector_sequence + combinator
# context: ROOT
elif u'[' == val and 'attrib' in expected:
# start of [attrib]
append(seq, val, 'attribute-start', token=token)
new['context'].append('attrib')
return attname
elif val in u'+>~' and 'combinator' in expected:
# no other combinator except S may be following
_names = {
'>': 'child',
'+': 'adjacent-sibling',
'~': 'following-sibling'}
if seq and seq[-1].value == S:
seq.replace(-1, val, _names[val])
else:
append(seq, val, _names[val], token=token)
return simple_selector_sequence
elif u',' == val:
# not a selectorlist
new['wellformed'] = False
self._log.error(
u'Selector: Single selector only.',
error=xml.dom.InvalidModificationErr,
token=token)
return expected
else:
new['wellformed'] = False
self._log.error(
u'Selector: Unexpected CHAR.', token=token)
return expected
def _negation(expected, seq, token, tokenizer=None):
# not(
context = new['context'][-1]
val = self._tokenvalue(token, normalize=True)
if 'negation' in expected:
new['context'].append('negation')
append(seq, val, 'negation-start', token=token)
return negation_arg
else:
new['wellformed'] = False
self._log.error(
u'Selector: Unexpected negation.', token=token)
return expected
# expected: only|not or mediatype, mediatype, feature, and
newseq = self._tempSeq()
wellformed, expected = self._parse(expected=simple_selector_sequence,
seq=newseq, tokenizer=tokenizer,
productions={'CHAR': _char,
'class': _class,
'HASH': _hash,
'STRING': _string,
'IDENT': _ident,
'namespace_prefix': _namespace_prefix,
'negation': _negation,
'pseudo-class': _pseudo,
'pseudo-element': _pseudo,
'universal': _universal,
# pseudo
'NUMBER': _expression,
'DIMENSION': _expression,
# attribute
'PREFIXMATCH': _attcombinator,
'SUFFIXMATCH': _attcombinator,
'SUBSTRINGMATCH': _attcombinator,
'DASHMATCH': _attcombinator,
'INCLUDES': _attcombinator,
'S': _S,
'COMMENT': _COMMENT})
wellformed = wellformed and new['wellformed']
# post condition
if len(new['context']) > 1 or not newseq:
wellformed = False
self._log.error(u'Selector: Invalid or incomplete selector: %s' %
self._valuestr(selectorText))
if expected == 'element_name':
wellformed = False
self._log.error(u'Selector: No element name found: %s' %
self._valuestr(selectorText))
if expected == simple_selector_sequence and newseq:
wellformed = False
self._log.error(u'Selector: Cannot end with combinator: %s' %
self._valuestr(selectorText))
if newseq and hasattr(newseq[-1].value, 'strip') and \
newseq[-1].value.strip() == u'':
del newseq[-1]
# set
if wellformed:
self.__namespaces = namespaces
self._element = new['element']
self._specificity = tuple(new['specificity'])
self._setSeq(newseq)
# filter that only used ones are kept
self.__namespaces = self._getUsedNamespaces()
selectorText = property(_getSelectorText, _setSelectorText,
doc="(DOM) The parsable textual representation of the selector.")
specificity = property(lambda self: self._specificity,
doc="Specificity of this selector (READONLY).")
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"<cssutils.css.%s object selectorText=%r specificity=%r _namespaces=%r at 0x%x>" % (
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
+249
View File
@@ -0,0 +1,249 @@
"""SelectorList is a list of CSS Selector objects.
TODO
- remove duplicate Selectors. -> CSSOM canonicalize
- ??? CSS2 gives a special meaning to the comma (,) in selectors.
However, since it is not known if the comma may acquire other
meanings in future versions of CSS, the whole statement should be
ignored if there is an error anywhere in the selector, even though
the rest of the selector may look reasonable in CSS2.
Illegal example(s):
For example, since the "&" is not a valid token in a CSS2 selector,
a CSS2 user agent must ignore the whole second line, and not set
the color of H3 to red:
"""
__all__ = ['SelectorList']
__docformat__ = 'restructuredtext'
__version__ = '$Id: selectorlist.py 1174 2008-03-20 17:43:07Z cthedot $'
import xml.dom
import cssutils
from selector import Selector
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
"""
def __init__(self, selectorText=None, parentRule=None,
readonly=False):
"""
initializes SelectorList with optional selectorText
:Parameters:
selectorText
parsable list of Selectors
parentRule
the parent CSSRule if available
"""
super(SelectorList, self).__init__()
self._parentRule = parentRule
if selectorText:
self.selectorText = selectorText
self._readonly = readonly
def __prepareset(self, newSelector, namespaces=None):
"used by appendSelector and __setitem__"
if not namespaces:
namespaces = {}
self._checkReadonly()
if not isinstance(newSelector, Selector):
newSelector = Selector((newSelector, namespaces),
parentList=self)
if newSelector.wellformed:
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"
try:
return self.parentRule.parentStyleSheet.namespaces
except AttributeError:
namespaces = {}
for selector in self.seq:
namespaces.update(selector._namespaces)
return namespaces
_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 _getSelectorText(self):
"returns serialized format"
return cssutils.ser.do_css_SelectorList(self)
def _setSelectorText(self, selectorText):
"""
:param selectorText:
comma-separated list of selectors or a tuple of
(selectorText, dict-of-namespaces)
:Exceptions:
- `NAMESPACE_ERR`: (Selector)
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)
selectorText, namespaces = self._splitNamespacesOff(selectorText)
try:
# use parent's only if available
namespaces = self.parentRule.parentStyleSheet.namespaces
except AttributeError:
pass
wellformed = True
tokenizer = self._tokenize2(selectorText)
newseq = []
expected = True
while True:
# find all upto and including next ",", EOF or nothing
selectortokens = self._tokensupto2(tokenizer, listseponly=True)
if selectortokens:
if self._tokenvalue(selectortokens[-1]) == ',':
expected = selectortokens.pop()
else:
expected = None
selector = Selector((selectortokens, namespaces),
parentList=self)
if selector.wellformed:
newseq.append(selector)
else:
wellformed = False
self._log.error(u'SelectorList: Invalid Selector: %s' %
self._valuestr(selectortokens))
else:
break
# post condition
if u',' == expected:
wellformed = False
self._log.error(u'SelectorList: Cannot end with ",": %r' %
self._valuestr(selectorText))
elif expected:
wellformed = False
self._log.error(u'SelectorList: Unknown Syntax: %r' %
self._valuestr(selectorText))
if wellformed:
self.seq = newseq
# for selector in newseq:
# self.appendSelector(selector)
selectorText = property(_getSelectorText, _setSelectorText,
doc="""(cssutils) The textual representation of the selector for
a rule set.""")
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 "<cssutils.css.%s object selectorText=%r _namespaces=%r at 0x%x>" % (
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