Update bundled cssutils to 0.9.7 for speed improvements in CSS parsing

This commit is contained in:
Kovid Goyal 2009-11-06 10:01:38 -07:00
parent 51e95261e2
commit 6f51ac9b73
24 changed files with 1165 additions and 323 deletions

View File

@ -70,11 +70,11 @@ Usage may be::
__all__ = ['css', 'stylesheets', 'CSSParser', 'CSSSerializer']
__docformat__ = 'restructuredtext'
__author__ = 'Christof Hoeke with contributions by Walter Doerwald'
__date__ = '$LastChangedDate:: 2009-08-01 16:10:11 -0600 #$:'
__date__ = '$LastChangedDate:: 2009-10-17 15:12:28 -0600 #$:'
VERSION = '0.9.6b3'
VERSION = '0.9.7a1'
__version__ = '%s $Id: __init__.py 1832 2009-08-01 22:10:11Z cthedot $' % VERSION
__version__ = '%s $Id: __init__.py 1877 2009-10-17 21:12:28Z cthedot $' % VERSION
import codec
import xml.dom
@ -270,6 +270,12 @@ def replaceUrls(sheet, replacer):
def resolveImports(sheet, target=None):
"""Recurcively combine all rules in given `sheet` into a `target` sheet.
@import rules which use media information are tried to be wrapped into
@media rules so keeping the media information. This may not work in
all instances (if e.g. an @import rule itself contains an @import rule
with different media infos or if it is contains rules which may not be
used inside an @media block like @namespace rules.). In these cases
the @import rule is kept as in the original sheet and a WARNING is issued.
:param sheet:
in this given :class:`cssutils.css.CSSStyleSheet` all import rules are
@ -290,8 +296,22 @@ def resolveImports(sheet, target=None):
log.info(u'Processing @import %r' % rule.href, neverraise=True)
if rule.styleSheet:
target.add(css.CSSComment(cssText=u'/* START @import "%s" */' % rule.href))
resolveImports(rule.styleSheet, target)
target.add(css.CSSComment(cssText=u'/* END "%s" */' % rule.href))
if rule.media.mediaText == 'all':
t = target
else:
log.info(u'Replacing @import media with @media: %s' %
rule.media.mediaText, neverraise=True)
t = css.CSSMediaRule(rule.media.mediaText)
try:
resolveImports(rule.styleSheet, t)
except xml.dom.HierarchyRequestErr, e:
log.warn(u'Cannot resolve @import: %s' %
e, neverraise=True)
target.add(rule)
else:
if t != target:
target.add(t)
t.add(css.CSSComment(cssText=u'/* END "%s" */' % rule.href))
else:
log.error(u'Cannot get referenced stylesheet %r' %
rule.href, neverraise=True)

View File

@ -37,12 +37,13 @@ __all__ = [
'CSSPageRule',
'CSSStyleRule',
'CSSUnknownRule',
'CSSVariablesRule'
'Selector', 'SelectorList',
'CSSStyleDeclaration', 'Property',
'CSSValue', 'CSSPrimitiveValue', 'CSSValueList'
]
__docformat__ = 'restructuredtext'
__version__ = '$Id: __init__.py 1610 2009-01-03 21:07:57Z cthedot $'
__version__ = '$Id: __init__.py 1859 2009-10-10 21:50:27Z cthedot $'
from cssstylesheet import *
from cssrulelist import *
@ -55,9 +56,11 @@ from cssmediarule import *
from cssnamespacerule import *
from csspagerule import *
from cssstylerule import *
from cssvariablesrule import *
from cssunknownrule import *
from selector import *
from selectorlist import *
from cssstyledeclaration import *
from cssvariablesdeclaration import *
from property import *
from cssvalue import *

View File

@ -5,7 +5,7 @@ added http://www.w3.org/TR/css3-fonts/.
"""
__all__ = ['CSSFontFaceRule']
__docformat__ = 'restructuredtext'
__version__ = '$Id: cssfontfacerule.py 1818 2009-07-30 21:39:00Z cthedot $'
__version__ = '$Id: cssfontfacerule.py 1868 2009-10-17 19:36:54Z cthedot $'
from cssstyledeclaration import CSSStyleDeclaration
import cssrule
@ -85,12 +85,16 @@ class CSSFontFaceRule(cssrule.CSSRule):
self._valuestr(cssText),
error=xml.dom.InvalidModificationErr)
else:
wellformed = True
# save if parse goes wrong
oldstyle = CSSStyleDeclaration()
oldstyle._absorb(self.style)
ok = True
beforetokens, brace = self._tokensupto2(tokenizer,
blockstartonly=True,
separateEnd=True)
if self._tokenvalue(brace) != u'{':
wellformed = False
ok = False
self._log.error(
u'CSSFontFaceRule: No start { of style declaration found: %r' %
self._valuestr(cssText), brace)
@ -102,7 +106,7 @@ class CSSFontFaceRule(cssrule.CSSRule):
beforewellformed, expected = self._parse(expected=':',
seq=newseq, tokenizer=self._tokenize2(beforetokens),
productions={})
wellformed = wellformed and beforewellformed and new['wellformed']
ok = ok and beforewellformed and new['wellformed']
styletokens, braceorEOFtoken = self._tokensupto2(tokenizer,
blockendonly=True,
@ -110,32 +114,30 @@ class CSSFontFaceRule(cssrule.CSSRule):
val, typ = self._tokenvalue(braceorEOFtoken), self._type(braceorEOFtoken)
if val != u'}' and typ != 'EOF':
wellformed = False
ok = False
self._log.error(
u'CSSFontFaceRule: No "}" after style declaration found: %r' %
self._valuestr(cssText))
nonetoken = self._nexttoken(tokenizer)
if nonetoken:
wellformed = False
ok = False
self._log.error(u'CSSFontFaceRule: Trailing content found.',
token=nonetoken)
teststyle = CSSStyleDeclaration(parentRule=self)
if 'EOF' == typ:
# add again as style needs it
styletokens.append(braceorEOFtoken)
# may raise:
teststyle.cssText = styletokens
if wellformed:
# contains probably comments only upto {
self._setSeq(newseq)
# known as correct from before
cssutils.log.enabled = False
self.style.cssText = styletokens
cssutils.log.enabled = True
# SET, may raise:
self.style.cssText = styletokens
if ok:
# contains probably comments only (upto ``{``)
self._setSeq(newseq)
else:
# RESET
self.style._absorb(oldstyle)
cssText = property(_getCssText, _setCssText,

View File

@ -2,7 +2,7 @@
``name`` property from http://www.w3.org/TR/css3-cascade/#cascading."""
__all__ = ['CSSImportRule']
__docformat__ = 'restructuredtext'
__version__ = '$Id: cssimportrule.py 1824 2009-08-01 21:00:34Z cthedot $'
__version__ = '$Id: cssimportrule.py 1871 2009-10-17 19:57:37Z cthedot $'
import cssrule
import cssutils
@ -105,6 +105,10 @@ class CSSImportRule(cssrule.CSSRule):
self._valuestr(cssText),
error=xml.dom.InvalidModificationErr)
else:
# save if parse goes wrong
oldmedia = cssutils.stylesheets.MediaList()
oldmedia._absorb(self.media)
# for closures: must be a mutable
new = {'keyword': self._tokenvalue(attoken),
'href': None,
@ -164,12 +168,14 @@ class CSSImportRule(cssrule.CSSRule):
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')
#media = cssutils.stylesheets.MediaList()
self.media.mediaText = mediatokens
if self.media.wellformed:
new['media'] = self.media
seq.append(self.media, 'media')
else:
# RESET
self.media._absorb(oldmedia)
new['wellformed'] = False
self._log.error(u'CSSImportRule: Invalid MediaList: %s' %
self._valuestr(cssText), token=token)
@ -227,17 +233,8 @@ class CSSImportRule(cssrule.CSSRule):
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
if not new['media']:
# reset media to base media
self.media.mediaText = u'all'
newseq.append(self.media, 'media')
self.name = new['name']

View File

@ -1,7 +1,7 @@
"""CSSMediaRule implements DOM Level 2 CSS CSSMediaRule."""
__all__ = ['CSSMediaRule']
__docformat__ = 'restructuredtext'
__version__ = '$Id: cssmediarule.py 1820 2009-08-01 20:53:08Z cthedot $'
__version__ = '$Id: cssmediarule.py 1871 2009-10-17 19:57:37Z cthedot $'
import cssrule
import cssutils
@ -87,6 +87,7 @@ class CSSMediaRule(cssrule.CSSRule):
- :exc:`~xml.dom.NoModificationAllowedErr`:
Raised if the rule is readonly.
"""
# media "name"? { cssRules }
super(CSSMediaRule, self)._setCssText(cssText)
# might be (cssText, namespaces)
@ -104,7 +105,9 @@ class CSSMediaRule(cssrule.CSSRule):
self._valuestr(cssText),
error=xml.dom.InvalidModificationErr)
else:
# media "name"? { cssRules }
# save if parse goes wrong
oldmedia = cssutils.stylesheets.MediaList()
oldmedia._absorb(self.media)
# media
wellformed = True
@ -112,8 +115,7 @@ class CSSMediaRule(cssrule.CSSRule):
mediaqueryendonly=True,
separateEnd=True)
if u'{' == self._tokenvalue(end) or self._prods.STRING == self._type(end):
newmedia = cssutils.stylesheets.MediaList()
newmedia.mediaText = mediatokens
self.media.mediaText = mediatokens
# name (optional)
name = None
@ -209,14 +211,16 @@ class CSSMediaRule(cssrule.CSSRule):
new=new)
# no post condition
if newmedia.wellformed and wellformed:
# keep reference
self._media.mediaText = newmedia.mediaText
if self.media.wellformed and wellformed:
self.name = name
self._setSeq(nameseq)
del self._cssRules[:]
for r in newcssrules:
self._cssRules.append(r)
else:
# RESET
self.media._absorb(oldmedia)
cssText = property(_getCssText, _setCssText,
doc="(DOM) The parsable textual representation of this rule.")
@ -243,7 +247,13 @@ class CSSMediaRule(cssrule.CSSRule):
Delete the rule at `index` from the media block.
:param index:
of the rule to remove within the media block's rule collection
The `index` of the rule to be removed from the media block's rule
list. For an `index` < 0 **no** :exc:`~xml.dom.IndexSizeErr` is
raised but rules for normal Python lists are used. E.g.
``deleteRule(-1)`` removes the last rule in cssRules.
`index` may also be a CSSRule object which will then be removed
from the media block.
:Exceptions:
- :exc:`~xml.dom.IndexSizeErr`:
@ -254,6 +264,16 @@ class CSSMediaRule(cssrule.CSSRule):
"""
self._checkReadonly()
if isinstance(index, cssrule.CSSRule):
for i, r in enumerate(self.cssRules):
if index == r:
index = i
break
else:
raise xml.dom.IndexSizeErr(u"CSSMediaRule: Not a rule in"
" this rule'a cssRules list: %s"
% index)
try:
self._cssRules[index]._parentRule = None # detach
del self._cssRules[index] # remove from @media

View File

@ -1,7 +1,7 @@
"""CSSPageRule implements DOM Level 2 CSS CSSPageRule."""
__all__ = ['CSSPageRule']
__docformat__ = 'restructuredtext'
__version__ = '$Id: csspagerule.py 1824 2009-08-01 21:00:34Z cthedot $'
__version__ = '$Id: csspagerule.py 1868 2009-10-17 19:36:54Z cthedot $'
from cssstyledeclaration import CSSStyleDeclaration
from selectorlist import SelectorList
@ -141,7 +141,6 @@ class CSSPageRule(cssrule.CSSRule):
# 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 wellformed, newseq
def _getCssText(self):
@ -171,7 +170,11 @@ class CSSPageRule(cssrule.CSSRule):
self._valuestr(cssText),
error=xml.dom.InvalidModificationErr)
else:
wellformed = True
# save if parse goes wrong
oldstyle = CSSStyleDeclaration()
oldstyle._absorb(self.style)
ok = True
selectortokens, startbrace = self._tokensupto2(tokenizer,
blockstartonly=True,
separateEnd=True)
@ -180,22 +183,21 @@ class CSSPageRule(cssrule.CSSRule):
separateEnd=True)
nonetoken = self._nexttoken(tokenizer)
if self._tokenvalue(startbrace) != u'{':
wellformed = False
ok = False
self._log.error(
u'CSSPageRule: No start { of style declaration found: %r' %
self._valuestr(cssText), startbrace)
elif nonetoken:
wellformed = False
ok = False
self._log.error(
u'CSSPageRule: Trailing content found.', token=nonetoken)
wellformed, newselectorseq = self.__parseSelectorText(selectortokens)
selok, newselectorseq = self.__parseSelectorText(selectortokens)
ok = ok and selok
teststyle = CSSStyleDeclaration(parentRule=self)
val, typ = self._tokenvalue(braceorEOFtoken), self._type(braceorEOFtoken)
if val != u'}' and typ != 'EOF':
wellformed = False
ok = False
self._log.error(
u'CSSPageRule: No "}" after style declaration found: %r' %
self._valuestr(cssText))
@ -203,14 +205,14 @@ class CSSPageRule(cssrule.CSSRule):
if 'EOF' == typ:
# add again as style needs it
styletokens.append(braceorEOFtoken)
teststyle.cssText = styletokens
if wellformed:
# known as correct from before
cssutils.log.enabled = False
self._selectorText = newselectorseq # TODO: TEST and REFS
self.style.cssText = styletokens
cssutils.log.enabled = True
if ok:
# TODO: TEST and REFS
self._selectorText = newselectorseq
else:
# RESET
self.style._absorb(oldstyle)
cssText = property(_getCssText, _setCssText,
doc="(DOM) The parsable textual representation of this rule.")

