mirror of
https://github.com/kovidgoyal/calibre.git
synced 2026-03-23 01:47:53 -04:00
Update bundled cssutils to 0.9.7 for speed improvements in CSS parsing
This commit is contained in:
parent
51e95261e2
commit
6f51ac9b73
@ -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)
|
||||
|
||||
@ -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 *
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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']
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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.")
|
||||
|
||||
@ -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):
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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.")
|
||||
|
||||
@ -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:
|
||||
|
||||
@ -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)
|
||||
|
||||
292
src/cssutils/css/cssvariablesdeclaration.py
Normal file
292
src/cssutils/css/cssvariablesdeclaration.py
Normal 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.")
|
||||
164
src/cssutils/css/cssvariablesrule.py
Normal file
164
src/cssutils/css/cssvariablesrule.py
Normal 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)
|
||||
@ -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")
|
||||
|
||||
|
||||
@ -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")
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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])
|
||||
|
||||
@ -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):
|
||||
"""
|
||||
|
||||
@ -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)
|
||||
|
||||
|
||||
@ -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'"(\\\"|[^\"])*"',
|
||||
|
||||
@ -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_)
|
||||
|
||||
|
||||
@ -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).")
|
||||
|
||||
|
||||
@ -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 '{}:;,':
|
||||
|
||||
@ -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):
|
||||
"""
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user