View File

@ -1,7 +1,7 @@
"""CSSRule implements DOM Level 2 CSS CSSRule."""
__all__ = ['CSSRule']
__docformat__ = 'restructuredtext'
__version__ = '$Id: cssrule.py 1808 2009-07-29 13:09:36Z cthedot $'
__version__ = '$Id: cssrule.py 1855 2009-10-07 17:03:19Z cthedot $'
import cssutils
import xml.dom
@ -27,9 +27,11 @@ class CSSRule(cssutils.util.Base2):
FONT_FACE_RULE = 5 #f
PAGE_RULE = 6 #p
NAMESPACE_RULE = 7 # CSSOM
VARIABLES_RULE = 8 # CSS Variables
_typestrings = ['UNKNOWN_RULE', 'STYLE_RULE', 'CHARSET_RULE', 'IMPORT_RULE',
'MEDIA_RULE', 'FONT_FACE_RULE', 'PAGE_RULE', 'NAMESPACE_RULE',
'VARIABLES_RULE',
'COMMENT']
def __init__(self, parentRule=None, parentStyleSheet=None, readonly=False):

View File

@ -51,7 +51,7 @@ TODO:
"""
__all__ = ['CSSStyleDeclaration', 'Property']
__docformat__ = 'restructuredtext'
__version__ = '$Id: cssstyledeclaration.py 1819 2009-08-01 20:52:43Z cthedot $'
__version__ = '$Id: cssstyledeclaration.py 1870 2009-10-17 19:56:59Z cthedot $'
from cssproperties import CSS2Properties
from property import Property
@ -201,6 +201,12 @@ class CSSStyleDeclaration(CSS2Properties, cssutils.util.Base2):
names.append(val.name)
return reversed(names)
def _absorb(self, other):
"""Replace all own data with data from other object."""
self._parentRule = other._parentRule
self.seq.absorb(other.seq)
self._readonly = other._readonly
# overwritten accessor functions for CSS2Properties' properties
def _getP(self, CSSName):
"""(DOM CSS2Properties) Overwritten here and effectively the same as

View File

@ -1,7 +1,7 @@
"""CSSStyleRule implements DOM Level 2 CSS CSSStyleRule."""
__all__ = ['CSSStyleRule']
__docformat__ = 'restructuredtext'
__version__ = '$Id: cssstylerule.py 1815 2009-07-29 16:51:58Z cthedot $'
__version__ = '$Id: cssstylerule.py 1868 2009-10-17 19:36:54Z cthedot $'
from cssstyledeclaration import CSSStyleDeclaration
from selectorlist import SelectorList
@ -104,28 +104,30 @@ class CSSStyleRule(cssrule.CSSRule):
self._log.error(u'CSSStyleRule: No style rule: %r' %
self._valuestr(cssText),
error=xml.dom.InvalidModificationErr)
else:
wellformed = True
testselectorlist, teststyle = None, None
else:
# save if parse goes wrong
oldstyle = CSSStyleDeclaration()
oldstyle._absorb(self.style)
oldselector = SelectorList()
oldselector._absorb(self.selectorList)
ok = True
bracetoken = selectortokens.pop()
if self._tokenvalue(bracetoken) != u'{':
wellformed = False
ok = False
self._log.error(
u'CSSStyleRule: No start { of style declaration found: %r' %
self._valuestr(cssText), bracetoken)
elif not selectortokens:
wellformed = False
ok = False
self._log.error(u'CSSStyleRule: No selector found: %r.' %
self._valuestr(cssText), bracetoken)
testselectorlist = SelectorList(selectorText=(selectortokens,
namespaces),
parentRule=self)
# SET
self.selectorList.selectorText = (selectortokens,
namespaces)
if not styletokens:
wellformed = False
ok = False
self._log.error(
u'CSSStyleRule: No style declaration or "}" found: %r' %
self._valuestr(cssText))
@ -133,7 +135,7 @@ class CSSStyleRule(cssrule.CSSRule):
braceorEOFtoken = styletokens.pop()
val, typ = self._tokenvalue(braceorEOFtoken), self._type(braceorEOFtoken)
if val != u'}' and typ != 'EOF':
wellformed = False
ok = False
self._log.error(
u'CSSStyleRule: No "}" after style declaration found: %r' %
self._valuestr(cssText))
@ -141,14 +143,19 @@ class CSSStyleRule(cssrule.CSSRule):
if 'EOF' == typ:
# add again as style needs it
styletokens.append(braceorEOFtoken)
teststyle = CSSStyleDeclaration(styletokens, parentRule=self)
# SET
try:
self.style.cssText = styletokens
except:
# reset in case of error
self.selectorList._absorb(oldselector)
raise
if wellformed and testselectorlist and teststyle:
# known as correct from before
cssutils.log.enabled = False
self.style.cssText = styletokens
self.selectorList.selectorText=(selectortokens, namespaces)
cssutils.log.enabled = True
if not ok or not self.wellformed:
# reset as not ok
self.selectorList._absorb(oldselector)
self.style._absorb(oldstyle)
cssText = property(_getCssText, _setCssText,
doc="(DOM) The parsable textual representation of this rule.")

View File

@ -9,10 +9,11 @@ TODO:
"""
__all__ = ['CSSStyleSheet']
__docformat__ = 'restructuredtext'
__version__ = '$Id: cssstylesheet.py 1820 2009-08-01 20:53:08Z cthedot $'
__version__ = '$Id: cssstylesheet.py 1857 2009-10-10 21:49:33Z cthedot $'
from cssutils.helper import Deprecated
from cssutils.util import _Namespaces, _SimpleNamespaces, _readUrl
from cssrule import CSSRule
import cssutils.stylesheets
import xml.dom
@ -204,11 +205,26 @@ class CSSStyleSheet(cssutils.stylesheets.StyleSheet):
token, xml.dom.HierarchyRequestErr)
else:
if rule.wellformed:
for i, r in enumerate(seq):
if r.type == r.NAMESPACE_RULE and r.prefix == rule.prefix:
# replace as doubled:
seq[i] = rule
self._log.info(
u'CSSStylesheet: CSSNamespaceRule with same prefix found, replacing: %r'
% r.cssText,
token, neverraise=True)
seq.append(rule)
# temporary namespaces given to CSSStyleRule and @media
new['namespaces'][rule.prefix] = rule.namespaceURI
return 2
def variablesrule(expected, seq, token, tokenizer):
rule = cssutils.css.CSSVariablesRule(parentStyleSheet=self)
rule.cssText = self._tokensupto2(tokenizer, token)
if rule.wellformed:
seq.append(rule)
return 2
def fontfacerule(expected, seq, token, tokenizer):
rule = cssutils.css.CSSFontFaceRule(parentStyleSheet=self)
rule.cssText = self._tokensupto2(tokenizer, token)
@ -266,6 +282,7 @@ class CSSStyleSheet(cssutils.stylesheets.StyleSheet):
'NAMESPACE_SYM': namespacerule,
'PAGE_SYM': pagerule,
'MEDIA_SYM': mediarule,
'VARIABLES_SYM': variablesrule,
'ATKEYWORD': unknownrule
},
default=ruleset)
@ -366,10 +383,14 @@ class CSSStyleSheet(cssutils.stylesheets.StyleSheet):
"""Delete rule at `index` from the style sheet.
:param index:
of the rule to remove in the StyleSheet's rule list. For an
`index` < 0 **no** :exc:`~xml.dom.IndexSizeErr` is raised but
rules for normal Python lists are used. E.g. ``deleteRule(-1)``
removes the last rule in cssRules.
The `index` of the rule to be removed from the StyleSheet's rule
list. For an `index` < 0 **no** :exc:`~xml.dom.IndexSizeErr` is
raised but rules for normal Python lists are used. E.g.
``deleteRule(-1)`` removes the last rule in cssRules.
`index` may also be a CSSRule object which will then be removed
from the StyleSheet.
:exceptions:
- :exc:`~xml.dom.IndexSizeErr`:
Raised if the specified index does not correspond to a rule in
@ -381,6 +402,16 @@ class CSSStyleSheet(cssutils.stylesheets.StyleSheet):
"""
self._checkReadonly()
if isinstance(index, CSSRule):
for i, r in enumerate(self.cssRules):
if index == r:
index = i
break
else:
raise xml.dom.IndexSizeErr(u"CSSStyleSheet: Not a rule in"
" this sheets'a cssRules list: %s"
% index)
try:
rule = self._cssRules[index]
except IndexError:
@ -495,14 +526,16 @@ class CSSStyleSheet(cssutils.stylesheets.StyleSheet):
if inOrder:
index = 0
# always first and only
if (self._cssRules and self._cssRules[0].type == rule.CHARSET_RULE):
if (self._cssRules
and self._cssRules[0].type == rule.CHARSET_RULE):
self._cssRules[0].encoding = rule.encoding
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.',
u'CSSStylesheet: @charset only allowed once at the'
' beginning of a stylesheet.',
error=xml.dom.HierarchyRequestErr)
return
else:
@ -532,7 +565,7 @@ class CSSStyleSheet(cssutils.stylesheets.StyleSheet):
else:
# find first point to insert
if self._cssRules and self._cssRules[0].type in (rule.CHARSET_RULE,
rule.COMMENT):
rule.COMMENT):
index = 1
else:
index = 0
@ -544,12 +577,18 @@ class CSSStyleSheet(cssutils.stylesheets.StyleSheet):
u'CSSStylesheet: Found @charset at index 0.',
error=xml.dom.HierarchyRequestErr)
return
# before @namespace, @page, @font-face, @media and stylerule
# before @namespace @variables @page @font-face @media 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):
if r.type in (r.NAMESPACE_RULE,
r.VARIABLES_RULE,
r.MEDIA_RULE,
r.PAGE_RULE,
r.STYLE_RULE,
r.FONT_FACE_RULE):
self._log.error(
u'CSSStylesheet: Cannot insert @import here, found @namespace, @media, @page or CSSStyleRule before index %s.' %
u'CSSStylesheet: Cannot insert @import here,'
' found @namespace, @variables, @media, @page or'
' CSSStyleRule before index %s.' %
index,
error=xml.dom.HierarchyRequestErr)
return
@ -567,8 +606,10 @@ class CSSStyleSheet(cssutils.stylesheets.StyleSheet):
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):
if r.type in (r.VARIABLES_RULE, r.MEDIA_RULE,
r.PAGE_RULE, r.STYLE_RULE,
r.FONT_FACE_RULE, r.UNKNOWN_RULE,
r.COMMENT):
index = i # before these
break
else:
@ -576,16 +617,22 @@ class CSSStyleSheet(cssutils.stylesheets.StyleSheet):
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.' %
u'CSSStylesheet: Cannot insert @namespace here,'
' found @charset or @import after index %s.' %
index,
error=xml.dom.HierarchyRequestErr)
return
# before @media and stylerule
# before @variables @media @page @font-face and stylerule
for r in self._cssRules[:index]:
if r.type in (r.MEDIA_RULE, r.PAGE_RULE, r.STYLE_RULE,
if r.type in (r.VARIABLES_RULE,
r.MEDIA_RULE,
r.PAGE_RULE,
r.STYLE_RULE,
r.FONT_FACE_RULE):
self._log.error(
u'CSSStylesheet: Cannot insert @namespace here, found @media, @page or CSSStyleRule before index %s.' %
u'CSSStylesheet: Cannot insert @namespace here,'
' found @variables, @media, @page or CSSStyleRule'
' before index %s.' %
index,
error=xml.dom.HierarchyRequestErr)
return
@ -597,6 +644,56 @@ class CSSStyleSheet(cssutils.stylesheets.StyleSheet):
if _clean:
self._cleanNamespaces()
# @variables
elif rule.type == rule.VARIABLES_RULE:
if inOrder:
if rule.type in (r.type for r in self):
# find last of this type
for i, r in enumerate(reversed(self._cssRules)):
if r.type == rule.type:
index = len(self._cssRules) - i
break
else:
# find first point to insert
for i, r in enumerate(self._cssRules):
if r.type in (r.MEDIA_RULE,
r.PAGE_RULE,
r.STYLE_RULE,
r.FONT_FACE_RULE,
r.UNKNOWN_RULE,
r.COMMENT):
index = i # before these
break
else:
# after @charset @import @namespace
for r in self._cssRules[index:]:
if r.type in (r.CHARSET_RULE,
r.IMPORT_RULE,
r.NAMESPACE_RULE):
self._log.error(
u'CSSStylesheet: Cannot insert @variables here,'
' found @charset, @import or @namespace after'
' index %s.' %
index,
error=xml.dom.HierarchyRequestErr)
return
# before @media @page @font-face and stylerule
for r in self._cssRules[:index]:
if r.type in (r.MEDIA_RULE,
r.PAGE_RULE,
r.STYLE_RULE,
r.FONT_FACE_RULE):
self._log.error(
u'CSSStylesheet: Cannot insert @variables here,'
' found @media, @page or CSSStyleRule'
' before index %s.' %
index,
error=xml.dom.HierarchyRequestErr)
return
self._cssRules.insert(index, rule)
# all other where order is not important
else:
if inOrder:

View File

@ -5,9 +5,10 @@
- CSSValueList implements DOM Level 2 CSS CSSValueList
"""
__all__ = ['CSSValue', 'CSSPrimitiveValue', 'CSSValueList', 'RGBColor']
__all__ = ['CSSValue', 'CSSPrimitiveValue', 'CSSValueList',
'CSSVariable', 'RGBColor']
__docformat__ = 'restructuredtext'
__version__ = '$Id: cssvalue.py 1834 2009-08-02 12:20:21Z cthedot $'
__version__ = '$Id: cssvalue.py 1864 2009-10-11 15:11:39Z cthedot $'
from cssutils.prodparser import *
import cssutils
@ -29,13 +30,16 @@ class CSSValue(cssutils.util._NewBase):
CSS_VALUE_LIST = 2
# The value is a custom value.
CSS_CUSTOM = 3
# The value is a CSSVariable.
CSS_VARIABLE = 4
_typestrings = {0: 'CSS_INHERIT' ,
_typestrings = {0: 'CSS_INHERIT' ,
1: 'CSS_PRIMITIVE_VALUE',
2: 'CSS_VALUE_LIST',
3: 'CSS_CUSTOM'}
3: 'CSS_CUSTOM',
4: 'CSS_VARIABLE'}
def __init__(self, cssText=None, readonly=False):
def __init__(self, cssText=None, parent=None, readonly=False):
"""
:param cssText:
the parsable cssText of the value
@ -46,6 +50,7 @@ class CSSValue(cssutils.util._NewBase):
self._cssValueType = None
self.wellformed = False
self.parent = parent
if cssText is not None: # may be 0
if type(cssText) in (int, float):
@ -109,9 +114,9 @@ class CSSValue(cssutils.util._NewBase):
# used as operator is , / or S
nextSor = u',/'
term = Choice(Sequence(PreDef.unary(),
Choice(PreDef.number(nextSor=nextSor),
term = Choice(Sequence(PreDef.unary(),
Choice(PreDef.number(nextSor=nextSor),
PreDef.percentage(nextSor=nextSor),
PreDef.dimension(nextSor=nextSor))),
PreDef.string(nextSor=nextSor),
@ -120,33 +125,55 @@ class CSSValue(cssutils.util._NewBase):
PreDef.hexcolor(nextSor=nextSor),
PreDef.unicode_range(nextSor=nextSor),
# special case IE only expression
Prod(name='expression',
Prod(name='expression',
match=lambda t, v: t == self._prods.FUNCTION and (
cssutils.helper.normalize(v) in (u'expression(',
u'alpha(') or
v.startswith(u'progid:DXImageTransform.Microsoft.') ),
cssutils.helper.normalize(v) in (u'expression(',
u'alpha(') or
v.startswith(u'progid:DXImageTransform.Microsoft.')
),
nextSor=nextSor,
toSeq=lambda t, tokens: (ExpressionValue.name,
ExpressionValue(cssutils.helper.pushtoken(t,
tokens)))
toSeq=lambda t, tokens: (ExpressionValue._functionName,
ExpressionValue(cssutils.helper.pushtoken(t,
tokens)))
),
# CSS Variable var(
PreDef.variable(nextSor=nextSor,
toSeq=lambda t, tokens: ('CSSVariable',
CSSVariable(
cssutils.helper.pushtoken(t, tokens))
)
),
# other functions like rgb( etc
PreDef.function(nextSor=nextSor,
toSeq=lambda t, tokens: ('FUNCTION',
CSSFunction(cssutils.helper.pushtoken(t,
tokens)))))
toSeq=lambda t, tokens: ('FUNCTION',
CSSFunction(
cssutils.helper.pushtoken(t, tokens))
)
)
)
operator = Choice(PreDef.S(),
PreDef.char('comma', ',', toSeq=lambda t, tokens: ('operator', t[1])),
PreDef.char('slash', '/', toSeq=lambda t, tokens: ('operator', t[1])),
PreDef.char('comma', ',',
toSeq=lambda t, tokens: ('operator', t[1])),
PreDef.char('slash', '/',
toSeq=lambda t, tokens: ('operator', t[1])),
optional=True)
# CSSValue PRODUCTIONS
valueprods = Sequence(term,
valueprods = Sequence(term,
# TODO: only when setting via other class
PreDef.char('END', ';',
stopAndKeep=True,
optional=True),
Sequence(operator, # mayEnd this Sequence if whitespace
term,
PreDef.char('END', ';',
stopAndKeep=True,
optional=True),
minmax=lambda: (0, None)))
# parse
wellformed, seq, store, unusedtokens = ProdParser().parse(cssText,
u'CSSValue',
valueprods)
wellformed, seq, store, notused = ProdParser().parse(cssText,
u'CSSValue',
valueprods,
keepS=True)
if wellformed:
# - count actual values and set firstvalue which is used later on
# - combine comma separated list, e.g. font-family to a single item
@ -183,7 +210,7 @@ class CSSValue(cssutils.util._NewBase):
break
newval = item.value + next.value
newseq.append(newval, next.type,
newseq.append(newval, next.type,
item.line, item.col)
if not firstvalue:
firstvalue = (newval, next.type)
@ -202,7 +229,7 @@ class CSSValue(cssutils.util._NewBase):
if not firstvalue:
self._log.error(
u'CSSValue: Unknown syntax or no value: %r.' %
u'CSSValue: Unknown syntax or no value: %r.' %
self._valuestr(cssText))
else:
# ok and set
@ -214,16 +241,24 @@ class CSSValue(cssutils.util._NewBase):
del self._value
if count == 1:
# inherit, primitive or variable
if isinstance(firstvalue[0], basestring) and\
u'inherit' == cssutils.helper.normalize(firstvalue[0]):
self.__class__ = CSSValue
self._cssValueType = CSSValue.CSS_INHERIT
self._cssValueType = CSSValue.CSS_INHERIT
elif 'CSSVariable' == firstvalue[1]:
self.__class__ = CSSVariable
self._value = firstvalue
# TODO: remove major hack!
self._name = firstvalue[0]._name
else:
self.__class__ = CSSPrimitiveValue
self._value = firstvalue
elif count > 1:
# valuelist
self.__class__ = CSSValueList
# change items in list to specific type (primitive etc)
newseq = self._tempSeq()
commalist = []
@ -235,7 +270,8 @@ class CSSValue(cssutils.util._NewBase):
return cssutils.helper.string(item.value)
elif self._prods.URI == item.type:
return cssutils.helper.uri(item.value)
elif self._prods.FUNCTION == item.type:
elif self._prods.FUNCTION == item.type or\
'CSSVariable' == item.type:
return item.value.cssText
else:
return item.value
@ -246,9 +282,9 @@ class CSSValue(cssutils.util._NewBase):
if anything in there
"""
if commalist:
newseq.replace(-1,
newseq.replace(-1,
CSSPrimitiveValue(cssText=u''.join(
commalist)),
commalist)),
CSSPrimitiveValue,
newseq[-1].line,
newseq[-1].col)
@ -263,7 +299,8 @@ class CSSValue(cssutils.util._NewBase):
self._prods.PERCENTAGE,
self._prods.STRING,
self._prods.URI,
self._prods.UNICODE_RANGE):
self._prods.UNICODE_RANGE,
'CSSVariable'):
if nexttocommalist:
# wait until complete
commalist.append(itemValue(item))
@ -271,13 +308,13 @@ class CSSValue(cssutils.util._NewBase):
saveifcommalist(commalist, newseq)
# append new item
if hasattr(item.value, 'cssText'):
newseq.append(item.value,
item.value.__class__,
newseq.append(item.value,
item.value.__class__,
item.line, item.col)
else:
newseq.append(CSSPrimitiveValue(itemValue(item)),
CSSPrimitiveValue,
newseq.append(CSSPrimitiveValue(itemValue(item)),
CSSPrimitiveValue,
item.line, item.col)
nexttocommalist = False
@ -285,7 +322,7 @@ class CSSValue(cssutils.util._NewBase):
elif u',' == item.value:
if not commalist:
# save last item to commalist
commalist.append(itemValue(self._seq[i-1]))
commalist.append(itemValue(self._seq[i - 1]))
commalist.append(u',')
nexttocommalist = True
@ -297,12 +334,13 @@ class CSSValue(cssutils.util._NewBase):
saveifcommalist(commalist, newseq)
self._setSeq(newseq)
else:
# should not happen...
self.__class__ = CSSValue
self._cssValueType = CSSValue.CSS_CUSTOM
cssText = property(lambda self: cssutils.ser.do_css_CSSValue(self),
cssText = property(lambda self: cssutils.ser.do_css_CSSValue(self),
_setCssText,
doc="A string representation of the current value.")
@ -373,7 +411,7 @@ class CSSPrimitiveValue(CSSValue):
_countertypes = (CSS_COUNTER,)
_recttypes = (CSS_RECT,)
_rbgtypes = (CSS_RGBCOLOR, CSS_RGBACOLOR)
_lengthtypes = (CSS_NUMBER, CSS_EMS, CSS_EXS,
_lengthtypes = (CSS_NUMBER, CSS_EMS, CSS_EXS,
CSS_PX, CSS_CM, CSS_MM, CSS_IN, CSS_PT, CSS_PC)
# oldtype: newType: converterfunc
@ -436,14 +474,14 @@ class CSSPrimitiveValue(CSSValue):
'CSS_DEG', 'CSS_RAD', 'CSS_GRAD',
'CSS_MS', 'CSS_S',
'CSS_HZ', 'CSS_KHZ',
'CSS_DIMENSION',
'CSS_DIMENSION',
'CSS_STRING', 'CSS_URI', 'CSS_IDENT',
'CSS_ATTR', 'CSS_COUNTER', 'CSS_RECT',
'CSS_RGBCOLOR', 'CSS_RGBACOLOR',
'CSS_UNICODE_RANGE'
]
_reNumDim = re.compile(ur'([+-]?\d*\.\d+|[+-]?\d+)(.*)$', re.I| re.U|re.X)
_reNumDim = re.compile(ur'([+-]?\d*\.\d+|[+-]?\d+)(.*)$', re.I | re.U | re.X)
def _unitDIMENSION(value):
"""Check val for dimension name."""
@ -467,8 +505,8 @@ class CSSPrimitiveValue(CSSValue):
'rgb(': 'CSS_RGBCOLOR',
'rgba(': 'CSS_RGBACOLOR',
}
return units.get(re.findall(ur'^(.*?\()',
cssutils.helper.normalize(value.cssText),
return units.get(re.findall(ur'^(.*?\()',
cssutils.helper.normalize(value.cssText),
re.U)[0],
'CSS_UNKNOWN')
@ -601,13 +639,13 @@ class CSSPrimitiveValue(CSSValue):
self._checkReadonly()
if unitType not in self._floattypes:
raise xml.dom.InvalidAccessErr(
u'CSSPrimitiveValue: unitType %r is not a float type' %
u'CSSPrimitiveValue: unitType %r is not a float type' %
self._getCSSPrimitiveTypeString(unitType))
try:
val = float(floatValue)
except ValueError, e:
raise xml.dom.InvalidAccessErr(
u'CSSPrimitiveValue: floatValue %r is not a float' %
u'CSSPrimitiveValue: floatValue %r is not a float' %
floatValue)
oldval, dim = self._getNumDim()
@ -761,17 +799,17 @@ class CSSValueList(CSSValue):
"""
cssValueType = CSSValue.CSS_VALUE_LIST
def __init__(self, cssText=None, readonly=False):
def __init__(self, cssText=None, parent=None, readonly=False):
"""Init a new CSSValueList"""
super(CSSValueList, self).__init__(cssText=cssText, readonly=readonly)
super(CSSValueList, self).__init__(cssText=cssText,
parent=parent,
readonly=readonly)
self._items = []
def __iter__(self):
"CSSValueList is iterable."
def itemsiter():
for i in range (0, self.length):
yield self.item(i)
return itemsiter()
for item in self.__items():
yield item.value
def __str__(self):
return "<cssutils.css.%s object cssValueType=%r cssText=%r length=%r at 0x%x>" % (
@ -799,7 +837,7 @@ class CSSValueList(CSSValue):
class CSSFunction(CSSPrimitiveValue):
"""A CSS function value like rect() etc."""
name = u'CSSFunction'
_functionName = u'CSSFunction'
primitiveType = CSSPrimitiveValue.CSS_UNKNOWN
def __init__(self, cssText=None, readonly=False):
@ -812,46 +850,37 @@ class CSSFunction(CSSPrimitiveValue):
defaults to False
"""
super(CSSFunction, self).__init__()
self._funcType = None
self.valid = False
self.wellformed = False
if cssText is not None:
self.cssText = cssText
self._funcType = None
self._readonly = readonly
def __repr__(self):
return "cssutils.css.%s(%r)" % (self.__class__.__name__, self.cssText)
def __str__(self):
return "<cssutils.css.%s object primitiveType=%s cssText=%r at 0x%x>" % (
self.__class__.__name__, self.primitiveTypeString, self.cssText,
id(self))
def _productiondefinition(self):
"""Return defintion used for parsing."""
types = self._prods # rename!
valueProd = Prod(name='PrimitiveValue',
match=lambda t, v: t in (types.DIMENSION,
types.IDENT,
types.NUMBER,
valueProd = Prod(name='PrimitiveValue',
match=lambda t, v: t in (types.DIMENSION,
types.IDENT,
types.NUMBER,
types.PERCENTAGE,
types.STRING),
toSeq=lambda t, tokens: (t[0], CSSPrimitiveValue(t[1])))
funcProds = Sequence(Prod(name='FUNC',
match=lambda t, v: t == types.FUNCTION,
funcProds = Sequence(Prod(name='FUNC',
match=lambda t, v: t == types.FUNCTION,
toSeq=lambda t, tokens: (t[0], cssutils.helper.normalize(t[1]))),
Choice(Sequence(PreDef.unary(),
Choice(Sequence(PreDef.unary(),
valueProd,
# more values starting with Comma
# should use store where colorType is saved to
# define min and may, closure?
Sequence(PreDef.comma(),
PreDef.unary(),
valueProd,
minmax=lambda: (0, 3)),
Sequence(PreDef.comma(),
PreDef.unary(),
valueProd,
minmax=lambda: (0, 3)),
PreDef.funcEnd(stop=True)),
PreDef.funcEnd(stop=True))
)
@ -861,8 +890,9 @@ class CSSFunction(CSSPrimitiveValue):
self._checkReadonly()
# store: colorType, parts
wellformed, seq, store, unusedtokens = ProdParser().parse(cssText,
self.name,
self._productiondefinition())
self._functionName,
self._productiondefinition(),
keepS=True)
if wellformed:
# combine +/- and following CSSPrimitiveValue, remove S
newseq = self._tempSeq()
@ -876,9 +906,9 @@ class CSSFunction(CSSPrimitiveValue):
next = seq[i]
newval = next.value
if isinstance(newval, CSSPrimitiveValue):
newval.setFloatValue(newval.primitiveType,
newval.setFloatValue(newval.primitiveType,
float(item.value + str(newval.getFloatValue())))
newseq.append(newval, next.type,
newseq.append(newval, next.type,
item.line, item.col)
else:
# expressions only?
@ -893,7 +923,7 @@ class CSSFunction(CSSPrimitiveValue):
self._setSeq(newseq)
self._funcType = newseq[0].value
cssText = property(lambda self: cssutils.ser.do_css_RGBColor(self),
cssText = property(lambda self: cssutils.ser.do_css_RGBColor(self),
_setCssText)
funcType = property(lambda self: self._funcType)
@ -930,37 +960,38 @@ class RGBColor(CSSPrimitiveValue):
def _setCssText(self, cssText):
self._checkReadonly()
types = self._prods # rename!
valueProd = Prod(name='value',
match=lambda t, v: t in (types.NUMBER, types.PERCENTAGE),
valueProd = Prod(name='value',
match=lambda t, v: t in (types.NUMBER, types.PERCENTAGE),
toSeq=lambda t, v: (CSSPrimitiveValue, CSSPrimitiveValue(v)),
toStore='parts'
)
# COLOR PRODUCTION
funccolor = Sequence(Prod(name='FUNC',
funccolor = Sequence(Prod(name='FUNC',
match=lambda t, v: self._normalize(v) in ('rgb(', 'rgba(', 'hsl(', 'hsla(') and t == types.FUNCTION,
toSeq=lambda t, v: (t, self._normalize(v)),
toStore='colorType' ),
PreDef.unary(),
toSeq=lambda t, v: (t, self._normalize(v)),
toStore='colorType'),
PreDef.unary(),
valueProd,
# 2 or 3 more values starting with Comma
Sequence(PreDef.comma(),
PreDef.unary(),
valueProd,
minmax=lambda: (2, 3)),
Sequence(PreDef.comma(),
PreDef.unary(),
valueProd,
minmax=lambda: (2, 3)),
PreDef.funcEnd()
)
colorprods = Choice(funccolor,
PreDef.hexcolor('colorType'),
Prod(name='named color',
Prod(name='named color',
match=lambda t, v: t == types.IDENT,
toStore='colorType'
)
)
# store: colorType, parts
wellformed, seq, store, unusedtokens = ProdParser().parse(cssText,
u'RGBColor',
wellformed, seq, store, unusedtokens = ProdParser().parse(cssText,
u'RGBColor',
colorprods,
{'parts': []})
keepS=True,
store={'parts': []})
if wellformed:
self.wellformed = True
@ -973,16 +1004,16 @@ class RGBColor(CSSPrimitiveValue):
self._setSeq(seq)
cssText = property(lambda self: cssutils.ser.do_css_RGBColor(self),
cssText = property(lambda self: cssutils.ser.do_css_RGBColor(self),
_setCssText)
colorType = property(lambda self: self._colorType)
class ExpressionValue(CSSFunction):
"""Special IE only CSSFunction which may contain *anything*.
Used for expressions and ``alpha(opacity=100)`` currently."""
name = u'Expression (IE only)'
_functionName = u'Expression (IE only)'
def _productiondefinition(self):
"""Return defintion used for parsing."""
@ -992,19 +1023,19 @@ class ExpressionValue(CSSFunction):
"Do not normalize function name!"
return t[0], t[1]
funcProds = Sequence(Prod(name='expression',
match=lambda t, v: t == types.FUNCTION,
funcProds = Sequence(Prod(name='expression',
match=lambda t, v: t == types.FUNCTION,
toSeq=toSeq
),
Sequence(Choice(Prod(name='nested function',
Sequence(Choice(Prod(name='nested function',
match=lambda t, v: t == self._prods.FUNCTION,
toSeq=lambda t, tokens: (CSSFunction.name,
CSSFunction(cssutils.helper.pushtoken(t,
toSeq=lambda t, tokens: (CSSFunction._functionName,
CSSFunction(cssutils.helper.pushtoken(t,
tokens)))
),
Prod(name='part',
Prod(name='part',
match=lambda t, v: v != u')',
toSeq=lambda t, tokens: (t[0], t[1])),
toSeq=lambda t, tokens: (t[0], t[1])),
),
minmax=lambda: (0, None)),
PreDef.funcEnd(stop=True))
@ -1018,3 +1049,80 @@ class ExpressionValue(CSSFunction):
cssText = property(_getCssText, _setCssText,
doc="A string representation of the current value.")
class CSSVariable(CSSValue):
"""The CSSVariable represents a call to CSS Variable."""
def __init__(self, cssText=None, readonly=False):
"""Init a new CSSVariable.
:param cssText:
the parsable cssText of the value, e.g. ``var(x)``
:param readonly:
defaults to False
"""
self._name = None
super(CSSVariable, self).__init__(cssText=cssText,
readonly=readonly)
def __repr__(self):
return "cssutils.css.%s(%r)" % (self.__class__.__name__, self.cssText)
def __str__(self):
return "<cssutils.css.%s object name=%r value=%r at 0x%x>" % (
self.__class__.__name__, self.name, self.value,
id(self))
def _setCssText(self, cssText):
self._checkReadonly()
types = self._prods # rename!
funcProds = Sequence(Prod(name='var',
match=lambda t, v: t == types.FUNCTION
),
PreDef.ident(toStore='ident'),
PreDef.funcEnd(stop=True))
# store: name of variable
store = {'ident': None}
wellformed, seq, store, unusedtokens = ProdParser().parse(cssText,
u'CSSVariable',
funcProds,
keepS=True)
if wellformed:
self._name = store['ident'].value
self._setSeq(seq)
self.wellformed = True
cssText = property(lambda self: cssutils.ser.do_css_CSSVariable(self),
_setCssText,
doc="A string representation of the current variable.")
cssValueType = CSSValue.CSS_VARIABLE
# TODO: writable? check if var (value) available?
name = property(lambda self: self._name)
def _getValue(self):
"Find contained sheet and @variables there"
# TODO: imports!
# property:
if self.parent:
# styleDeclaration:
if self.parent.parent:
# styleRule:
if self.parent.parent.parentRule:
# stylesheet
if self.parent.parent.parentRule.parentStyleSheet:
sheet = self.parent.parent.parentRule.parentStyleSheet
for r in sheet.cssRules:
if r.VARIABLES_RULE == r.type and r.variables:
try:
return r.variables[self.name]
except KeyError:
return None
value = property(_getValue)

View File

@ -0,0 +1,292 @@
"""CSSVariablesDeclaration
http://disruptive-innovations.com/zoo/cssvariables/#mozTocId496530
"""
__all__ = ['CSSVariablesDeclaration']
__docformat__ = 'restructuredtext'
__version__ = '$Id: cssstyledeclaration.py 1819 2009-08-01 20:52:43Z cthedot $'
from cssutils.prodparser import *
from cssvalue import CSSValue
import cssutils
import itertools
import xml.dom
class CSSVariablesDeclaration(cssutils.util._NewBase):
"""The CSSVariablesDeclaration interface represents a single block of
variable declarations.
"""
def __init__(self, cssText=u'', parentRule=None, readonly=False):
"""
:param cssText:
Shortcut, sets CSSVariablesDeclaration.cssText
:param parentRule:
The CSS rule that contains this declaration block or
None if this CSSVariablesDeclaration is not attached to a CSSRule.
:param readonly:
defaults to False
"""
super(CSSVariablesDeclaration, self).__init__()
self._parentRule = parentRule
self._vars = {}
if cssText:
self.cssText = cssText
self._readonly = readonly
def __repr__(self):
return "cssutils.css.%s(cssText=%r)" % (
self.__class__.__name__, self.cssText)
def __str__(self):
return "<cssutils.css.%s object length=%r at 0x%x>" % (
self.__class__.__name__, self.length, id(self))
def __contains__(self, variableName):
"""Check if a variable is in variable declaration block.
:param variableName:
a string
"""
return variableName.lower() in self.keys()
def __getitem__(self, variableName):
"""Retrieve the value of variable ``variableName`` from this
declaration.
"""
return self.getVariableValue(variableName.lower())
def __setitem__(self, variableName, value):
self.setVariable(variableName.lower(), value)
def __delitem__(self, variableName):
return self.removeVariable(variableName.lower())
def __iter__(self):
"""Iterator of names of set variables."""
for name in self.keys():
yield name
def _absorb(self, other):
"""Replace all own data with data from other object."""
self._parentRule = other._parentRule
self.seq.absorb(other.seq)
self._readonly = other._readonly
def keys(self):
"""Analoguous to standard dict returns variable names which are set in
this declaration."""
return self._vars.keys()
def _getCssText(self):
"""Return serialized property cssText."""
return cssutils.ser.do_css_CSSVariablesDeclaration(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.
:exceptions:
- :exc:`~xml.dom.NoModificationAllowedErr`:
Raised if this declaration is readonly or a property is readonly.
- :exc:`~xml.dom.SyntaxErr`:
Raised if the specified CSS string value has a syntax error and
is unparsable.
Format::
variableset
: vardeclaration [ ';' S* vardeclaration ]*
;
vardeclaration
: varname ':' S* term
;
varname
: IDENT S*
;
expr
: [ VARCALL | term ] [ operator [ VARCALL | term ] ]*
;
"""
self._checkReadonly()
vardeclaration = Sequence(
PreDef.ident(),
PreDef.char(u':', u':', toSeq=False),
#PreDef.S(toSeq=False, optional=True),
Prod(name=u'term', match=lambda t, v: True,
toSeq=lambda t, tokens: (u'value',
CSSValue(itertools.chain([t],
tokens))
)
),
PreDef.char(u';', u';', toSeq=False, optional=True),
)
prods = Sequence(vardeclaration, minmax=lambda: (0, None))
# parse
wellformed, seq, store, notused = \
ProdParser().parse(cssText,
u'CSSVariableDeclaration',
prods)
if wellformed:
newseq = self._tempSeq()
# seq contains only name: value pairs plus comments etc
lastname = None
for item in seq:
if u'IDENT' == item.type:
lastname = item
self._vars[lastname.value.lower()] = None
elif u'value' == item.type:
self._vars[lastname.value.lower()] = item.value
newseq.append((lastname.value, item.value),
'var',
lastname.line, lastname.col)
else:
newseq.appendItem(item)
self._setSeq(newseq)
self.wellformed = True
cssText = property(_getCssText, _setCssText,
doc="(DOM) A parsable textual representation of the declaration\
block excluding the surrounding curly braces.")
def _setParentRule(self, parentRule):
self._parentRule = parentRule
parentRule = property(lambda self: self._parentRule, _setParentRule,
doc="(DOM) The CSS rule that contains this"
" declaration block or None if this block"
" is not attached to a CSSRule.")
def getVariableValue(self, variableName):
"""Used to retrieve the value of a variable if it has been explicitly
set within this variable declaration block.
:param variableName:
The name of the variable.
:returns:
the value of the variable if it has been explicitly set in this
variable declaration block. Returns the empty string if the
variable has not been set.
"""
try:
return self._vars[variableName.lower()].cssText
except KeyError, e:
return u''
def removeVariable(self, variableName):
"""Used to remove a variable if it has been explicitly set within this
variable declaration block.
:param variableName:
The name of the variable.
:returns:
the value of the variable if it has been explicitly set for this
variable declaration block. Returns the empty string if the
variable has not been set.
:exceptions:
- :exc:`~xml.dom.NoModificationAllowedErr`:
Raised if this declaration is readonly is readonly.
"""
try:
r = self._vars[variableName.lower()]
except KeyError, e:
return u''
else:
self.seq._readonly = False
if variableName in self._vars:
for i, x in enumerate(self.seq):
if x.value[0] == variableName:
del self.seq[i]
self.seq._readonly = True
del self._vars[variableName.lower()]
return r.cssText
def setVariable(self, variableName, value):
"""Used to set a variable value within this variable declaration block.
:param variableName:
The name of the CSS variable.
:param value:
The new value of the variable, may also be a CSSValue object.
:exceptions:
- :exc:`~xml.dom.SyntaxErr`:
Raised if the specified value has a syntax error and is
unparsable.
- :exc:`~xml.dom.NoModificationAllowedErr`:
Raised if this declaration is readonly or the property is
readonly.
"""
self._checkReadonly()
# check name
wellformed, seq, store, unused = ProdParser().parse(variableName.lower(),
u'variableName',
Sequence(PreDef.ident()
))
if not wellformed:
self._log.error(u'Invalid variableName: %r: %r'
% (variableName, value))
else:
# check value
if isinstance(value, CSSValue):
v = value
else:
v = CSSValue(cssText=value)
if not v.wellformed:
self._log.error(u'Invalid variable value: %r: %r'
% (variableName, value))
else:
# update seq
self.seq._readonly = False
if variableName in self._vars:
for i, x in enumerate(self.seq):
if x.value[0] == variableName:
x.replace(i,
[variableName, v],
x.type,
x.line,
x.col)
break
else:
self.seq.append([variableName, v], 'var')
self.seq._readonly = True
self._vars[variableName] = v
def item(self, index):
"""Used to retrieve the variables that have been explicitly set in
this variable declaration block. The order of the variables
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 variables
in this variable declaration block.
:param index:
of the variable name to retrieve, negative values behave like
negative indexes on Python lists, so -1 is the last element
:returns:
The name of the variable at this ordinal position. The empty
string if no variable exists at this position.
"""
try:
return self.keys()[index]
except IndexError:
return u''
length = property(lambda self: len(self._vars),
doc="The number of variables that have been explicitly set in this"
" variable declaration block. The range of valid indices is 0"
" to length-1 inclusive.")

View File

@ -0,0 +1,164 @@
"""CSSVariables implements (and only partly) experimental
`CSS Variables <http://disruptive-innovations.com/zoo/cssvariables/>`_
"""
__all__ = ['CSSVariablesRule']
__docformat__ = 'restructuredtext'
__version__ = '$Id: cssfontfacerule.py 1818 2009-07-30 21:39:00Z cthedot $'
from cssvariablesdeclaration import CSSVariablesDeclaration
import cssrule
import cssutils
import xml.dom
class CSSVariablesRule(cssrule.CSSRule):
"""
The CSSVariablesRule interface represents a @variables rule within a CSS
style sheet. The @variables rule is used to specify variables.
cssutils uses a :class:`~cssutils.css.CSSVariablesDeclaration` to
represent the variables.
"""
def __init__(self, mediaText=None, variables=None, parentRule=None,
parentStyleSheet=None, readonly=False):
"""
If readonly allows setting of properties in constructor only.
"""
super(CSSVariablesRule, self).__init__(parentRule=parentRule,
parentStyleSheet=parentStyleSheet)
self._atkeyword = u'@variables'
self._media = cssutils.stylesheets.MediaList(mediaText,
readonly=readonly)
self._variables = CSSVariablesDeclaration(parentRule=self)
if variables:
self.variables = variables
self._readonly = readonly
def __repr__(self):
return "cssutils.css.%s(mediaText=%r, variables=%r)" % (
self.__class__.__name__,
self._media.mediaText, self.variables.cssText)
def __str__(self):
return "<cssutils.css.%s object mediaText=%r variables=%r valid=%r at 0x%x>" % (
self.__class__.__name__, self._media.mediaText,
self.variables.cssText, self.valid, id(self))
def _getCssText(self):
"""Return serialized property cssText."""
return cssutils.ser.do_CSSVariablesRule(self)
def _setCssText(self, cssText):
"""
:exceptions:
- :exc:`~xml.dom.SyntaxErr`:
Raised if the specified CSS string value has a syntax error and
is unparsable.
- :exc:`~xml.dom.InvalidModificationErr`:
Raised if the specified CSS string value represents a different
type of rule than the current one.
- :exc:`~xml.dom.HierarchyRequestErr`:
Raised if the rule cannot be inserted at this point in the
style sheet.
- :exc:`~xml.dom.NoModificationAllowedErr`:
Raised if the rule is readonly.
Format::
variables
: VARIABLES_SYM S* medium [ COMMA S* medium ]* LBRACE S* variableset* '}' S*
;
variableset
: LBRACE S* vardeclaration [ ';' S* vardeclaration ]* '}' S*
;
"""
super(CSSVariablesRule, self)._setCssText(cssText)
tokenizer = self._tokenize2(cssText)
attoken = self._nexttoken(tokenizer, None)
if self._type(attoken) != self._prods.VARIABLES_SYM:
self._log.error(u'CSSVariablesRule: No CSSVariablesRule found: %s' %
self._valuestr(cssText),
error=xml.dom.InvalidModificationErr)
else:
# save if parse goes wrong
oldvariables = CSSVariablesDeclaration()
oldvariables._absorb(self.variables)
ok = True
beforetokens, brace = self._tokensupto2(tokenizer,
blockstartonly=True,
separateEnd=True)
if self._tokenvalue(brace) != u'{':
ok = False
self._log.error(
u'CSSVariablesRule: No start { of variable 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={})
ok = ok and beforewellformed and new['wellformed']
variablestokens, braceorEOFtoken = self._tokensupto2(tokenizer,
blockendonly=True,
separateEnd=True)
val, typ = self._tokenvalue(braceorEOFtoken), self._type(braceorEOFtoken)
if val != u'}' and typ != 'EOF':
ok = False
self._log.error(
u'CSSVariablesRule: No "}" after variables declaration found: %r' %
self._valuestr(cssText))
nonetoken = self._nexttoken(tokenizer)
if nonetoken:
ok = False
self._log.error(u'CSSVariablesRule: Trailing content found.',
token=nonetoken)
if 'EOF' == typ:
# add again as variables needs it
variablestokens.append(braceorEOFtoken)
# may raise:
self.variables.cssText = variablestokens
if ok:
# contains probably comments only upto {
self._setSeq(newseq)
else:
# RESET
self.variables._absorb(oldvariables)
cssText = property(_getCssText, _setCssText,
doc="(DOM) The parsable textual representation of this rule.")
def _setVariables(self, variables):
"""
:param variables:
a CSSVariablesDeclaration or string
"""
self._checkReadonly()
if isinstance(variables, basestring):
self._variables.cssText = variables
else:
self._variables = variables
self._variables.parentRule = self
variables = property(lambda self: self._variables, _setVariables,
doc="(DOM) The variables of this rule set, "
"a :class:`~cssutils.css.CSSVariablesDeclaration`.")
type = property(lambda self: self.VARIABLES_RULE,
doc="The type of this rule, as defined by a CSSRule "
"type constant.")
valid = property(lambda self: True, doc='TODO')
# constant but needed:
wellformed = property(lambda self: True)

View File

@ -1,7 +1,7 @@
"""Property is a single CSS property in a CSSStyleDeclaration."""
__all__ = ['Property']
__docformat__ = 'restructuredtext'
__version__ = '$Id: property.py 1811 2009-07-29 13:11:15Z cthedot $'
__version__ = '$Id: property.py 1868 2009-10-17 19:36:54Z cthedot $'
from cssutils.helper import Deprecated
from cssvalue import CSSValue
@ -44,7 +44,7 @@ class Property(cssutils.util.Base):
"""
def __init__(self, name=None, value=None, priority=u'',
_mediaQuery=False, parent=None, parentStyle=None):
_mediaQuery=False, parent=None):
"""
:param name:
a property name string (will be normalized)
@ -58,8 +58,6 @@ class Property(cssutils.util.Base):
:param parent:
the parent object, normally a
:class:`cssutils.css.CSSStyleDeclaration`
:param parentStyle:
DEPRECATED: Use ``parent`` instead
"""
super(Property, self).__init__()
self.seqs = [[], None, []]
@ -76,7 +74,7 @@ class Property(cssutils.util.Base):
if value:
self.cssValue = value
else:
self.seqs[1] = CSSValue()
self.seqs[1] = CSSValue(parent=self)
self._priority = u''
self._literalpriority = u''
@ -246,31 +244,28 @@ class Property(cssutils.util.Base):
type of values than the values allowed by the CSS property.
"""
if self._mediaQuery and not cssText:
self.seqs[1] = CSSValue()
self.seqs[1] = CSSValue(parent=self)
else:
if not self.seqs[1]:
self.seqs[1] = CSSValue()
#if not self.seqs[1]:
# self.seqs[1] = CSSValue(parent=self)
cssvalue = self.seqs[1]
cssvalue.cssText = cssText
if cssvalue.wellformed: #cssvalue._value and
self.seqs[1] = cssvalue
self.wellformed = self.wellformed and cssvalue.wellformed
self.seqs[1] = CSSValue(parent=self)
self.seqs[1].cssText = cssText
self.wellformed = self.wellformed and self.seqs[1].wellformed
# self.valid = self.valid and self.cssValue.valid
cssValue = property(_getCSSValue, _setCSSValue,
doc="(cssutils) CSSValue object of this property")
def _getValue(self):
if self.cssValue:
return self.cssValue.cssText # _value # [0]
return self.cssValue.cssText
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
self._setCSSValue(value)
value = property(_getValue, _setValue,
doc="The textual value of this Properties cssValue.")
@ -483,12 +478,3 @@ class Property(cssutils.util.Base):
valid = property(validate, doc="Check if value of this property is valid "
"in the properties context.")
@Deprecated('Use ``parent`` attribute instead.')
def _getParentStyle(self):
return self._parent
parentStyle = property(_getParentStyle, _setParent,
doc="DEPRECATED: Use ``parent`` instead")

View File

@ -7,9 +7,10 @@ TODO
"""
__all__ = ['Selector']
__docformat__ = 'restructuredtext'
__version__ = '$Id: selector.py 1741 2009-05-09 18:20:20Z cthedot $'
__version__ = '$Id: selector.py 1868 2009-10-17 19:36:54Z cthedot $'
from cssutils.util import _SimpleNamespaces
from cssutils.helper import Deprecated
import cssutils
import xml.dom
@ -98,13 +99,13 @@ class Selector(cssutils.util.Base2):
;
"""
def __init__(self, selectorText=None, parentList=None,
def __init__(self, selectorText=None, parent=None,
readonly=False):
"""
:Parameters:
selectorText
initial value of this selector
parentList
parent
a SelectorList
readonly
default to False
@ -113,7 +114,7 @@ class Selector(cssutils.util.Base2):
self.__namespaces = _SimpleNamespaces(log=self._log)
self._element = None
self._parent = parentList
self._parent = parent
self._specificity = (0, 0, 0, 0)
if selectorText:
@ -169,10 +170,10 @@ class Selector(cssutils.util.Base2):
element = property(lambda self: self._element,
doc=u"Effective element target of this selector.")
parentList = property(lambda self: self._parent,
parent = 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):
"""Return serialized format."""
return cssutils.ser.do_css_Selector(self)
@ -201,7 +202,7 @@ class Selector(cssutils.util.Base2):
try:
# uses parent stylesheets namespaces if available, otherwise given ones
namespaces = self.parentList.parentRule.parentStyleSheet.namespaces
namespaces = self.parent.parentRule.parentStyleSheet.namespaces
except AttributeError:
pass
tokenizer = self._tokenize2(selectorText)
@ -787,3 +788,11 @@ class Selector(cssutils.util.Base2):
""")
wellformed = property(lambda self: bool(len(self.seq)))
@Deprecated('Use property parent instead')
def _getParentList(self):
return self.parent
parentList = property(_getParentList,
doc="DEPRECATED, see property parent instead")

View File

@ -17,7 +17,7 @@ TODO
"""
__all__ = ['SelectorList']
__docformat__ = 'restructuredtext'
__version__ = '$Id: selectorlist.py 1638 2009-01-13 20:39:33Z cthedot $'
__version__ = '$Id: selectorlist.py 1868 2009-10-17 19:36:54Z cthedot $'
from selector import Selector
import cssutils
@ -73,7 +73,7 @@ class SelectorList(cssutils.util.Base, cssutils.util.ListSeq):
self._checkReadonly()
if not isinstance(newSelector, Selector):
newSelector = Selector((newSelector, namespaces),
parentList=self)
parent=self)
if newSelector.wellformed:
newSelector._parent = self # maybe set twice but must be!
return newSelector
@ -88,6 +88,12 @@ class SelectorList(cssutils.util.Base, cssutils.util.ListSeq):
namespaces.update(selector._namespaces)
return namespaces
def _absorb(self, other):
"""Replace all own data with data from other object."""
self._parentRule = other._parentRule
self.seq[:] = other.seq[:]
self._readonly = other._readonly
def _getUsedUris(self):
"Used by CSSStyleSheet to check if @namespace rules are needed"
uris = set()
@ -191,7 +197,7 @@ class SelectorList(cssutils.util.Base, cssutils.util.ListSeq):
expected = None
selector = Selector((selectortokens, namespaces),
parentList=self)
parent=self)
if selector.wellformed:
newseq.append(selector)
else:
@ -212,8 +218,6 @@ class SelectorList(cssutils.util.Base, cssutils.util.ListSeq):
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

View File

@ -12,7 +12,7 @@ open issues
"""
__all__ = ['CSSProductions', 'MACROS', 'PRODUCTIONS']
__docformat__ = 'restructuredtext'
__version__ = '$Id: cssproductions.py 1835 2009-08-02 16:47:27Z cthedot $'
__version__ = '$Id: cssproductions.py 1855 2009-10-07 17:03:19Z cthedot $'
# a complete list of css3 macros
MACROS = {
@ -41,6 +41,7 @@ MACROS = {
'nl': r'\n|\r\n|\r|\f',
'A': r'A|a|\\0{0,4}(?:41|61)(?:\r\n|[ \t\r\n\f])?',
'B': r'B|b|\\0{0,4}(?:42|62)(?:\r\n|[ \t\r\n\f])?',
'C': r'C|c|\\0{0,4}(?:43|63)(?:\r\n|[ \t\r\n\f])?',
'D': r'D|d|\\0{0,4}(?:44|64)(?:\r\n|[ \t\r\n\f])?',
'E': r'E|e|\\0{0,4}(?:45|65)(?:\r\n|[ \t\r\n\f])?',
@ -58,6 +59,7 @@ MACROS = {
'S': r'S|s|\\0{0,4}(?:53|73)(?:\r\n|[ \t\r\n\f])?|\\S|\\s',
'T': r'T|t|\\0{0,4}(?:54|74)(?:\r\n|[ \t\r\n\f])?|\\T|\\t',
'U': r'U|u|\\0{0,4}(?:55|75)(?:\r\n|[ \t\r\n\f])?|\\U|\\u',
'V': r'V|v|\\0{0,4}(?:56|76)(?:\r\n|[ \t\r\n\f])?|\\V|\\v',
'X': r'X|x|\\0{0,4}(?:58|78)(?:\r\n|[ \t\r\n\f])?|\\X|\\x',
'Z': r'Z|z|\\0{0,4}(?:5a|7a)(?:\r\n|[ \t\r\n\f])?|\\Z|\\z',
}
@ -107,6 +109,7 @@ class CSSProductions(object):
IMPORT_SYM = 'IMPORT_SYM'
NAMESPACE_SYM = 'NAMESPACE_SYM'
PAGE_SYM = 'PAGE_SYM'
VARIABLES_SYM = 'VARIABLES_SYM'
for i, t in enumerate(PRODUCTIONS):
setattr(CSSProductions, t[0].replace('-', '_'), t[0])

View File

@ -60,8 +60,8 @@ def pushtoken(token, tokens):
``tokens``"""
# TODO: may use itertools.chain?
yield token
for x in tokens:
yield x
for t in tokens:
yield t
def string(value):
"""

View File

@ -219,22 +219,31 @@ class Prod(object):
"""Single Prod in Sequence or Choice."""
def __init__(self, name, match, optional=False,
toSeq=None, toStore=None,
stop=False, nextSor=False, mayEnd=False):
stop=False, stopAndKeep=False,
nextSor=False, mayEnd=False):
"""
name
name used for error reporting
match callback
function called with parameters tokentype and tokenvalue
returning True, False or raising ParseError
toSeq callback (optional)
toSeq callback (optional) or False
calling toSeq(token, tokens) returns (type_, val) == (token[0], token[1])
to be appended to seq else simply unaltered (type_, val)
if False nothing is added
toStore (optional)
key to save util.Item to store or callback(store, util.Item)
optional = False
wether Prod is optional or not
stop = False
if True stop parsing of tokens here
stopAndKeep
if True stop parsing of tokens here but return stopping
token in unused tokens
nextSor=False
next is S or other like , or / (CSSValue)
mayEnd = False
no token must follow even defined by Sequence.
Used for operator ',/ ' currently only
@ -243,6 +252,7 @@ class Prod(object):
self.match = match
self.optional = optional
self.stop = stop
self.stopAndKeep = stopAndKeep
self.nextSor = nextSor
self.mayEnd = mayEnd
@ -256,7 +266,7 @@ class Prod(object):
store[key] = item
return toStore
if toSeq:
if toSeq or toSeq is False:
# called: seq.append(toSeq(value))
self.toSeq = toSeq
else:
@ -288,22 +298,27 @@ class Prod(object):
self.__class__.__name__, self._name, id(self))
# global tokenizer as there is only one!
tokenizer = cssutils.tokenize2.Tokenizer()
class ProdParser(object):
"""Productions parser."""
def __init__(self):
def __init__(self, clear=True):
self.types = cssutils.cssproductions.CSSProductions
self._log = cssutils.log
self._tokenizer = cssutils.tokenize2.Tokenizer()
if clear:
tokenizer.clear()
def _texttotokens(self, text):
"""Build a generator which is the only thing that is parsed!
old classes may use lists etc
"""
if isinstance(text, basestring):
# to tokenize strip space
tokens = self._tokenizer.tokenize(text.strip())
# DEFAULT, to tokenize strip space
return tokenizer.tokenize(text.strip())
elif isinstance(text, tuple):
# (token, tokens) or a single token
# OLD: (token, tokens) or a single token
if len(text) == 2:
# (token, tokens)
def gen(token, tokens):
@ -312,19 +327,19 @@ class ProdParser(object):
for t in tokens:
yield t
tokens = (t for t in gen(*text))
return (t for t in gen(*text))
else:
# single token
tokens = (t for t in [text])
return (t for t in [text])
elif isinstance(text, list):
# generator from list
tokens = (t for t in text)
# OLD: generator from list
return (t for t in text)
else:
# already tokenized, assume generator
tokens = text
return tokens
# DEFAULT, already tokenized, assume generator
return text
def _SorTokens(self, tokens, until=',/'):
"""New tokens generator which has S tokens removed,
@ -359,7 +374,7 @@ class ProdParser(object):
return (token for token in removedS(tokens))
def parse(self, text, name, productions, store=None):
def parse(self, text, name, productions, keepS=False, store=None):
"""
text (or token generator)
to parse, will be tokenized if not a generator yet
@ -374,6 +389,8 @@ class ProdParser(object):
used for logging
productions
used to parse tokens
keepS
if WS should be added to Seq or just be ignored
store UPDATED
If a Prod defines ``toStore`` the key defined there
is a key in store to be set or if store[key] is a list
@ -389,7 +406,7 @@ class ProdParser(object):
:store: filled keys defined by Prod.toStore
:unusedtokens: token generator containing tokens not used yet
"""
tokens = self._texttotokens(text)
tokens = self._texttotokens(text)
if not tokens:
self._log.error(u'No content to parse.')
# TODO: return???
@ -411,7 +428,7 @@ class ProdParser(object):
except StopIteration:
break
type_, val, line, col = token
# default productions
if type_ == self.types.COMMENT:
# always append COMMENT
@ -419,10 +436,10 @@ class ProdParser(object):
cssutils.css.CSSComment, line, col)
elif defaultS and type_ == self.types.S:
# append S (but ignore starting ones)
if started:
seq.append(val, type_, line, col)
else:
if not keepS or not started:
continue
else:
seq.append(val, type_, line, col)
# elif type_ == self.types.ATKEYWORD:
# # @rule
# r = cssutils.css.CSSUnknownRule(cssText=val)
@ -465,15 +482,23 @@ class ProdParser(object):
break
else:
# process prod
if prod.toSeq:
if prod.toSeq and not prod.stopAndKeep:
type_, val = prod.toSeq(token, tokens)
if val is not None:
seq.append(val, type_, line, col)
if prod.toStore:
prod.toStore(store, seq[-1])
if val is not None:
seq.append(val, type_, line, col)
if prod.toStore:
prod.toStore(store, seq[-1])
if prod.stop: # EOF?
# stop here and ignore following tokens
break
if prod.stopAndKeep: # e.g. ;
# stop here and ignore following tokens
# but keep this token for next run
tokenizer.push(token)
break
if prod.nextSor:
# following is S or other token (e.g. ",")?
# remove S if
@ -533,12 +558,12 @@ class PreDef(object):
types = cssutils.cssproductions.CSSProductions
@staticmethod
def char(name='char', char=u',', toSeq=None, stop=False,
nextSor=False):
def char(name='char', char=u',', toSeq=None,
stop=False, stopAndKeep=False,
optional=True, nextSor=False):
"any CHAR"
return Prod(name=name, match=lambda t, v: v == char,
toSeq=toSeq,
stop=stop,
return Prod(name=name, match=lambda t, v: v == char, toSeq=toSeq,
stop=stop, stopAndKeep=stopAndKeep, optional=optional,
nextSor=nextSor)
@staticmethod
@ -566,9 +591,10 @@ class PreDef(object):
stop=stop)
@staticmethod
def ident(nextSor=False):
def ident(toStore=None, nextSor=False):
return Prod(name=u'ident',
match=lambda t, v: t == PreDef.types.IDENT,
toStore=toStore,
nextSor=nextSor)
@staticmethod
@ -592,9 +618,11 @@ class PreDef(object):
nextSor=nextSor)
@staticmethod
def S():
def S(toSeq=None, optional=False):
return Prod(name=u'whitespace',
match=lambda t, v: t == PreDef.types.S,
toSeq=toSeq,
optional=optional,
mayEnd=True)
@staticmethod
@ -628,3 +656,11 @@ class PreDef(object):
toSeq=lambda t, tokens: (t[0], t[1].lower()),
nextSor=nextSor
)
@staticmethod
def variable(toSeq=None, nextSor=False):
return Prod(name=u'variable',
match=lambda t, v: u'var(' == cssutils.helper.normalize(v),
toSeq=toSeq,
nextSor=nextSor)

View File

@ -60,7 +60,7 @@ class Profiles(object):
'int': r'[-]?\d+',
'nmchar': r'[\w-]|{nonascii}|{escape}',
'num': r'[-]?\d+|[-]?\d*\.\d+',
'positivenum': r'\d+|[-]?\d*\.\d+',
'positivenum': r'\d+|\d*\.\d+',
'number': r'{num}',
'string': r'{string1}|{string2}',
'string1': r'"(\\\"|[^\"])*"',

View File

@ -3,7 +3,7 @@
"""cssutils serializer"""
__all__ = ['CSSSerializer', 'Preferences']
__docformat__ = 'restructuredtext'
__version__ = '$Id: serialize.py 1741 2009-05-09 18:20:20Z cthedot $'
__version__ = '$Id: serialize.py 1872 2009-10-17 21:00:40Z cthedot $'
import codecs
import cssutils
@ -384,6 +384,32 @@ class CSSSerializer(object):
else:
return u''
def do_CSSVariablesRule(self, rule):
"""
serializes CSSVariablesRule
media
TODO
variables
CSSStyleDeclaration
+ CSSComments
"""
variablesText = rule.variables.cssText
if variablesText and rule.wellformed:
out = Out(self)
out.append(self._atkeyword(rule, u'@variables'))
for item in rule.seq:
# assume comments {
out.append(item.value, item.type)
out.append(u'{')
out.append(u'%s%s}' % (variablesText, self.prefs.lineSeparator),
indent=1)
return out.value()
else:
return u''
def do_CSSFontFaceRule(self, rule):
"""
serializes CSSFontFaceRule
@ -712,11 +738,40 @@ class CSSSerializer(object):
else:
return u''
def do_css_CSSVariablesDeclaration(self, variables):
"""Variables of CSSVariableRule."""
if len(variables.seq) > 0:
out = Out(self)
lastitem = len(variables.seq) - 1
for i, item in enumerate(variables.seq):
type_, val = item.type, item.value
if u'var' == type_:
name, cssvalue = val
out.append(name)
out.append(u':')
out.append(cssvalue.cssText)
if i < lastitem or not self.prefs.omitLastSemicolon:
out.append(u';')
elif isinstance(val, cssutils.css.CSSComment):
# CSSComment
out.append(val, 'COMMENT')
out.append(self.prefs.lineSeparator)
else:
out.append(val.cssText, type_)
out.append(self.prefs.lineSeparator)
return out.value().strip()
else:
return u''
def do_css_CSSStyleDeclaration(self, style, separator=None):
"""
Style declaration of CSSStyleRule
"""
# # TODO: use Out()
# TODO: use Out()
# may be comments only
if len(style.seq) > 0:
@ -867,7 +922,19 @@ class CSSSerializer(object):
out.append(val, type_)
return out.value()
def do_css_CSSVariable(self, variable):
"""Serializes a CSSVariable"""
if not variable:
return u''
else:
out = Out(self)
for item in variable.seq:
type_, val = item.type, item.value
out.append(val, type_)
return out.value()
def do_css_RGBColor(self, cssvalue):
"""Serialize a RGBColor value"""
if not cssvalue:
@ -877,17 +944,6 @@ class CSSSerializer(object):
unary = None
for item in cssvalue.seq:
type_, val = item.type, item.value
# # prepare
# if 'CHAR' == type_ and val in u'+-':
# # save - for next round
# if u'-' == val:
# # omit +
# unary = val
# continue
# elif unary:
# val = unary + val.cssText
# unary = None
out.append(val, type_)

View File

@ -6,7 +6,7 @@ TODO:
"""
__all__ = ['MediaList']
__docformat__ = 'restructuredtext'
__version__ = '$Id: medialist.py 1605 2009-01-03 18:27:32Z cthedot $'
__version__ = '$Id: medialist.py 1871 2009-10-17 19:57:37Z cthedot $'
from cssutils.css import csscomment
from mediaquery import MediaQuery
@ -56,6 +56,13 @@ class MediaList(cssutils.util.Base, cssutils.util.ListSeq):
return "<cssutils.stylesheets.%s object mediaText=%r at 0x%x>" % (
self.__class__.__name__, self.mediaText, id(self))
def _absorb(self, other):
"""Replace all own data with data from other object."""
#self._parentRule = other._parentRule
self.seq[:] = other.seq[:]
self._readonly = other._readonly
length = property(lambda self: len(self),
doc="The number of media in the list (DOM readonly).")

View File

@ -4,10 +4,11 @@
"""
__all__ = ['Tokenizer', 'CSSProductions']
__docformat__ = 'restructuredtext'
__version__ = '$Id: tokenize2.py 1834 2009-08-02 12:20:21Z cthedot $'
__version__ = '$Id: tokenize2.py 1865 2009-10-11 15:23:11Z cthedot $'
from cssproductions import *
from helper import normalize
import itertools
import re
class Tokenizer(object):
@ -20,7 +21,8 @@ class Tokenizer(object):
u'@import': CSSProductions.IMPORT_SYM,
u'@media': CSSProductions.MEDIA_SYM,
u'@namespace': CSSProductions.NAMESPACE_SYM,
u'@page': CSSProductions.PAGE_SYM
u'@page': CSSProductions.PAGE_SYM,
u'@variables': CSSProductions.VARIABLES_SYM
}
_linesep = u'\n'
unicodesub = re.compile(r'\\[0-9a-fA-F]{1,6}(?:\r\n|[\t|\r|\n|\f|\x20])?').sub
@ -40,6 +42,8 @@ class Tokenizer(object):
productions))
self.commentmatcher = [x[1] for x in self.tokenmatches if x[0] == 'COMMENT'][0]
self.urimatcher = [x[1] for x in self.tokenmatches if x[0] == 'URI'][0]
self._pushed = []
def _expand_macros(self, macros, productions):
"""returns macro expanded productions, order of productions is kept"""
@ -60,6 +64,13 @@ class Tokenizer(object):
compiled.append((key, re.compile('^(?:%s)' % value, re.U).match))
return compiled
def push(self, *tokens):
"""Push back tokens which have been pulled but not processed."""
self._pushed = itertools.chain(tokens, self._pushed)
def clear(self):
self._pushed = []
def tokenize(self, text, fullsheet=False):
"""Generator: Tokenize text and yield tokens, each token is a tuple
of::
@ -107,6 +118,11 @@ class Tokenizer(object):
col += len(found)
while text:
for pushed in self._pushed:
# do pushed tokens before new ones
yield pushed
# speed test for most used CHARs
c = text[0]
if c in '{}:;,':

View File

@ -2,7 +2,7 @@
"""
__all__ = []
__docformat__ = 'restructuredtext'
__version__ = '$Id: util.py 1781 2009-07-19 12:30:49Z cthedot $'
__version__ = '$Id: util.py 1872 2009-10-17 21:00:40Z cthedot $'
from helper import normalize
from itertools import ifilter
@ -488,6 +488,27 @@ class Seq(object):
self._seq = []
self._readonly = readonly
def __repr__(self):
"returns a repr same as a list of tuples of (value, type)"
return u'cssutils.%s.%s([\n %s], readonly=%r)' % (self.__module__,
self.__class__.__name__,
u',\n '.join([u'%r' % item for item in self._seq]
), self._readonly)
def __str__(self):
vals = []
for v in self:
if isinstance(v.value, basestring):
vals.append(v.value)
elif type(v) == tuple:
vals.append(v.value[1])
else:
vals.append(str(v))
return "<cssutils.%s.%s object length=%r values=%r readonly=%r at 0x%x>" % (
self.__module__, self.__class__.__name__, len(self),
u', '.join(vals), self._readonly, id(self))
def __delitem__(self, i):
del self._seq[i]
@ -503,8 +524,12 @@ class Seq(object):
def __len__(self):
return len(self._seq)
def absorb(self, other):
"Replace own data with data from other seq"
self._seq = other._seq
def append(self, val, typ, line=None, col=None):
"if not readonly add new Item()"
"If not readonly add new Item()"
if self._readonly:
raise AttributeError('Seq is readonly.')
else:
@ -517,7 +542,7 @@ class Seq(object):
else:
self._seq.append(item)
def replace(self, index= - 1, val=None, typ=None, line=None, col=None):
def replace(self, index=-1, val=None, typ=None, line=None, col=None):
"""
if not readonly replace Item at index with new Item or
simply replace value or type
@ -544,26 +569,6 @@ class Seq(object):
self._seq[index] = Item(old.value + val, old.type,
old.line, old.col)
def __repr__(self):
"returns a repr same as a list of tuples of (value, type)"
return u'cssutils.%s.%s([\n %s], readonly=%r)' % (self.__module__,
self.__class__.__name__,
u',\n '.join([u'%r' % item for item in self._seq]
), self._readonly)
def __str__(self):
vals = []
for v in self:
if isinstance(v.value, basestring):
vals.append(v.value)
elif type(v) == tuple:
vals.append(v.value[1])
else:
vals.append(str(v))
return "<cssutils.%s.%s object length=%r values=%r readonly=%r at 0x%x>" % (
self.__module__, self.__class__.__name__, len(self),
u''.join(vals), self._readonly, id(self))
class Item(object):
"""