mirror of
https://github.com/kovidgoyal/calibre.git
synced 2025-07-09 03:04:10 -04:00
IGN:...
This commit is contained in:
parent
308b128089
commit
fc934f1f66
@ -23,3 +23,8 @@ installer/windows/calibre/build.log
|
|||||||
src/calibre/translations/.errors
|
src/calibre/translations/.errors
|
||||||
src/calibre/plugins/*
|
src/calibre/plugins/*
|
||||||
src/calibre/gui2/pictureflow/.build
|
src/calibre/gui2/pictureflow/.build
|
||||||
|
src/cssutils/.svn/
|
||||||
|
src/cssutils/_todo/
|
||||||
|
src/cssutils/scripts/
|
||||||
|
src/cssutils/css/.svn/
|
||||||
|
src/cssutils/stylesheets/.svn/
|
||||||
|
@ -11,6 +11,20 @@ from calibre.utils.config import Config, StringConfig
|
|||||||
from calibre.utils.zipfile import ZipFile, ZIP_STORED
|
from calibre.utils.zipfile import ZipFile, ZIP_STORED
|
||||||
from calibre.ebooks.html import config as common_config
|
from calibre.ebooks.html import config as common_config
|
||||||
|
|
||||||
|
class DefaultProfile(object):
|
||||||
|
|
||||||
|
flow_size = sys.maxint
|
||||||
|
|
||||||
|
class PRS505(DefaultProfile):
|
||||||
|
|
||||||
|
flow_size = 300000
|
||||||
|
|
||||||
|
|
||||||
|
PROFILES = {
|
||||||
|
'PRS505' : PRS505,
|
||||||
|
'None' : DefaultProfile,
|
||||||
|
}
|
||||||
|
|
||||||
def initialize_container(path_to_container, opf_name='metadata.opf'):
|
def initialize_container(path_to_container, opf_name='metadata.opf'):
|
||||||
'''
|
'''
|
||||||
Create an empty EPUB document, with a default skeleton.
|
Create an empty EPUB document, with a default skeleton.
|
||||||
@ -43,7 +57,8 @@ def config(defaults=None):
|
|||||||
|
|
||||||
c.add_opt('output', ['-o', '--output'], default=None,
|
c.add_opt('output', ['-o', '--output'], default=None,
|
||||||
help=_('The output EPUB file. If not specified, it is derived from the input file name.'))
|
help=_('The output EPUB file. If not specified, it is derived from the input file name.'))
|
||||||
|
c.add_opt('profile', ['--profile'], default='PRS505', choices=list(PROFILES.keys()),
|
||||||
|
help=_('Profile of the target device this EPUB is meant for. Set to None to create a device independent EPUB. The profile is used for device specific restrictions on the EPUB. Choices are: ')+str(list(PROFILES.keys())))
|
||||||
|
|
||||||
structure = c.add_group('structure detection', _('Control auto-detection of document structure.'))
|
structure = c.add_group('structure detection', _('Control auto-detection of document structure.'))
|
||||||
structure('chapter', ['--chapter'], default="//*[re:match(name(), 'h[1-2]') and re:test(., 'chapter|book|section', 'i')]",
|
structure('chapter', ['--chapter'], default="//*[re:match(name(), 'h[1-2]') and re:test(., 'chapter|book|section', 'i')]",
|
||||||
|
@ -17,7 +17,7 @@ from calibre.ebooks.epub import config as common_config
|
|||||||
from calibre.ptempfile import TemporaryDirectory
|
from calibre.ptempfile import TemporaryDirectory
|
||||||
from calibre.ebooks.metadata import MetaInformation
|
from calibre.ebooks.metadata import MetaInformation
|
||||||
from calibre.ebooks.metadata.toc import TOC
|
from calibre.ebooks.metadata.toc import TOC
|
||||||
from calibre.ebooks.epub import initialize_container
|
from calibre.ebooks.epub import initialize_container, PROFILES
|
||||||
|
|
||||||
|
|
||||||
class HTMLProcessor(Processor):
|
class HTMLProcessor(Processor):
|
||||||
@ -38,6 +38,13 @@ class HTMLProcessor(Processor):
|
|||||||
|
|
||||||
self.split()
|
self.split()
|
||||||
|
|
||||||
|
def save(self):
|
||||||
|
file = Processor.save(self)
|
||||||
|
with open(file, 'rb') as f:
|
||||||
|
f.seek(0, 2)
|
||||||
|
size = f.tell()
|
||||||
|
if size > self.opts.profile.flow_size:
|
||||||
|
self.split()
|
||||||
|
|
||||||
|
|
||||||
def collect_font_statistics(self):
|
def collect_font_statistics(self):
|
||||||
@ -87,6 +94,7 @@ def convert(htmlfile, opts, notification=None):
|
|||||||
htmlfile = os.path.abspath(htmlfile)
|
htmlfile = os.path.abspath(htmlfile)
|
||||||
if opts.output is None:
|
if opts.output is None:
|
||||||
opts.output = os.path.splitext(os.path.basename(htmlfile))[0] + '.epub'
|
opts.output = os.path.splitext(os.path.basename(htmlfile))[0] + '.epub'
|
||||||
|
opts.profile = PROFILES[opts.profile]
|
||||||
opts.output = os.path.abspath(opts.output)
|
opts.output = os.path.abspath(opts.output)
|
||||||
if htmlfile.lower().endswith('.opf'):
|
if htmlfile.lower().endswith('.opf'):
|
||||||
opf = OPFReader(htmlfile, os.path.dirname(os.path.abspath(htmlfile)))
|
opf = OPFReader(htmlfile, os.path.dirname(os.path.abspath(htmlfile)))
|
||||||
|
@ -438,7 +438,7 @@ class Processor(Parser):
|
|||||||
style = etree.SubElement(head, 'style', attrib={'type':'text/css'})
|
style = etree.SubElement(head, 'style', attrib={'type':'text/css'})
|
||||||
style.text='\n'+self.css
|
style.text='\n'+self.css
|
||||||
style.tail = '\n\n'
|
style.tail = '\n\n'
|
||||||
Parser.save(self)
|
return Parser.save(self)
|
||||||
|
|
||||||
def populate_toc(self, toc):
|
def populate_toc(self, toc):
|
||||||
if self.level >= self.opts.max_toc_recursion:
|
if self.level >= self.opts.max_toc_recursion:
|
||||||
|
@ -1,4 +0,0 @@
|
|||||||
from csscombine import csscombine
|
|
||||||
__all__ = ["csscapture", "csscombine", "cssparse"]
|
|
||||||
|
|
||||||
|
|
@ -1,69 +0,0 @@
|
|||||||
#!/usr/bin/env python
|
|
||||||
"""Retrieve all CSS stylesheets including embedded for a given URL.
|
|
||||||
Retrieve as StyleSheetList or save to disk - raw, parsed or minified version.
|
|
||||||
|
|
||||||
TODO:
|
|
||||||
- maybe use DOM 3 load/save?
|
|
||||||
- logger class which handles all cases when no log is given...
|
|
||||||
- saveto: why does urllib2 hang?
|
|
||||||
"""
|
|
||||||
__all__ = ['CSSCapture']
|
|
||||||
__docformat__ = 'restructuredtext'
|
|
||||||
__version__ = '$Id: csscapture.py 1332 2008-07-09 13:12:56Z cthedot $'
|
|
||||||
|
|
||||||
import logging
|
|
||||||
import optparse
|
|
||||||
import sys
|
|
||||||
from cssutils.script import CSSCapture
|
|
||||||
|
|
||||||
def main(args=None):
|
|
||||||
usage = "usage: %prog [options] URL"
|
|
||||||
parser = optparse.OptionParser(usage=usage)
|
|
||||||
parser.add_option('-d', '--debug', action='store_true', dest='debug',
|
|
||||||
help='show debug messages during capturing')
|
|
||||||
parser.add_option('-m', '--minified', action='store_true', dest='minified',
|
|
||||||
help='saves minified version of captured files')
|
|
||||||
parser.add_option('-n', '--notsave', action='store_true', dest='notsave',
|
|
||||||
help='if given files are NOT saved, only log is written')
|
|
||||||
# parser.add_option('-r', '--saveraw', action='store_true', dest='saveraw',
|
|
||||||
# help='if given saves raw css otherwise cssutils\' parsed files')
|
|
||||||
parser.add_option('-s', '--saveto', action='store', dest='saveto',
|
|
||||||
help='saving retrieved files to "saveto", defaults to "_CSSCapture_SAVED"')
|
|
||||||
parser.add_option('-u', '--useragent', action='store', dest='ua',
|
|
||||||
help='useragent to use for request of URL, default is urllib2s default')
|
|
||||||
options, url = parser.parse_args()
|
|
||||||
|
|
||||||
# TODO:
|
|
||||||
options.saveraw = False
|
|
||||||
|
|
||||||
if not url:
|
|
||||||
parser.error('no URL given')
|
|
||||||
else:
|
|
||||||
url = url[0]
|
|
||||||
|
|
||||||
if options.debug:
|
|
||||||
level = logging.DEBUG
|
|
||||||
else:
|
|
||||||
level = logging.INFO
|
|
||||||
|
|
||||||
# START
|
|
||||||
c = CSSCapture(ua=options.ua, defaultloglevel=level)
|
|
||||||
|
|
||||||
stylesheetlist = c.capture(url)
|
|
||||||
|
|
||||||
if options.notsave is None or not options.notsave:
|
|
||||||
if options.saveto:
|
|
||||||
saveto = options.saveto
|
|
||||||
else:
|
|
||||||
saveto = u'_CSSCapture_SAVED'
|
|
||||||
c.saveto(saveto, saveraw=options.saveraw, minified=options.minified)
|
|
||||||
else:
|
|
||||||
for i, s in enumerate(stylesheetlist):
|
|
||||||
print u'''%s.
|
|
||||||
encoding: %r
|
|
||||||
title: %r
|
|
||||||
href: %r''' % (i + 1, s.encoding, s.title, s.href)
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
sys.exit(main())
|
|
@ -1,90 +0,0 @@
|
|||||||
#!/usr/bin/env python
|
|
||||||
"""Combine sheets referred to by @import rules in a given CSS proxy sheet
|
|
||||||
into a single new sheet.
|
|
||||||
|
|
||||||
- proxy currently is a path (no URI!)
|
|
||||||
- in @import rules only relative paths do work for now but should be used
|
|
||||||
anyway
|
|
||||||
- currently no nested @imports are resolved
|
|
||||||
- messages are send to stderr
|
|
||||||
- output to stdout.
|
|
||||||
|
|
||||||
Example::
|
|
||||||
|
|
||||||
csscombine sheets\csscombine-proxy.css -m -t ascii -s utf-8
|
|
||||||
1>combined.css 2>log.txt
|
|
||||||
|
|
||||||
results in log.txt::
|
|
||||||
|
|
||||||
COMBINING sheets/csscombine-proxy.css
|
|
||||||
USING SOURCE ENCODING: css
|
|
||||||
* PROCESSING @import sheets\csscombine-1.css
|
|
||||||
* PROCESSING @import sheets\csscombine-2.css
|
|
||||||
INFO Nested @imports are not combined: @import "1.css";
|
|
||||||
SETTING TARGET ENCODING: ascii
|
|
||||||
|
|
||||||
and combined.css::
|
|
||||||
|
|
||||||
@charset "ascii";@import"1.css";@namespaces2"uri";s2|sheet-1{top:1px}s2|sheet-2{top:2px}proxy{top:3px}
|
|
||||||
|
|
||||||
or without option -m::
|
|
||||||
|
|
||||||
@charset "ascii";
|
|
||||||
@import "1.css";
|
|
||||||
@namespace s2 "uri";
|
|
||||||
@namespace other "other";
|
|
||||||
/* proxy sheet were imported sheets should be combined */
|
|
||||||
/* non-ascii chars: \F6 \E4 \FC */
|
|
||||||
/* @import "csscombine-1.css"; */
|
|
||||||
/* combined sheet 1 */
|
|
||||||
s2|sheet-1 {
|
|
||||||
top: 1px
|
|
||||||
}
|
|
||||||
/* @import url(csscombine-2.css); */
|
|
||||||
/* combined sheet 2 */
|
|
||||||
s2|sheet-2 {
|
|
||||||
top: 2px
|
|
||||||
}
|
|
||||||
proxy {
|
|
||||||
top: 3px
|
|
||||||
}
|
|
||||||
|
|
||||||
TODO
|
|
||||||
- URL or file hrefs? URI should be default
|
|
||||||
- no nested @imports are resolved yet
|
|
||||||
- maybe add a config file which is used?
|
|
||||||
|
|
||||||
"""
|
|
||||||
__all__ = ['csscombine']
|
|
||||||
__docformat__ = 'restructuredtext'
|
|
||||||
__version__ = '$Id: csscombine.py 1332 2008-07-09 13:12:56Z cthedot $'
|
|
||||||
|
|
||||||
import optparse
|
|
||||||
import sys
|
|
||||||
from cssutils.script import csscombine
|
|
||||||
|
|
||||||
def main(args=None):
|
|
||||||
usage = "usage: %prog [options] path"
|
|
||||||
parser = optparse.OptionParser(usage=usage)
|
|
||||||
parser.add_option('-s', '--sourceencoding', action='store',
|
|
||||||
dest='sourceencoding',
|
|
||||||
help='encoding of input, defaulting to "css". If given overwrites other encoding information like @charset declarations')
|
|
||||||
parser.add_option('-t', '--targetencoding', action='store',
|
|
||||||
dest='targetencoding',
|
|
||||||
help='encoding of output, defaulting to "UTF-8"', default='utf-8')
|
|
||||||
parser.add_option('-m', '--minify', action='store_true', dest='minify',
|
|
||||||
default=False,
|
|
||||||
help='saves minified version of combined files, defaults to False')
|
|
||||||
options, path = parser.parse_args()
|
|
||||||
|
|
||||||
if not path:
|
|
||||||
parser.error('no path given')
|
|
||||||
else:
|
|
||||||
path = path[0]
|
|
||||||
|
|
||||||
print csscombine(path, options.sourceencoding, options.targetencoding,
|
|
||||||
options.minify)
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
sys.exit(main())
|
|
@ -1,59 +0,0 @@
|
|||||||
#!/usr/bin/env python
|
|
||||||
"""utility script to parse given filenames or string
|
|
||||||
"""
|
|
||||||
__docformat__ = 'restructuredtext'
|
|
||||||
__version__ = '$Id: cssparse.py 1327 2008-07-08 21:17:12Z cthedot $'
|
|
||||||
|
|
||||||
import cssutils
|
|
||||||
import logging
|
|
||||||
import optparse
|
|
||||||
import sys
|
|
||||||
|
|
||||||
def main(args=None):
|
|
||||||
"""
|
|
||||||
Parses given filename(s) or string (using optional encoding) and prints
|
|
||||||
the parsed style sheet to stdout.
|
|
||||||
|
|
||||||
Redirect stdout to save CSS. Redirect stderr to save parser log infos.
|
|
||||||
"""
|
|
||||||
usage = """usage: %prog [options] filename1.css [filename2.css ...]
|
|
||||||
[>filename_combined.css] [2>parserinfo.log] """
|
|
||||||
p = optparse.OptionParser(usage=usage)
|
|
||||||
p.add_option('-e', '--encoding', action='store', dest='encoding',
|
|
||||||
help='encoding of the file')
|
|
||||||
p.add_option('-d', '--debug', action='store_true', dest='debug',
|
|
||||||
help='activate debugging output')
|
|
||||||
p.add_option('-m', '--minify', action='store_true', dest='minify',
|
|
||||||
help='minify parsed CSS', default=False)
|
|
||||||
p.add_option('-s', '--string', action='store_true', dest='string',
|
|
||||||
help='parse given string')
|
|
||||||
|
|
||||||
(options, params) = p.parse_args(args)
|
|
||||||
|
|
||||||
if not params:
|
|
||||||
p.error("no filename given")
|
|
||||||
|
|
||||||
if options.debug:
|
|
||||||
p = cssutils.CSSParser(loglevel=logging.DEBUG)
|
|
||||||
else:
|
|
||||||
p = cssutils.CSSParser()
|
|
||||||
|
|
||||||
if options.minify:
|
|
||||||
cssutils.ser.prefs.useMinified()
|
|
||||||
|
|
||||||
if options.string:
|
|
||||||
sheet = p.parseString(u''.join(params), encoding=options.encoding)
|
|
||||||
print sheet.cssText
|
|
||||||
print
|
|
||||||
sys.stderr.write('\n')
|
|
||||||
else:
|
|
||||||
for filename in params:
|
|
||||||
sys.stderr.write('=== CSS FILE: "%s" ===\n' % filename)
|
|
||||||
sheet = p.parseFile(filename, encoding=options.encoding)
|
|
||||||
print sheet.cssText
|
|
||||||
print
|
|
||||||
sys.stderr.write('\n')
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
sys.exit(main())
|
|
@ -70,7 +70,7 @@ Usage may be::
|
|||||||
__all__ = ['css', 'stylesheets', 'CSSParser', 'CSSSerializer']
|
__all__ = ['css', 'stylesheets', 'CSSParser', 'CSSSerializer']
|
||||||
__docformat__ = 'restructuredtext'
|
__docformat__ = 'restructuredtext'
|
||||||
__author__ = 'Christof Hoeke with contributions by Walter Doerwald'
|
__author__ = 'Christof Hoeke with contributions by Walter Doerwald'
|
||||||
__date__ = '$LastChangedDate:: 2008-08-11 20:11:23 +0200 #$:'
|
__date__ = '$LastChangedDate:: 2008-08-11 11:11:23 -0700 #$:'
|
||||||
|
|
||||||
VERSION = '0.9.5.1'
|
VERSION = '0.9.5.1'
|
||||||
|
|
@ -61,7 +61,7 @@ simply called like functions.)
|
|||||||
"""
|
"""
|
||||||
__all__ = ['CSS2Properties', 'cssvalues']
|
__all__ = ['CSS2Properties', 'cssvalues']
|
||||||
__docformat__ = 'restructuredtext'
|
__docformat__ = 'restructuredtext'
|
||||||
__version__ = '$Id: cssproperties.py 1116 2008-03-05 13:52:23Z cthedot $'
|
__version__ = '$Id: cssproperties.py 1469 2008-09-15 19:06:00Z cthedot $'
|
||||||
|
|
||||||
import re
|
import re
|
||||||
|
|
||||||
@ -168,7 +168,7 @@ cssvalues = {
|
|||||||
'border-top-width': r'{border-width}|inherit',
|
'border-top-width': r'{border-width}|inherit',
|
||||||
'border-right-width': r'{border-width}|inherit',
|
'border-right-width': r'{border-width}|inherit',
|
||||||
'border-bottom-width': r'{border-width}|inherit',
|
'border-bottom-width': r'{border-width}|inherit',
|
||||||
'border-right-width': r'{border-width}|inherit',
|
'border-left-width': r'{border-width}|inherit',
|
||||||
'border-width': r'{border-width}(\s+{border-width}){0,3}|inherit',
|
'border-width': r'{border-width}(\s+{border-width}){0,3}|inherit',
|
||||||
'border': r'{border-attrs}(\s+{border-attrs})*|inherit',
|
'border': r'{border-attrs}(\s+{border-attrs})*|inherit',
|
||||||
'bottom': r'{length}|{percentage}|auto|inherit',
|
'bottom': r'{length}|{percentage}|auto|inherit',
|
@ -5,16 +5,18 @@
|
|||||||
- CSSValueList implements DOM Level 2 CSS CSSValueList
|
- CSSValueList implements DOM Level 2 CSS CSSValueList
|
||||||
|
|
||||||
"""
|
"""
|
||||||
__all__ = ['CSSValue', 'CSSPrimitiveValue', 'CSSValueList']
|
__all__ = ['CSSValue', 'CSSPrimitiveValue', 'CSSValueList', 'CSSColor']
|
||||||
__docformat__ = 'restructuredtext'
|
__docformat__ = 'restructuredtext'
|
||||||
__version__ = '$Id: cssvalue.py 1228 2008-05-19 19:59:50Z cthedot $'
|
__version__ = '$Id: cssvalue.py 1473 2008-09-15 21:15:54Z cthedot $'
|
||||||
|
|
||||||
import re
|
import re
|
||||||
import xml.dom
|
import xml.dom
|
||||||
import cssutils
|
import cssutils
|
||||||
import cssproperties
|
from cssutils.profiles import profiles
|
||||||
|
from cssutils.prodparser import *
|
||||||
|
|
||||||
class CSSValue(cssutils.util.Base):
|
|
||||||
|
class CSSValue(cssutils.util.Base2):
|
||||||
"""
|
"""
|
||||||
The CSSValue interface represents a simple or a complex value.
|
The CSSValue interface represents a simple or a complex value.
|
||||||
A CSSValue object only occurs in a context of a CSS property
|
A CSSValue object only occurs in a context of a CSS property
|
||||||
@ -73,7 +75,7 @@ class CSSValue(cssutils.util.Base):
|
|||||||
"""
|
"""
|
||||||
super(CSSValue, self).__init__()
|
super(CSSValue, self).__init__()
|
||||||
|
|
||||||
self.seq = []
|
#self.seq = []
|
||||||
self.valid = False
|
self.valid = False
|
||||||
self.wellformed = False
|
self.wellformed = False
|
||||||
self._valueValue = u''
|
self._valueValue = u''
|
||||||
@ -88,14 +90,26 @@ class CSSValue(cssutils.util.Base):
|
|||||||
self._readonly = readonly
|
self._readonly = readonly
|
||||||
|
|
||||||
def _getValue(self):
|
def _getValue(self):
|
||||||
|
# TODO:
|
||||||
v = []
|
v = []
|
||||||
for x in self.seq:
|
for item in self.seq:
|
||||||
if isinstance(x, cssutils.css.CSSComment):
|
type_, val = item.type, item.value
|
||||||
|
if isinstance(val, cssutils.css.CSSComment):
|
||||||
|
# only value here
|
||||||
continue
|
continue
|
||||||
elif isinstance(x, basestring):
|
elif 'STRING' == type_:
|
||||||
v.append(x)
|
v.append(cssutils.ser._string(val))
|
||||||
else: # maybe CSSPrimitiveValue
|
elif 'URI' == type_:
|
||||||
v.append(x.cssText)
|
v.append(cssutils.ser._uri(val))
|
||||||
|
elif u',' == val:
|
||||||
|
# list of items
|
||||||
|
v.append(u' ')
|
||||||
|
v.append(val)
|
||||||
|
elif isinstance(val, basestring):
|
||||||
|
v.append(val)
|
||||||
|
else:
|
||||||
|
# maybe CSSPrimitiveValue
|
||||||
|
v.append(val.cssText)
|
||||||
if v and u'' == v[-1].strip():
|
if v and u'' == v[-1].strip():
|
||||||
# simple strip of joined string does not work for escaped spaces
|
# simple strip of joined string does not work for escaped spaces
|
||||||
del v[-1]
|
del v[-1]
|
||||||
@ -158,15 +172,17 @@ class CSSValue(cssutils.util.Base):
|
|||||||
self._checkReadonly()
|
self._checkReadonly()
|
||||||
|
|
||||||
# for closures: must be a mutable
|
# for closures: must be a mutable
|
||||||
new = {'values': [],
|
new = {'rawvalues': [], # used for validation
|
||||||
|
'values': [],
|
||||||
'commas': 0,
|
'commas': 0,
|
||||||
'valid': True,
|
'valid': True,
|
||||||
'wellformed': True }
|
'wellformed': True }
|
||||||
|
|
||||||
def _S(expected, seq, token, tokenizer=None):
|
def _S(expected, seq, token, tokenizer=None):
|
||||||
val = self._tokenvalue(token)
|
type_, val, line, col = token
|
||||||
if expected.endswith('operator'):
|
new['rawvalues'].append(u' ')
|
||||||
seq.append(u' ')
|
if expected == 'operator': #expected.endswith('operator'):
|
||||||
|
seq.append(u' ', 'separator', line=line, col=col)
|
||||||
return 'term or operator'
|
return 'term or operator'
|
||||||
elif expected.endswith('S'):
|
elif expected.endswith('S'):
|
||||||
return 'term or S'
|
return 'term or S'
|
||||||
@ -174,60 +190,69 @@ class CSSValue(cssutils.util.Base):
|
|||||||
return expected
|
return expected
|
||||||
|
|
||||||
def _char(expected, seq, token, tokenizer=None):
|
def _char(expected, seq, token, tokenizer=None):
|
||||||
val = self._tokenvalue(token)
|
type_, val, line, col = token
|
||||||
|
new['rawvalues'].append(val)
|
||||||
|
|
||||||
if 'funcend' == expected and u')' == val:
|
if 'funcend' == expected and u')' == val:
|
||||||
# end of FUNCTION
|
# end of FUNCTION
|
||||||
seq[-1] += val
|
seq.appendToVal(val)
|
||||||
new['values'].append(seq[-1])
|
new['values'].append(seq[-1])
|
||||||
return 'operator'
|
return 'operator'
|
||||||
|
|
||||||
elif expected in (')', ']', '}') and expected == val:
|
elif expected in (')', ']', '}') and expected == val:
|
||||||
# end of any block: (), [], {}
|
# end of any block: (), [], {}
|
||||||
seq[-1] += val
|
seq.appendToVal(val)
|
||||||
return 'operator'
|
return 'operator'
|
||||||
|
|
||||||
elif expected in ('funcend', ')', ']', '}'):
|
elif expected in ('funcend', ')', ']', '}'):
|
||||||
# content of func or block: (), [], {}
|
# content of func or block: (), [], {}
|
||||||
seq[-1] += val
|
seq.appendToVal(val)
|
||||||
return expected
|
return expected
|
||||||
|
|
||||||
elif expected.endswith('operator') and ',' == val:
|
elif expected.endswith('operator') and ',' == val:
|
||||||
# term , term
|
# term, term; remove all WS between terms!!!
|
||||||
new['commas'] += 1
|
new['commas'] += 1
|
||||||
if seq and seq[-1] == u' ':
|
if seq and seq[-1].type == 'separator':
|
||||||
seq[-1] = val
|
seq.replace(-1, val, type_, line=line, col=col)
|
||||||
else:
|
else:
|
||||||
seq.append(val)
|
seq.append(val, type_, line=line, col=col)
|
||||||
return 'term or S'
|
return 'term or S'
|
||||||
|
|
||||||
elif expected.endswith('operator') and '/' == val:
|
elif expected.endswith('operator') and '/' == val:
|
||||||
# term / term
|
# term / term
|
||||||
if seq and seq[-1] == u' ':
|
if seq and seq[-1].value == u' ':
|
||||||
seq[-1] = val
|
old = seq[-1]
|
||||||
|
seq.replace(-1, val, old.type, old.line, old.col)
|
||||||
|
#seq[-1] = val
|
||||||
else:
|
else:
|
||||||
seq.append(val)
|
seq.append(val, type_, line=line, col=col)
|
||||||
return 'term or S'
|
return 'term or S'
|
||||||
|
|
||||||
elif expected.startswith('term') and u'(' == val:
|
elif expected.startswith('term') and u'(' == val:
|
||||||
# start of ( any* ) block
|
# start of ( any* ) block
|
||||||
seq.append(val)
|
seq.append(val, type_, line=line, col=col)
|
||||||
return ')'
|
return ')'
|
||||||
elif expected.startswith('term') and u'[' == val:
|
elif expected.startswith('term') and u'[' == val:
|
||||||
# start of [ any* ] block
|
# start of [ any* ] block
|
||||||
seq.append(val)
|
seq.append(val, type_, line=line, col=col)
|
||||||
return ']'
|
return ']'
|
||||||
elif expected.startswith('term') and u'{' == val:
|
elif expected.startswith('term') and u'{' == val:
|
||||||
# start of { any* } block
|
# start of { any* } block
|
||||||
seq.append(val)
|
seq.append(val, type_, line=line, col=col)
|
||||||
return '}'
|
return '}'
|
||||||
elif expected.startswith('term') and u'-' == val or u'+' == 'val':
|
elif expected.startswith('term') and u'+' == val:
|
||||||
# unary operator
|
# unary operator "+"
|
||||||
seq.append(val)
|
seq.append(val, type_, line=line, col=col)
|
||||||
|
new['values'].append(val)
|
||||||
|
return 'number percentage dimension'
|
||||||
|
elif expected.startswith('term') and u'-' == val:
|
||||||
|
# unary "-" operator
|
||||||
|
seq.append(val, type_, line=line, col=col)
|
||||||
new['values'].append(val)
|
new['values'].append(val)
|
||||||
return 'number percentage dimension'
|
return 'number percentage dimension'
|
||||||
elif expected.startswith('term') and u'/' == val:
|
elif expected.startswith('term') and u'/' == val:
|
||||||
# font-size/line-height separator
|
# font-size/line-height separator
|
||||||
seq.append(val)
|
seq.append(val, type_, line=line, col=col)
|
||||||
new['values'].append(val)
|
new['values'].append(val)
|
||||||
return 'number percentage dimension'
|
return 'number percentage dimension'
|
||||||
else:
|
else:
|
||||||
@ -237,71 +262,91 @@ class CSSValue(cssutils.util.Base):
|
|||||||
|
|
||||||
def _number_percentage_dimension(expected, seq, token, tokenizer=None):
|
def _number_percentage_dimension(expected, seq, token, tokenizer=None):
|
||||||
# NUMBER PERCENTAGE DIMENSION after -/+ or operator
|
# NUMBER PERCENTAGE DIMENSION after -/+ or operator
|
||||||
|
type_, val, line, col = token
|
||||||
|
new['rawvalues'].append(val)
|
||||||
if expected.startswith('term') or expected == 'number percentage dimension':
|
if expected.startswith('term') or expected == 'number percentage dimension':
|
||||||
# normal value
|
# normal value
|
||||||
val = self._tokenvalue(token)
|
|
||||||
if new['values'] and new['values'][-1] in (u'-', u'+'):
|
if new['values'] and new['values'][-1] in (u'-', u'+'):
|
||||||
new['values'][-1] += val
|
new['values'][-1] += val
|
||||||
else:
|
else:
|
||||||
new['values'].append(val)
|
new['values'].append(val)
|
||||||
seq.append(val)
|
seq.append(val, type_, line=line, col=col)
|
||||||
return 'operator'
|
return 'operator'
|
||||||
elif 'operator' == expected:
|
elif 'operator' == expected:
|
||||||
# expected S but token which is ok
|
# expected S but token which is ok
|
||||||
val = self._tokenvalue(token)
|
|
||||||
if new['values'] and new['values'][-1] in (u'-', u'+'):
|
if new['values'] and new['values'][-1] in (u'-', u'+'):
|
||||||
new['values'][-1] += val
|
new['values'][-1] += val
|
||||||
else:
|
else:
|
||||||
new['values'].append(u' ')
|
new['values'].append(u' ')
|
||||||
seq.append(u' ')
|
seq.append(u' ', 'separator') # self._prods.S
|
||||||
new['values'].append(val)
|
new['values'].append(val)
|
||||||
seq.append(val)
|
seq.append(val, type_, line=line, col=col)
|
||||||
return 'operator'
|
return 'operator'
|
||||||
elif expected in ('funcend', ')', ']', '}'):
|
elif expected in ('funcend', ')', ']', '}'):
|
||||||
# a block
|
# a block
|
||||||
seq[-1] += self._tokenvalue(token)
|
seq.appendToVal(val)
|
||||||
return expected
|
return expected
|
||||||
else:
|
else:
|
||||||
new['wellformed'] = False
|
new['wellformed'] = False
|
||||||
self._log.error(u'CSSValue: Unexpected token.', token)
|
self._log.error(u'CSSValue: Unexpected token.', token)
|
||||||
return expected
|
return expected
|
||||||
|
|
||||||
def _string_ident_uri_hexcolor(expected, seq, token, tokenizer=None):
|
def _string_ident_uri(expected, seq, token, tokenizer=None):
|
||||||
# STRING IDENT URI HASH
|
# STRING IDENT URI
|
||||||
|
type_, val, line, col = token
|
||||||
|
|
||||||
|
new['rawvalues'].append(val)
|
||||||
if expected.startswith('term'):
|
if expected.startswith('term'):
|
||||||
# normal value
|
# normal value
|
||||||
|
if self._prods.STRING == type_:
|
||||||
# TODO: use actual values, probably needs Base2 for this
|
val = self._stringtokenvalue(token)
|
||||||
typ = self._type(token)
|
elif self._prods.URI == type_:
|
||||||
if self._prods.STRING == typ:
|
val = self._uritokenvalue(token)
|
||||||
val = u'"%s"' % self._stringtokenvalue(token)
|
|
||||||
# elif 'URI' == typ:
|
|
||||||
# val = u'url(%s)' % self._uritokenvalue(token)
|
|
||||||
else:
|
|
||||||
val = self._tokenvalue(token)
|
|
||||||
|
|
||||||
new['values'].append(val)
|
new['values'].append(val)
|
||||||
seq.append(val)
|
seq.append(val, type_, line=line, col=col)
|
||||||
return 'operator'
|
return 'operator'
|
||||||
elif 'operator' == expected:
|
elif 'operator' == expected:
|
||||||
# expected S but still ok
|
# expected S but still ok
|
||||||
|
if self._prods.STRING == type_:
|
||||||
# TODO: use actual values, probably needs Base2 for this
|
val = self._stringtokenvalue(token)
|
||||||
typ = self._type(token)
|
elif self._prods.URI == type_:
|
||||||
if self._prods.STRING == typ:
|
val = self._uritokenvalue(token)
|
||||||
val = u'"%s"' % self._stringtokenvalue(token)
|
|
||||||
# elif 'URI' == typ:
|
|
||||||
# val = u'url(%s)' % self._uritokenvalue(token)
|
|
||||||
else:
|
|
||||||
val = self._tokenvalue(token)
|
|
||||||
new['values'].append(u' ')
|
new['values'].append(u' ')
|
||||||
new['values'].append(val)
|
new['values'].append(val)
|
||||||
seq.append(u' ')
|
seq.append(u' ', 'separator') # self._prods.S
|
||||||
seq.append(val)
|
seq.append(val, type_, line=line, col=col)
|
||||||
return 'operator'
|
return 'operator'
|
||||||
elif expected in ('funcend', ')', ']', '}'):
|
elif expected in ('funcend', ')', ']', '}'):
|
||||||
# a block
|
# a block
|
||||||
seq[-1] += self._tokenvalue(token)
|
seq.appendToVal(val)
|
||||||
|
return expected
|
||||||
|
else:
|
||||||
|
new['wellformed'] = False
|
||||||
|
self._log.error(u'CSSValue: Unexpected token.', token)
|
||||||
|
return expected
|
||||||
|
|
||||||
|
def _hash(expected, seq, token, tokenizer=None):
|
||||||
|
# HASH
|
||||||
|
type_, val, line, col = token
|
||||||
|
new['rawvalues'].append(val)
|
||||||
|
|
||||||
|
val = CSSColor(cssText=token)
|
||||||
|
type_ = type(val)
|
||||||
|
if expected.startswith('term'):
|
||||||
|
# normal value
|
||||||
|
new['values'].append(val)
|
||||||
|
seq.append(val, type_, line=line, col=col)
|
||||||
|
return 'operator'
|
||||||
|
elif 'operator' == expected:
|
||||||
|
# expected S but still ok
|
||||||
|
new['values'].append(u' ')
|
||||||
|
new['values'].append(val)
|
||||||
|
seq.append(u' ', 'separator') # self._prods.S
|
||||||
|
seq.append(val, type_, line=line, col=col)
|
||||||
|
return 'operator'
|
||||||
|
elif expected in ('funcend', ')', ']', '}'):
|
||||||
|
# a block
|
||||||
|
seq.appendToVal(val)
|
||||||
return expected
|
return expected
|
||||||
else:
|
else:
|
||||||
new['wellformed'] = False
|
new['wellformed'] = False
|
||||||
@ -309,19 +354,32 @@ class CSSValue(cssutils.util.Base):
|
|||||||
return expected
|
return expected
|
||||||
|
|
||||||
def _function(expected, seq, token, tokenizer=None):
|
def _function(expected, seq, token, tokenizer=None):
|
||||||
# FUNCTION
|
# FUNCTION incl colors
|
||||||
|
type_, val, line, col = token
|
||||||
|
|
||||||
|
if self._normalize(val) in ('rgb(', 'rgba(', 'hsl(', 'hsla('):
|
||||||
|
# a CSSColor
|
||||||
|
val = CSSColor(cssText=(token, tokenizer))
|
||||||
|
type_ = type(val)
|
||||||
|
seq.append(val, type_, line=line, col=col)
|
||||||
|
new['values'].append(val)
|
||||||
|
new['rawvalues'].append(val.cssText)
|
||||||
|
return 'operator'
|
||||||
|
|
||||||
|
new['rawvalues'].append(val)
|
||||||
|
|
||||||
if expected.startswith('term'):
|
if expected.startswith('term'):
|
||||||
# normal value but add if funcend if found
|
# normal value but add if funcend is found
|
||||||
seq.append(self._tokenvalue(token))
|
seq.append(val, type_, line=line, col=col)
|
||||||
return 'funcend'
|
return 'funcend'
|
||||||
elif 'operator' == expected:
|
elif 'operator' == expected:
|
||||||
# normal value but add if funcend if found
|
# normal value but add if funcend is found
|
||||||
seq.append(u' ')
|
seq.append(u' ', 'separator') # self._prods.S
|
||||||
seq.append(self._tokenvalue(token))
|
seq.append(val, type_, line=line, col=col)
|
||||||
return 'funcend'
|
return 'funcend'
|
||||||
elif expected in ('funcend', ')', ']', '}'):
|
elif expected in ('funcend', ')', ']', '}'):
|
||||||
# a block
|
# a block
|
||||||
seq[-1] += self._tokenvalue(token)
|
seq.appendToVal(val)
|
||||||
return expected
|
return expected
|
||||||
else:
|
else:
|
||||||
new['wellformed'] = False
|
new['wellformed'] = False
|
||||||
@ -335,11 +393,9 @@ class CSSValue(cssutils.util.Base):
|
|||||||
self._log.error(u'CSSValue: Unknown syntax or no value: %r.' %
|
self._log.error(u'CSSValue: Unknown syntax or no value: %r.' %
|
||||||
self._valuestr(cssText))
|
self._valuestr(cssText))
|
||||||
else:
|
else:
|
||||||
# TODO: not very efficient tokenizing twice!
|
newseq = self._tempSeq() # []
|
||||||
tokenizer = self._tokenize2(cssText)
|
|
||||||
newseq = []
|
|
||||||
wellformed, expected = self._parse(expected='term',
|
wellformed, expected = self._parse(expected='term',
|
||||||
seq=newseq, tokenizer=tokenizer,
|
seq=newseq, tokenizer=tokenizer,initialtoken=linetoken,
|
||||||
productions={'S': _S,
|
productions={'S': _S,
|
||||||
'CHAR': _char,
|
'CHAR': _char,
|
||||||
|
|
||||||
@ -347,11 +403,11 @@ class CSSValue(cssutils.util.Base):
|
|||||||
'PERCENTAGE': _number_percentage_dimension,
|
'PERCENTAGE': _number_percentage_dimension,
|
||||||
'DIMENSION': _number_percentage_dimension,
|
'DIMENSION': _number_percentage_dimension,
|
||||||
|
|
||||||
'STRING': _string_ident_uri_hexcolor,
|
'STRING': _string_ident_uri,
|
||||||
'IDENT': _string_ident_uri_hexcolor,
|
'IDENT': _string_ident_uri,
|
||||||
'URI': _string_ident_uri_hexcolor,
|
'URI': _string_ident_uri,
|
||||||
'HASH': _string_ident_uri_hexcolor,
|
'HASH': _hash,
|
||||||
'UNICODE-RANGE': _string_ident_uri_hexcolor, #?
|
'UNICODE-RANGE': _string_ident_uri, #?
|
||||||
|
|
||||||
'FUNCTION': _function
|
'FUNCTION': _function
|
||||||
})
|
})
|
||||||
@ -359,7 +415,16 @@ class CSSValue(cssutils.util.Base):
|
|||||||
wellformed = wellformed and new['wellformed']
|
wellformed = wellformed and new['wellformed']
|
||||||
|
|
||||||
# post conditions
|
# post conditions
|
||||||
if expected.startswith('term') and newseq and newseq[-1] != u' ' or (
|
def lastseqvalue(seq):
|
||||||
|
"""find last actual value in seq, not COMMENT!"""
|
||||||
|
for i, item in enumerate(reversed(seq)):
|
||||||
|
if 'COMMENT' != item.type:
|
||||||
|
return len(seq)-1-i, item.value
|
||||||
|
else:
|
||||||
|
return 0, None
|
||||||
|
lastpos, lastval = lastseqvalue(newseq)
|
||||||
|
|
||||||
|
if expected.startswith('term') and lastval != u' ' or (
|
||||||
expected in ('funcend', ')', ']', '}')):
|
expected in ('funcend', ')', ']', '}')):
|
||||||
wellformed = False
|
wellformed = False
|
||||||
self._log.error(u'CSSValue: Incomplete value: %r.' %
|
self._log.error(u'CSSValue: Incomplete value: %r.' %
|
||||||
@ -371,11 +436,14 @@ class CSSValue(cssutils.util.Base):
|
|||||||
self._valuestr(cssText))
|
self._valuestr(cssText))
|
||||||
|
|
||||||
else:
|
else:
|
||||||
self._linetoken = linetoken # used for line report
|
# remove last token if 'separator'
|
||||||
self.seq = newseq
|
if lastval == u' ':
|
||||||
self.valid = False
|
del newseq[lastpos]
|
||||||
|
|
||||||
self._validate()
|
self._linetoken = linetoken # used for line report
|
||||||
|
self._setSeq(newseq)
|
||||||
|
|
||||||
|
self.valid = self._validate(u''.join(new['rawvalues']))
|
||||||
|
|
||||||
if len(new['values']) == 1 and new['values'][0] == u'inherit':
|
if len(new['values']) == 1 and new['values'][0] == u'inherit':
|
||||||
self._value = u'inherit'
|
self._value = u'inherit'
|
||||||
@ -419,23 +487,42 @@ class CSSValue(cssutils.util.Base):
|
|||||||
cssValueTypeString = property(_getCssValueTypeString,
|
cssValueTypeString = property(_getCssValueTypeString,
|
||||||
doc="cssutils: Name of cssValueType of this CSSValue (readonly).")
|
doc="cssutils: Name of cssValueType of this CSSValue (readonly).")
|
||||||
|
|
||||||
def _validate(self):
|
def _validate(self, value=None, profile=None):
|
||||||
"""
|
"""
|
||||||
validates value against _propertyName context if given
|
validates value against _propertyName context if given
|
||||||
"""
|
"""
|
||||||
|
valid = False
|
||||||
if self._value:
|
if self._value:
|
||||||
if self._propertyName in cssproperties.cssvalues:
|
if self._propertyName and self._propertyName in profiles.propertiesByProfile():
|
||||||
if cssproperties.cssvalues[self._propertyName](self._value):
|
valid, validprofile = \
|
||||||
self.valid = True
|
profiles.validateWithProfile(self._propertyName,
|
||||||
else:
|
self._normalize(self._value))
|
||||||
self.valid = False
|
if not validprofile:
|
||||||
|
validprofile = u''
|
||||||
|
|
||||||
|
if not valid:
|
||||||
self._log.warn(
|
self._log.warn(
|
||||||
u'CSSValue: Invalid value for CSS2 property %r: %r' %
|
u'CSSValue: Invalid value for %s property "%s: %s".' %
|
||||||
(self._propertyName, self._value), neverraise=True)
|
(validprofile, self._propertyName,
|
||||||
|
self._value), neverraise=True)
|
||||||
|
elif profile and validprofile != profile:
|
||||||
|
self._log.warn(
|
||||||
|
u'CSSValue: Invalid value for %s property "%s: %s" but valid %s property.' %
|
||||||
|
(profile, self._propertyName, self._value,
|
||||||
|
validprofile), neverraise=True)
|
||||||
|
else:
|
||||||
|
self._log.debug(
|
||||||
|
u'CSSValue: Found valid %s property "%s: %s".' %
|
||||||
|
(validprofile, self._propertyName, self._value),
|
||||||
|
neverraise=True)
|
||||||
else:
|
else:
|
||||||
self._log.debug(
|
self._log.debug(u'CSSValue: Unable to validate as no or unknown property context set for value: %r'
|
||||||
u'CSSValue: Unable to validate as no or unknown property context set for this value: %r'
|
% self._value, neverraise=True)
|
||||||
% self._value, neverraise=True)
|
|
||||||
|
if not value:
|
||||||
|
# if value is given this should not be saved
|
||||||
|
self.valid = valid
|
||||||
|
return valid
|
||||||
|
|
||||||
def _get_propertyName(self):
|
def _get_propertyName(self):
|
||||||
return self.__propertyName
|
return self.__propertyName
|
||||||
@ -608,70 +695,80 @@ class CSSPrimitiveValue(CSSValue):
|
|||||||
no value is given as self._value is used
|
no value is given as self._value is used
|
||||||
"""
|
"""
|
||||||
primitiveType = self.CSS_UNKNOWN
|
primitiveType = self.CSS_UNKNOWN
|
||||||
_floatType = False # if unary expect NUMBER DIMENSION or PERCENTAGE
|
|
||||||
tokenizer = self._tokenize2(self._value)
|
|
||||||
t = self._nexttoken(tokenizer)
|
|
||||||
if not t:
|
|
||||||
self._log.error(u'CSSPrimitiveValue: No value.')
|
|
||||||
|
|
||||||
# unary operator:
|
for item in self.seq:
|
||||||
if self._tokenvalue(t) in (u'-', u'+'):
|
if item.type == self._prods.URI:
|
||||||
|
primitiveType = self.CSS_URI
|
||||||
|
break
|
||||||
|
elif item.type == self._prods.STRING:
|
||||||
|
primitiveType = self.CSS_STRING
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
|
||||||
|
_floatType = False # if unary expect NUMBER DIMENSION or PERCENTAGE
|
||||||
|
tokenizer = self._tokenize2(self._value)
|
||||||
t = self._nexttoken(tokenizer)
|
t = self._nexttoken(tokenizer)
|
||||||
if not t:
|
if not t:
|
||||||
self._log.error(u'CSSPrimitiveValue: No value.')
|
self._log.error(u'CSSPrimitiveValue: No value.')
|
||||||
|
|
||||||
_floatType = True
|
# unary operator:
|
||||||
|
if self._tokenvalue(t) in (u'-', u'+'):
|
||||||
|
t = self._nexttoken(tokenizer)
|
||||||
|
if not t:
|
||||||
|
self._log.error(u'CSSPrimitiveValue: No value.')
|
||||||
|
|
||||||
# check for font1, "font2" etc which is treated as ONE string
|
_floatType = True
|
||||||
fontstring = 0 # should be at leayst 2
|
|
||||||
expected = 'ident or string'
|
# check for font1, "font2" etc which is treated as ONE string
|
||||||
tokenizer = self._tokenize2(self._value) # add used tokens again
|
fontstring = 0 # should be at leayst 2
|
||||||
for token in tokenizer:
|
expected = 'ident or string'
|
||||||
val, typ = self._tokenvalue(token, normalize=True), self._type(token)
|
tokenizer = self._tokenize2(self._value) # add used tokens again
|
||||||
if expected == 'ident or string' and typ in (
|
for token in tokenizer:
|
||||||
self._prods.IDENT, self._prods.STRING):
|
val, typ = self._tokenvalue(token, normalize=True), self._type(token)
|
||||||
expected = 'comma'
|
if expected == 'ident or string' and typ in (
|
||||||
fontstring += 1
|
self._prods.IDENT, self._prods.STRING):
|
||||||
elif expected == 'comma' and val == ',':
|
expected = 'comma'
|
||||||
expected = 'ident or string'
|
fontstring += 1
|
||||||
fontstring += 1
|
elif expected == 'comma' and val == ',':
|
||||||
elif typ in (self._prods.S, self._prods.COMMENT):
|
expected = 'ident or string'
|
||||||
continue
|
fontstring += 1
|
||||||
|
elif typ in ('separator', self._prods.S, self._prods.COMMENT):
|
||||||
|
continue
|
||||||
|
else:
|
||||||
|
fontstring = False
|
||||||
|
break
|
||||||
|
|
||||||
|
if fontstring > 2:
|
||||||
|
# special case: e.g. for font-family: a, b; only COMMA IDENT and STRING
|
||||||
|
primitiveType = CSSPrimitiveValue.CSS_STRING
|
||||||
|
elif self._type(t) == self._prods.HASH:
|
||||||
|
# special case, maybe should be converted to rgb in any case?
|
||||||
|
primitiveType = CSSPrimitiveValue.CSS_RGBCOLOR
|
||||||
else:
|
else:
|
||||||
fontstring = False
|
for i, (name, tokentype, search) in enumerate(self._unitinfos):
|
||||||
break
|
val, typ = self._tokenvalue(t, normalize=True), self._type(t)
|
||||||
|
if typ == tokentype:
|
||||||
|
if typ == self._prods.DIMENSION:
|
||||||
|
if not search:
|
||||||
|
primitiveType = i
|
||||||
|
break
|
||||||
|
elif re.match(ur'^[^a-z]*(%s)$' % search, val):
|
||||||
|
primitiveType = i
|
||||||
|
break
|
||||||
|
elif typ == self._prods.FUNCTION:
|
||||||
|
if not search:
|
||||||
|
primitiveType = i
|
||||||
|
break
|
||||||
|
elif val.startswith(search):
|
||||||
|
primitiveType = i
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
primitiveType = i
|
||||||
|
break
|
||||||
|
|
||||||
if fontstring > 2:
|
if _floatType and primitiveType not in self._floattypes:
|
||||||
# special case: e.g. for font-family: a, b; only COMMA IDENT and STRING
|
# - or + only expected before floattype
|
||||||
primitiveType = CSSPrimitiveValue.CSS_STRING
|
primitiveType = self.CSS_UNKNOWN
|
||||||
elif self._type(t) == self._prods.HASH:
|
|
||||||
# special case, maybe should be converted to rgb in any case?
|
|
||||||
primitiveType = CSSPrimitiveValue.CSS_RGBCOLOR
|
|
||||||
else:
|
|
||||||
for i, (name, tokentype, search) in enumerate(self._unitinfos):
|
|
||||||
val, typ = self._tokenvalue(t, normalize=True), self._type(t)
|
|
||||||
if typ == tokentype:
|
|
||||||
if typ == self._prods.DIMENSION:
|
|
||||||
if not search:
|
|
||||||
primitiveType = i
|
|
||||||
break
|
|
||||||
elif re.match(ur'^[^a-z]*(%s)$' % search, val):
|
|
||||||
primitiveType = i
|
|
||||||
break
|
|
||||||
elif typ == self._prods.FUNCTION:
|
|
||||||
if not search:
|
|
||||||
primitiveType = i
|
|
||||||
break
|
|
||||||
elif val.startswith(search):
|
|
||||||
primitiveType = i
|
|
||||||
break
|
|
||||||
else:
|
|
||||||
primitiveType = i
|
|
||||||
break
|
|
||||||
|
|
||||||
if _floatType and primitiveType not in self._floattypes:
|
|
||||||
# - or + only expected before floattype
|
|
||||||
primitiveType = self.CSS_UNKNOWN
|
|
||||||
|
|
||||||
self._primitiveType = primitiveType
|
self._primitiveType = primitiveType
|
||||||
|
|
||||||
@ -711,7 +808,7 @@ class CSSPrimitiveValue(CSSValue):
|
|||||||
|
|
||||||
return val, dim
|
return val, dim
|
||||||
|
|
||||||
def getFloatValue(self, unitType):
|
def getFloatValue(self, unitType=None):
|
||||||
"""
|
"""
|
||||||
(DOM method) This method is used to get a float value in a
|
(DOM method) This method is used to get a float value in a
|
||||||
specified unit. If this CSS value doesn't contain a float value
|
specified unit. If this CSS value doesn't contain a float value
|
||||||
@ -722,20 +819,22 @@ class CSSPrimitiveValue(CSSValue):
|
|||||||
to get the float value. The unit code can only be a float unit type
|
to get the float value. The unit code can only be a float unit type
|
||||||
(i.e. CSS_NUMBER, CSS_PERCENTAGE, CSS_EMS, CSS_EXS, CSS_PX, CSS_CM,
|
(i.e. CSS_NUMBER, CSS_PERCENTAGE, CSS_EMS, CSS_EXS, CSS_PX, CSS_CM,
|
||||||
CSS_MM, CSS_IN, CSS_PT, CSS_PC, CSS_DEG, CSS_RAD, CSS_GRAD, CSS_MS,
|
CSS_MM, CSS_IN, CSS_PT, CSS_PC, CSS_DEG, CSS_RAD, CSS_GRAD, CSS_MS,
|
||||||
CSS_S, CSS_HZ, CSS_KHZ, CSS_DIMENSION).
|
CSS_S, CSS_HZ, CSS_KHZ, CSS_DIMENSION) or None in which case
|
||||||
|
the current dimension is used.
|
||||||
|
|
||||||
returns not necessarily a float but some cases just an int
|
returns not necessarily a float but some cases just an integer
|
||||||
e.g. if the value is ``1px`` it return ``1`` and **not** ``1.0``
|
e.g. if the value is ``1px`` it return ``1`` and **not** ``1.0``
|
||||||
|
|
||||||
conversions might return strange values like 1.000000000001
|
conversions might return strange values like 1.000000000001
|
||||||
"""
|
"""
|
||||||
if unitType not in self._floattypes:
|
if unitType is not None and unitType not in self._floattypes:
|
||||||
raise xml.dom.InvalidAccessErr(
|
raise xml.dom.InvalidAccessErr(
|
||||||
u'unitType Parameter is not a float type')
|
u'unitType Parameter is not a float type')
|
||||||
|
|
||||||
val, dim = self.__getValDim()
|
val, dim = self.__getValDim()
|
||||||
|
|
||||||
if self.primitiveType != unitType:
|
if unitType is not None and self.primitiveType != unitType:
|
||||||
|
# convert if needed
|
||||||
try:
|
try:
|
||||||
val = self._converter[self.primitiveType, unitType](val)
|
val = self._converter[self.primitiveType, unitType](val)
|
||||||
except KeyError:
|
except KeyError:
|
||||||
@ -937,6 +1036,17 @@ class CSSPrimitiveValue(CSSValue):
|
|||||||
# TODO: use Rect class
|
# TODO: use Rect class
|
||||||
raise NotImplementedError()
|
raise NotImplementedError()
|
||||||
|
|
||||||
|
def _getCssText(self):
|
||||||
|
"""overwritten from CSSValue"""
|
||||||
|
return cssutils.ser.do_css_CSSPrimitiveValue(self)
|
||||||
|
|
||||||
|
def _setCssText(self, cssText):
|
||||||
|
"""use CSSValue's implementation"""
|
||||||
|
return super(CSSPrimitiveValue, self)._setCssText(cssText)
|
||||||
|
|
||||||
|
cssText = property(_getCssText, _setCssText,
|
||||||
|
doc="A string representation of the current value.")
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return "<cssutils.css.%s object primitiveType=%s cssText=%r _propertyName=%r valid=%r at 0x%x>" % (
|
return "<cssutils.css.%s object primitiveType=%s cssText=%r _propertyName=%r valid=%r at 0x%x>" % (
|
||||||
self.__class__.__name__, self.primitiveTypeString,
|
self.__class__.__name__, self.primitiveTypeString,
|
||||||
@ -972,24 +1082,24 @@ class CSSValueList(CSSValue):
|
|||||||
ivalueseq, valueseq = 0, self._SHORTHANDPROPERTIES.get(
|
ivalueseq, valueseq = 0, self._SHORTHANDPROPERTIES.get(
|
||||||
self._propertyName, [])
|
self._propertyName, [])
|
||||||
self._items = []
|
self._items = []
|
||||||
newseq = []
|
newseq = self._tempSeq(False)
|
||||||
i, max = 0, len(self.seq)
|
i, max = 0, len(self.seq)
|
||||||
minus = None
|
minus = None
|
||||||
while i < max:
|
while i < max:
|
||||||
v = self.seq[i]
|
item = self.seq[i]
|
||||||
|
type_, val, line, col = item.type, item.value, item.line, item.col
|
||||||
if u'-' == v:
|
if u'-' == val:
|
||||||
if minus: # 2 "-" after another
|
if minus: # 2 "-" after another
|
||||||
self._log.error(
|
self._log.error( # TODO:
|
||||||
u'CSSValueList: Unknown syntax: %r.'
|
u'CSSValueList: Unknown syntax: %r.'
|
||||||
% u''.join(self.seq))
|
% u''.join(self.seq))
|
||||||
else:
|
else:
|
||||||
minus = v
|
minus = val
|
||||||
|
|
||||||
elif isinstance(v, basestring) and not v.strip() == u'' and\
|
elif isinstance(val, basestring) and not type_ == 'separator' and\
|
||||||
not u'/' == v:
|
not u'/' == val:
|
||||||
if minus:
|
if minus:
|
||||||
v = minus + v
|
val = minus + val
|
||||||
minus = None
|
minus = None
|
||||||
# TODO: complete
|
# TODO: complete
|
||||||
# if shorthand get new propname
|
# if shorthand get new propname
|
||||||
@ -1007,55 +1117,70 @@ class CSSValueList(CSSValue):
|
|||||||
if propname in self._SHORTHANDPROPERTIES:
|
if propname in self._SHORTHANDPROPERTIES:
|
||||||
propname = None
|
propname = None
|
||||||
|
|
||||||
if i+1 < max and self.seq[i+1] == u',':
|
if i+1 < max and self.seq[i+1].value == u',':
|
||||||
# a comma separated list of values as ONE value
|
# a comma separated list of values as ONE value
|
||||||
# e.g. font-family: a,b
|
# e.g. font-family: a,b
|
||||||
fullvalue = [v]
|
# CSSValue already has removed extra S tokens!
|
||||||
|
fullvalue = [val]
|
||||||
|
|
||||||
expected = 'comma' # or 'value'
|
expected = 'comma' # or 'value'
|
||||||
for j in range(i+1, max):
|
for j in range(i+1, max):
|
||||||
testv = self.seq[j]
|
item2 = self.seq[j]
|
||||||
if u' ' == testv: # a single value follows
|
typ2, val2, line2, col2 = (item2.type, item2.value,
|
||||||
|
item2.line, item2.col)
|
||||||
|
if u' ' == val2:
|
||||||
|
# end or a single value follows
|
||||||
break
|
break
|
||||||
elif testv in ('-', '+') and expected == 'value':
|
elif 'value' == expected and val2 in u'-+':
|
||||||
# unary modifier
|
# unary modifier
|
||||||
fullvalue.append(testv)
|
fullvalue.append(val2)
|
||||||
expected = 'value'
|
expected = 'value'
|
||||||
elif u',' == testv and expected == 'comma':
|
elif 'comma' == expected and u',' == val2:
|
||||||
fullvalue.append(testv)
|
fullvalue.append(val2)
|
||||||
expected = 'value'
|
expected = 'value'
|
||||||
elif u',' != testv and expected == 'value':
|
elif 'value' == expected and u',' != val2:
|
||||||
fullvalue.append(testv)
|
if 'STRING' == typ2:
|
||||||
|
val2 = cssutils.ser._string(val2)
|
||||||
|
fullvalue.append(val2)
|
||||||
expected = 'comma'
|
expected = 'comma'
|
||||||
else:
|
else:
|
||||||
self._log.error(
|
self._log.error(
|
||||||
u'CSSValueList: Unknown syntax: %r.'
|
u'CSSValueList: Unknown syntax: %r.'
|
||||||
% testv)
|
% val2)
|
||||||
return
|
return
|
||||||
if expected == 'value':
|
if expected == 'value':
|
||||||
self._log.error(
|
self._log.error( # TODO:
|
||||||
u'CSSValueList: Unknown syntax: %r.'
|
u'CSSValueList: Unknown syntax: %r.'
|
||||||
% u''.join(self.seq))
|
% u''.join(self.seq))
|
||||||
return
|
return
|
||||||
# setting _propertyName this way does not work
|
# setting _propertyName this way does not work
|
||||||
# for compound props like font!
|
# for compound props like font!
|
||||||
i += len(fullvalue) - 1
|
i += len(fullvalue) - 1
|
||||||
o = CSSValue(cssText=u''.join(fullvalue),
|
obj = CSSValue(cssText=u''.join(fullvalue),
|
||||||
_propertyName=propname)
|
_propertyName=propname)
|
||||||
else:
|
else:
|
||||||
# a single value, u' ' or nothing should be following
|
# a single value, u' ' or nothing should be following
|
||||||
o = CSSValue(cssText=v, _propertyName=propname)
|
if 'STRING' == type_:
|
||||||
|
val = cssutils.ser._string(val)
|
||||||
|
elif 'URI' == type_:
|
||||||
|
val = cssutils.ser._uri(val)
|
||||||
|
|
||||||
self._items.append(o)
|
obj = CSSValue(cssText=val, _propertyName=propname)
|
||||||
newseq.append(o)
|
|
||||||
|
self._items.append(obj)
|
||||||
|
newseq.append(obj, CSSValue)
|
||||||
|
|
||||||
|
elif CSSColor == type_:
|
||||||
|
self._items.append(val)
|
||||||
|
newseq.append(val, CSSColor)
|
||||||
|
|
||||||
else:
|
else:
|
||||||
# S (or TODO: comment?)
|
# S (or TODO: comment?)
|
||||||
newseq.append(v)
|
newseq.append(val, type_)
|
||||||
|
|
||||||
i += 1
|
i += 1
|
||||||
|
|
||||||
self.seq = newseq
|
self._setSeq(newseq)
|
||||||
|
|
||||||
length = property(lambda self: len(self._items),
|
length = property(lambda self: len(self._items),
|
||||||
doc="(DOM attribute) The number of CSSValues in the list.")
|
doc="(DOM attribute) The number of CSSValues in the list.")
|
||||||
@ -1081,6 +1206,174 @@ class CSSValueList(CSSValue):
|
|||||||
for i in range (0, self.length):
|
for i in range (0, self.length):
|
||||||
yield self.item(i)
|
yield self.item(i)
|
||||||
|
|
||||||
def __str_(self):
|
def __str__(self):
|
||||||
return "<cssutils.css.%s object length=%s at 0x%x>" % (
|
return "<cssutils.css.%s object cssValueType=%r cssText=%r length=%r propname=%r valid=%r at 0x%x>" % (
|
||||||
self.__class__.__name__, self.length, id(self))
|
self.__class__.__name__, self.cssValueTypeString,
|
||||||
|
self.cssText, self.length, self._propertyName,
|
||||||
|
self.valid, id(self))
|
||||||
|
|
||||||
|
|
||||||
|
class CSSFunction(CSSPrimitiveValue):
|
||||||
|
"""A CSS function value like rect() etc."""
|
||||||
|
|
||||||
|
def __init__(self, cssText=None, readonly=False):
|
||||||
|
"""
|
||||||
|
Init a new CSSFunction
|
||||||
|
|
||||||
|
cssText
|
||||||
|
the parsable cssText of the value
|
||||||
|
readonly
|
||||||
|
defaults to False
|
||||||
|
"""
|
||||||
|
super(CSSColor, self).__init__()
|
||||||
|
self.valid = False
|
||||||
|
self.wellformed = False
|
||||||
|
if cssText is not None:
|
||||||
|
self.cssText = cssText
|
||||||
|
|
||||||
|
self._funcType = None
|
||||||
|
|
||||||
|
self._readonly = readonly
|
||||||
|
|
||||||
|
def _setCssText(self, cssText):
|
||||||
|
self._checkReadonly()
|
||||||
|
if False:
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
types = self._prods # rename!
|
||||||
|
valueProd = Prod(name='value',
|
||||||
|
match=lambda t, v: t in (types.NUMBER, types.PERCENTAGE),
|
||||||
|
toSeq=CSSPrimitiveValue,
|
||||||
|
toStore='parts'
|
||||||
|
)
|
||||||
|
# COLOR PRODUCTION
|
||||||
|
funcProds = Sequence([
|
||||||
|
Prod(name='FUNC',
|
||||||
|
match=lambda t, v: t == types.FUNCTION,
|
||||||
|
toStore='funcType' ),
|
||||||
|
Prod(**PreDef.sign),
|
||||||
|
valueProd,
|
||||||
|
# more values starting with Comma
|
||||||
|
# should use store where colorType is saved to
|
||||||
|
# define min and may, closure?
|
||||||
|
Sequence([Prod(**PreDef.comma),
|
||||||
|
Prod(**PreDef.sign),
|
||||||
|
valueProd],
|
||||||
|
minmax=lambda: (2, 2)),
|
||||||
|
Prod(**PreDef.funcEnd)
|
||||||
|
])
|
||||||
|
# store: colorType, parts
|
||||||
|
wellformed, seq, store, unusedtokens = ProdsParser().parse(cssText,
|
||||||
|
u'CSSFunction',
|
||||||
|
funcProds,
|
||||||
|
{'parts': []})
|
||||||
|
|
||||||
|
if wellformed:
|
||||||
|
self.wellformed = True
|
||||||
|
self._setSeq(seq)
|
||||||
|
self._funcType = self._normalize(store['colorType'].value[:-1])
|
||||||
|
|
||||||
|
cssText = property(lambda self: cssutils.ser.do_css_CSSColor(self),
|
||||||
|
_setCssText)
|
||||||
|
|
||||||
|
funcType = property(lambda self: self._funcType)
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return "cssutils.css.%s(%r)" % (self.__class__.__name__, self.cssText)
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return "<cssutils.css.%s object colorType=%r cssText=%r at 0x%x>" % (
|
||||||
|
self.__class__.__name__, self.colorType, self.cssText,
|
||||||
|
id(self))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
class CSSColor(CSSPrimitiveValue):
|
||||||
|
"""A CSS color like RGB, RGBA or a simple value like `#000` or `red`."""
|
||||||
|
|
||||||
|
def __init__(self, cssText=None, readonly=False):
|
||||||
|
"""
|
||||||
|
Init a new CSSColor
|
||||||
|
|
||||||
|
cssText
|
||||||
|
the parsable cssText of the value
|
||||||
|
readonly
|
||||||
|
defaults to False
|
||||||
|
"""
|
||||||
|
super(CSSColor, self).__init__()
|
||||||
|
self._colorType = None
|
||||||
|
self.valid = False
|
||||||
|
self.wellformed = False
|
||||||
|
if cssText is not None:
|
||||||
|
self.cssText = cssText
|
||||||
|
|
||||||
|
self._readonly = readonly
|
||||||
|
|
||||||
|
def _setCssText(self, cssText):
|
||||||
|
self._checkReadonly()
|
||||||
|
if False:
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
types = self._prods # rename!
|
||||||
|
valueProd = Prod(name='value',
|
||||||
|
match=lambda t, v: t in (types.NUMBER, types.PERCENTAGE),
|
||||||
|
toSeq=CSSPrimitiveValue,
|
||||||
|
toStore='parts'
|
||||||
|
)
|
||||||
|
# COLOR PRODUCTION
|
||||||
|
funccolor = Sequence([Prod(name='FUNC',
|
||||||
|
match=lambda t, v: self._normalize(v) in ('rgb(', 'rgba(', 'hsl(', 'hsla(') and t == types.FUNCTION,
|
||||||
|
toSeq=lambda v: self._normalize(v),
|
||||||
|
toStore='colorType' ),
|
||||||
|
PreDef.unary(),
|
||||||
|
valueProd,
|
||||||
|
# 2 or 3 more values starting with Comma
|
||||||
|
Sequence([PreDef.comma(),
|
||||||
|
PreDef.unary(),
|
||||||
|
valueProd],
|
||||||
|
minmax=lambda: (2,3)),
|
||||||
|
PreDef.funcEnd()
|
||||||
|
]
|
||||||
|
)
|
||||||
|
colorprods = Choice([funccolor,
|
||||||
|
Prod(name='HEX color',
|
||||||
|
match=lambda t, v: t == types.HASH and
|
||||||
|
len(v) == 4 or len(v) == 7,
|
||||||
|
toStore='colorType'
|
||||||
|
),
|
||||||
|
Prod(name='named color',
|
||||||
|
match=lambda t, v: t == types.IDENT,
|
||||||
|
toStore='colorType'
|
||||||
|
),
|
||||||
|
]
|
||||||
|
)
|
||||||
|
# store: colorType, parts
|
||||||
|
wellformed, seq, store, unusedtokens = ProdParser().parse(cssText,
|
||||||
|
u'CSSColor',
|
||||||
|
colorprods,
|
||||||
|
{'parts': []})
|
||||||
|
|
||||||
|
if wellformed:
|
||||||
|
self.wellformed = True
|
||||||
|
if store['colorType'].type == self._prods.HASH:
|
||||||
|
self._colorType = 'HEX'
|
||||||
|
elif store['colorType'].type == self._prods.IDENT:
|
||||||
|
self._colorType = 'Named Color'
|
||||||
|
else:
|
||||||
|
self._colorType = self._normalize(store['colorType'].value)[:-1]
|
||||||
|
|
||||||
|
self._setSeq(seq)
|
||||||
|
|
||||||
|
cssText = property(lambda self: cssutils.ser.do_css_CSSColor(self),
|
||||||
|
_setCssText)
|
||||||
|
|
||||||
|
colorType = property(lambda self: self._colorType)
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return "cssutils.css.%s(%r)" % (self.__class__.__name__, self.cssText)
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return "<cssutils.css.%s object colorType=%r cssText=%r at 0x%x>" % (
|
||||||
|
self.__class__.__name__, self.colorType, self.cssText,
|
||||||
|
id(self))
|
@ -4,11 +4,12 @@ Internal use only, may be removed in the future!
|
|||||||
"""
|
"""
|
||||||
__all__ = ['Property']
|
__all__ = ['Property']
|
||||||
__docformat__ = 'restructuredtext'
|
__docformat__ = 'restructuredtext'
|
||||||
__version__ = '$Id: property.py 1305 2008-06-22 18:42:51Z cthedot $'
|
__version__ = '$Id: property.py 1444 2008-08-31 18:45:35Z cthedot $'
|
||||||
|
|
||||||
import xml.dom
|
import xml.dom
|
||||||
import cssutils
|
import cssutils
|
||||||
import cssproperties
|
#import cssproperties
|
||||||
|
from cssutils.profiles import profiles
|
||||||
from cssvalue import CSSValue
|
from cssvalue import CSSValue
|
||||||
from cssutils.helper import Deprecated
|
from cssutils.helper import Deprecated
|
||||||
|
|
||||||
@ -236,10 +237,10 @@ class Property(cssutils.util.Base):
|
|||||||
self.seqs[0] = newseq
|
self.seqs[0] = newseq
|
||||||
|
|
||||||
# validate
|
# validate
|
||||||
if self._name not in cssproperties.cssvalues:
|
if self._name not in profiles.propertiesByProfile():
|
||||||
self.valid = False
|
self.valid = False
|
||||||
tokenizer=self._tokenize2(name)
|
tokenizer=self._tokenize2(name)
|
||||||
self._log.info(u'Property: No CSS2 Property: %r.' %
|
self._log.warn(u'Property: Unknown Property: %r.' %
|
||||||
new['literalname'], token=token, neverraise=True)
|
new['literalname'], token=token, neverraise=True)
|
||||||
else:
|
else:
|
||||||
self.valid = True
|
self.valid = True
|
402
src/cssutils/prodparser.py
Normal file
402
src/cssutils/prodparser.py
Normal file
@ -0,0 +1,402 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
"""Productions parser used by css and stylesheets classes to parse
|
||||||
|
test into a cssutils.util.Seq and at the same time retrieving
|
||||||
|
additional specific cssutils.util.Item objects for later use.
|
||||||
|
|
||||||
|
TODO:
|
||||||
|
- ProdsParser
|
||||||
|
- handle EOF or STOP?
|
||||||
|
- handle unknown @rules
|
||||||
|
- handle S: maybe save to Seq? parameterized?
|
||||||
|
- store['_raw']: always?
|
||||||
|
|
||||||
|
- Sequence:
|
||||||
|
- opt first(), naive impl for now
|
||||||
|
|
||||||
|
"""
|
||||||
|
__all__ = ['ProdParser', 'Sequence', 'Choice', 'Prod', 'PreDef']
|
||||||
|
__docformat__ = 'restructuredtext'
|
||||||
|
__version__ = '$Id: parse.py 1418 2008-08-09 19:27:50Z cthedot $'
|
||||||
|
|
||||||
|
import cssutils
|
||||||
|
|
||||||
|
|
||||||
|
class ParseError(Exception):
|
||||||
|
"""Base Exception class for ProdParser (used internally)."""
|
||||||
|
pass
|
||||||
|
|
||||||
|
class Exhausted(ParseError):
|
||||||
|
"""Raised if Sequence or Choice is done."""
|
||||||
|
pass
|
||||||
|
|
||||||
|
class NoMatch(ParseError):
|
||||||
|
"""Raised if Sequence or Choice do not match."""
|
||||||
|
pass
|
||||||
|
|
||||||
|
class MissingToken(ParseError):
|
||||||
|
"""Raised if Sequence or Choice are not exhausted."""
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class Choice(object):
|
||||||
|
"""A Choice of productions (Sequence or single Prod)."""
|
||||||
|
def __init__(self, prods):
|
||||||
|
"""
|
||||||
|
prods
|
||||||
|
Prod or Sequence objects
|
||||||
|
"""
|
||||||
|
self._prods = prods
|
||||||
|
self._exhausted = False
|
||||||
|
|
||||||
|
def nextProd(self, token):
|
||||||
|
"""
|
||||||
|
Return:
|
||||||
|
|
||||||
|
- next matching Prod or Sequence
|
||||||
|
- raises ParseError if nothing matches
|
||||||
|
- raises Exhausted if choice already done
|
||||||
|
|
||||||
|
``token`` may be None but this occurs when no tokens left."""
|
||||||
|
if not self._exhausted:
|
||||||
|
for x in self._prods:
|
||||||
|
if isinstance(x, Prod):
|
||||||
|
test = x
|
||||||
|
else:
|
||||||
|
# nested Sequence matches if 1st prod matches
|
||||||
|
test = x.first()
|
||||||
|
try:
|
||||||
|
if test.matches(token):
|
||||||
|
self._exhausted = True
|
||||||
|
return x
|
||||||
|
except ParseError, e:
|
||||||
|
# do not raise if other my match
|
||||||
|
continue
|
||||||
|
else:
|
||||||
|
# None matched
|
||||||
|
raise ParseError(u'No match in choice')
|
||||||
|
else:
|
||||||
|
raise Exhausted(u'Extra token')
|
||||||
|
|
||||||
|
|
||||||
|
class Sequence(object):
|
||||||
|
"""A Sequence of productions (Choice or single Prod)."""
|
||||||
|
def __init__(self, prods, minmax=None):
|
||||||
|
"""
|
||||||
|
prods
|
||||||
|
Prod or Sequence objects
|
||||||
|
minmax = lambda: (1, 1)
|
||||||
|
callback returning number of times this sequence may run
|
||||||
|
"""
|
||||||
|
self._prods = prods
|
||||||
|
if not minmax:
|
||||||
|
minmax = lambda: (1, 1)
|
||||||
|
self._min, self._max = minmax()
|
||||||
|
|
||||||
|
self._number = len(self._prods)
|
||||||
|
self._round = 1 # 1 based!
|
||||||
|
self._pos = 0
|
||||||
|
|
||||||
|
def first(self):
|
||||||
|
"""Return 1st element of Sequence, used by Choice"""
|
||||||
|
# TODO: current impl first only if 1st if an prod!
|
||||||
|
for prod in self._prods:
|
||||||
|
if not prod.optional:
|
||||||
|
return prod
|
||||||
|
|
||||||
|
def _currentName(self):
|
||||||
|
"""Return current element of Sequence, used by name"""
|
||||||
|
# TODO: current impl first only if 1st if an prod!
|
||||||
|
for prod in self._prods[self._pos:]:
|
||||||
|
if not prod.optional:
|
||||||
|
return prod.name
|
||||||
|
else:
|
||||||
|
return 'Unknown'
|
||||||
|
|
||||||
|
name = property(_currentName, doc='Used for Error reporting')
|
||||||
|
|
||||||
|
def nextProd(self, token):
|
||||||
|
"""Return
|
||||||
|
|
||||||
|
- next matching Prod or Choice
|
||||||
|
- raises ParseError if nothing matches
|
||||||
|
- raises Exhausted if sequence already done
|
||||||
|
"""
|
||||||
|
while self._pos < self._number:
|
||||||
|
x = self._prods[self._pos]
|
||||||
|
thisround = self._round
|
||||||
|
|
||||||
|
self._pos += 1
|
||||||
|
if self._pos == self._number:
|
||||||
|
if self._round < self._max:
|
||||||
|
# new round?
|
||||||
|
self._pos = 0
|
||||||
|
self._round += 1
|
||||||
|
|
||||||
|
if isinstance(x, Prod):
|
||||||
|
if not token and (x.optional or thisround > self._min):
|
||||||
|
# token is None if nothing expected
|
||||||
|
raise Exhausted()
|
||||||
|
elif not token and not x.optional:
|
||||||
|
raise MissingToken(u'Missing token for production %s'
|
||||||
|
% x.name)
|
||||||
|
elif x.matches(token):
|
||||||
|
return x
|
||||||
|
elif x.optional:
|
||||||
|
# try next
|
||||||
|
continue
|
||||||
|
# elif thisround > self._min:
|
||||||
|
# # minimum done
|
||||||
|
# self._round = self._max
|
||||||
|
# self._pos = self._number
|
||||||
|
# return None
|
||||||
|
else:
|
||||||
|
# should have matched
|
||||||
|
raise NoMatch(u'No matching production for token')
|
||||||
|
|
||||||
|
else:
|
||||||
|
# nested Sequence or Choice
|
||||||
|
return x
|
||||||
|
|
||||||
|
# Sequence is exhausted
|
||||||
|
if self._round >= self._max:
|
||||||
|
raise Exhausted(u'Extra token')
|
||||||
|
|
||||||
|
|
||||||
|
class Prod(object):
|
||||||
|
"""Single Prod in Sequence or Choice."""
|
||||||
|
def __init__(self, name, match, toSeq=None, toStore=None,
|
||||||
|
optional=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)
|
||||||
|
if given calling toSeq(token) will be appended to seq
|
||||||
|
else simply seq
|
||||||
|
toStore (optional)
|
||||||
|
key to save util.Item to store or callback(store, util.Item)
|
||||||
|
optional = False
|
||||||
|
wether Prod is optional or not
|
||||||
|
"""
|
||||||
|
self.name = name
|
||||||
|
self.match = match
|
||||||
|
self.optional=optional
|
||||||
|
|
||||||
|
def makeToStore(key):
|
||||||
|
"Return a function used by toStore."
|
||||||
|
def toStore(store, item):
|
||||||
|
"Set or append store item."
|
||||||
|
if key in store:
|
||||||
|
store[key].append(item)
|
||||||
|
else:
|
||||||
|
store[key] = item
|
||||||
|
return toStore
|
||||||
|
|
||||||
|
if toSeq:
|
||||||
|
# called: seq.append(toSeq(value))
|
||||||
|
self.toSeq = toSeq
|
||||||
|
else:
|
||||||
|
self.toSeq = lambda val: val
|
||||||
|
|
||||||
|
if callable(toStore):
|
||||||
|
self.toStore = toStore
|
||||||
|
elif toStore:
|
||||||
|
self.toStore = makeToStore(toStore)
|
||||||
|
else:
|
||||||
|
# always set!
|
||||||
|
self.toStore = None
|
||||||
|
|
||||||
|
def matches(self, token):
|
||||||
|
"""Return if token matches."""
|
||||||
|
type_, val, line, col = token
|
||||||
|
return self.match(type_, val)
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return "<cssutils.prodsparser.%s object name=%r at 0x%x>" % (
|
||||||
|
self.__class__.__name__, self.name, id(self))
|
||||||
|
|
||||||
|
|
||||||
|
class ProdParser(object):
|
||||||
|
"""Productions parser."""
|
||||||
|
def __init__(self):
|
||||||
|
self.types = cssutils.cssproductions.CSSProductions
|
||||||
|
self._log = cssutils.log
|
||||||
|
self._tokenizer = cssutils.tokenize2.Tokenizer()
|
||||||
|
|
||||||
|
def parse(self, text, name, productions, store=None):
|
||||||
|
"""
|
||||||
|
text (or token generator)
|
||||||
|
to parse, will be tokenized if not a generator yet
|
||||||
|
|
||||||
|
may be:
|
||||||
|
- a string to be tokenized
|
||||||
|
- a single token, a tuple
|
||||||
|
- a tuple of (token, tokensGenerator)
|
||||||
|
- already tokenized so a tokens generator
|
||||||
|
|
||||||
|
name
|
||||||
|
used for logging
|
||||||
|
productions
|
||||||
|
used to parse tokens
|
||||||
|
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
|
||||||
|
the next Item is appended here.
|
||||||
|
|
||||||
|
TODO: NEEDED? :
|
||||||
|
Key ``raw`` is always added and holds all unprocessed
|
||||||
|
values found
|
||||||
|
|
||||||
|
returns
|
||||||
|
:wellformed: True or False
|
||||||
|
:seq: a filled cssutils.util.Seq object which is NOT readonly yet
|
||||||
|
:store: filled keys defined by Prod.toStore
|
||||||
|
:unusedtokens: token generator containing tokens not used yet
|
||||||
|
"""
|
||||||
|
if isinstance(text, basestring):
|
||||||
|
# to tokenize
|
||||||
|
tokens = self._tokenizer.tokenize(text)
|
||||||
|
elif isinstance(text, tuple):
|
||||||
|
# (token, tokens) or a single token
|
||||||
|
if len(text) == 2:
|
||||||
|
# (token, tokens)
|
||||||
|
def gen(token, tokens):
|
||||||
|
"new generator appending token and tokens"
|
||||||
|
yield token
|
||||||
|
for t in tokens:
|
||||||
|
yield t
|
||||||
|
|
||||||
|
tokens = (t for t in gen(*text))
|
||||||
|
|
||||||
|
else:
|
||||||
|
# single token
|
||||||
|
tokens = [text]
|
||||||
|
else:
|
||||||
|
# already tokenized, assume generator
|
||||||
|
tokens = text
|
||||||
|
|
||||||
|
# a new seq to append all Items to
|
||||||
|
seq = cssutils.util.Seq(readonly=False)
|
||||||
|
|
||||||
|
# store for specific values
|
||||||
|
if not store:
|
||||||
|
store = {}
|
||||||
|
# store['_raw'] = []
|
||||||
|
|
||||||
|
# stack of productions
|
||||||
|
prods = [productions]
|
||||||
|
|
||||||
|
wellformed = True
|
||||||
|
for token in tokens:
|
||||||
|
type_, val, line, col = token
|
||||||
|
# store['_raw'].append(val)
|
||||||
|
|
||||||
|
# default productions
|
||||||
|
if type_ == self.types.S:
|
||||||
|
# always append S?
|
||||||
|
seq.append(val, type_, line, col)
|
||||||
|
elif type_ == self.types.COMMENT:
|
||||||
|
# always append COMMENT
|
||||||
|
seq.append(val, type_, line, col)
|
||||||
|
# elif type_ == self.types.ATKEYWORD:
|
||||||
|
# # @rule
|
||||||
|
# r = cssutils.css.CSSUnknownRule(cssText=val)
|
||||||
|
# seq.append(r, type(r), line, col)
|
||||||
|
elif type_ == self.types.EOF:
|
||||||
|
# do nothing
|
||||||
|
pass
|
||||||
|
# next = 'EOF'
|
||||||
|
else:
|
||||||
|
# check prods
|
||||||
|
try:
|
||||||
|
while True:
|
||||||
|
# find next matching production
|
||||||
|
try:
|
||||||
|
prod = prods[-1].nextProd(token)
|
||||||
|
except (NoMatch, Exhausted), e:
|
||||||
|
# try next
|
||||||
|
prod = None
|
||||||
|
|
||||||
|
if isinstance(prod, Prod):
|
||||||
|
break
|
||||||
|
elif not prod:
|
||||||
|
if len(prods) > 1:
|
||||||
|
# nested exhausted, next in parent
|
||||||
|
prods.pop()
|
||||||
|
else:
|
||||||
|
raise Exhausted('Extra token')
|
||||||
|
else:
|
||||||
|
# nested Sequence, Choice
|
||||||
|
prods.append(prod)
|
||||||
|
|
||||||
|
except ParseError, e:
|
||||||
|
wellformed = False
|
||||||
|
self._log.error(u'%s: %s: %r' % (name, e, token))
|
||||||
|
|
||||||
|
else:
|
||||||
|
# process prod
|
||||||
|
if prod.toSeq:
|
||||||
|
seq.append(prod.toSeq(val), type_, line, col)
|
||||||
|
else:
|
||||||
|
seq.append(val, type_, line, col)
|
||||||
|
|
||||||
|
if prod.toStore:
|
||||||
|
prod.toStore(store, seq[-1])
|
||||||
|
|
||||||
|
# if 'STOP' == next: # EOF?
|
||||||
|
# # stop here and ignore following tokens
|
||||||
|
# break
|
||||||
|
|
||||||
|
while True:
|
||||||
|
# all productions exhausted?
|
||||||
|
try:
|
||||||
|
prod = prods[-1].nextProd(token=None)
|
||||||
|
except Exhausted, e:
|
||||||
|
prod = None # ok
|
||||||
|
except (MissingToken, NoMatch), e:
|
||||||
|
wellformed = False
|
||||||
|
self._log.error(u'%s: %s'
|
||||||
|
% (name, e))
|
||||||
|
else:
|
||||||
|
try:
|
||||||
|
if prod.optional:
|
||||||
|
# ignore optional ones
|
||||||
|
continue
|
||||||
|
except AttributeError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
if prod:
|
||||||
|
wellformed = False
|
||||||
|
self._log.error(u'%s: Missing token for production %r'
|
||||||
|
% (name, prod.name))
|
||||||
|
elif len(prods) > 1:
|
||||||
|
# nested exhausted, next in parent
|
||||||
|
prods.pop()
|
||||||
|
else:
|
||||||
|
break
|
||||||
|
|
||||||
|
# bool, Seq, None or generator
|
||||||
|
return wellformed, seq, store, tokens
|
||||||
|
|
||||||
|
|
||||||
|
class PreDef(object):
|
||||||
|
"""Predefined Prod definition for use in productions definition
|
||||||
|
for ProdParser instances.
|
||||||
|
"""
|
||||||
|
@staticmethod
|
||||||
|
def comma():
|
||||||
|
","
|
||||||
|
return Prod(name=u'comma', match=lambda t, v: v == u',')
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def funcEnd():
|
||||||
|
")"
|
||||||
|
return Prod(name=u'end FUNC ")"', match=lambda t, v: v == u')')
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def unary():
|
||||||
|
"+ or -"
|
||||||
|
return Prod(name=u'unary +-', match=lambda t, v: v in u'+-',
|
||||||
|
optional=True)
|
367
src/cssutils/profiles.py
Normal file
367
src/cssutils/profiles.py
Normal file
@ -0,0 +1,367 @@
|
|||||||
|
"""CSS profiles. Predefined are:
|
||||||
|
|
||||||
|
- 'CSS level 2'
|
||||||
|
|
||||||
|
"""
|
||||||
|
__all__ = ['profiles']
|
||||||
|
__docformat__ = 'restructuredtext'
|
||||||
|
__version__ = '$Id: cssproperties.py 1116 2008-03-05 13:52:23Z cthedot $'
|
||||||
|
|
||||||
|
import cssutils
|
||||||
|
import re
|
||||||
|
|
||||||
|
"""
|
||||||
|
Define some regular expression fragments that will be used as
|
||||||
|
macros within the CSS property value regular expressions.
|
||||||
|
"""
|
||||||
|
css2macros = {
|
||||||
|
'border-style': 'none|hidden|dotted|dashed|solid|double|groove|ridge|inset|outset',
|
||||||
|
'border-color': '{color}',
|
||||||
|
'border-width': '{length}|thin|medium|thick',
|
||||||
|
|
||||||
|
'background-color': r'{color}|transparent|inherit',
|
||||||
|
'background-image': r'{uri}|none|inherit',
|
||||||
|
'background-position': r'({percentage}|{length})(\s*({percentage}|{length}))?|((top|center|bottom)\s*(left|center|right))|((left|center|right)\s*(top|center|bottom))|inherit',
|
||||||
|
'background-repeat': r'repeat|repeat-x|repeat-y|no-repeat|inherit',
|
||||||
|
'background-attachment': r'scroll|fixed|inherit',
|
||||||
|
|
||||||
|
|
||||||
|
'shape': r'rect\(({w}({length}|auto}){w},){3}{w}({length}|auto){w}\)',
|
||||||
|
'counter': r'counter\({w}{identifier}{w}(?:,{w}{list-style-type}{w})?\)',
|
||||||
|
'identifier': r'{ident}',
|
||||||
|
'family-name': r'{string}|{identifier}',
|
||||||
|
'generic-family': r'serif|sans-serif|cursive|fantasy|monospace',
|
||||||
|
'absolute-size': r'(x?x-)?(small|large)|medium',
|
||||||
|
'relative-size': r'smaller|larger',
|
||||||
|
'font-family': r'(({family-name}|{generic-family}){w},{w})*({family-name}|{generic-family})|inherit',
|
||||||
|
'font-size': r'{absolute-size}|{relative-size}|{length}|{percentage}|inherit',
|
||||||
|
'font-style': r'normal|italic|oblique|inherit',
|
||||||
|
'font-variant': r'normal|small-caps|inherit',
|
||||||
|
'font-weight': r'normal|bold|bolder|lighter|[1-9]00|inherit',
|
||||||
|
'line-height': r'normal|{number}|{length}|{percentage}|inherit',
|
||||||
|
'list-style-image': r'{uri}|none|inherit',
|
||||||
|
'list-style-position': r'inside|outside|inherit',
|
||||||
|
'list-style-type': r'disc|circle|square|decimal|decimal-leading-zero|lower-roman|upper-roman|lower-greek|lower-(latin|alpha)|upper-(latin|alpha)|armenian|georgian|none|inherit',
|
||||||
|
'margin-width': r'{length}|{percentage}|auto',
|
||||||
|
'outline-color': r'{color}|invert|inherit',
|
||||||
|
'outline-style': r'{border-style}|inherit',
|
||||||
|
'outline-width': r'{border-width}|inherit',
|
||||||
|
'padding-width': r'{length}|{percentage}',
|
||||||
|
'specific-voice': r'{identifier}',
|
||||||
|
'generic-voice': r'male|female|child',
|
||||||
|
'content': r'{string}|{uri}|{counter}|attr\({w}{identifier}{w}\)|open-quote|close-quote|no-open-quote|no-close-quote',
|
||||||
|
'border-attrs': r'{border-width}|{border-style}|{border-color}',
|
||||||
|
'background-attrs': r'{background-color}|{background-image}|{background-repeat}|{background-attachment}|{background-position}',
|
||||||
|
'list-attrs': r'{list-style-type}|{list-style-position}|{list-style-image}',
|
||||||
|
'font-attrs': r'{font-style}|{font-variant}|{font-weight}',
|
||||||
|
'outline-attrs': r'{outline-color}|{outline-style}|{outline-width}',
|
||||||
|
'text-attrs': r'underline|overline|line-through|blink',
|
||||||
|
}
|
||||||
|
|
||||||
|
"""
|
||||||
|
Define the regular expressions for validation all CSS values
|
||||||
|
"""
|
||||||
|
css2 = {
|
||||||
|
'azimuth': r'{angle}|(behind\s+)?(left-side|far-left|left|center-left|center|center-right|right|far-right|right-side)(\s+behind)?|behind|leftwards|rightwards|inherit',
|
||||||
|
'background-attachment': r'{background-attachment}',
|
||||||
|
'background-color': r'{background-color}',
|
||||||
|
'background-image': r'{background-image}',
|
||||||
|
'background-position': r'{background-position}',
|
||||||
|
'background-repeat': r'{background-repeat}',
|
||||||
|
# Each piece should only be allowed one time
|
||||||
|
'background': r'{background-attrs}(\s+{background-attrs})*|inherit',
|
||||||
|
'border-collapse': r'collapse|separate|inherit',
|
||||||
|
'border-color': r'({border-color}|transparent)(\s+({border-color}|transparent)){0,3}|inherit',
|
||||||
|
'border-spacing': r'{length}(\s+{length})?|inherit',
|
||||||
|
'border-style': r'{border-style}(\s+{border-style}){0,3}|inherit',
|
||||||
|
'border-top': r'{border-attrs}(\s+{border-attrs})*|inherit',
|
||||||
|
'border-right': r'{border-attrs}(\s+{border-attrs})*|inherit',
|
||||||
|
'border-bottom': r'{border-attrs}(\s+{border-attrs})*|inherit',
|
||||||
|
'border-left': r'{border-attrs}(\s+{border-attrs})*|inherit',
|
||||||
|
'border-top-color': r'{border-color}|transparent|inherit',
|
||||||
|
'border-right-color': r'{border-color}|transparent|inherit',
|
||||||
|
'border-bottom-color': r'{border-color}|transparent|inherit',
|
||||||
|
'border-left-color': r'{border-color}|transparent|inherit',
|
||||||
|
'border-top-style': r'{border-style}|inherit',
|
||||||
|
'border-right-style': r'{border-style}|inherit',
|
||||||
|
'border-bottom-style': r'{border-style}|inherit',
|
||||||
|
'border-left-style': r'{border-style}|inherit',
|
||||||
|
'border-top-width': r'{border-width}|inherit',
|
||||||
|
'border-right-width': r'{border-width}|inherit',
|
||||||
|
'border-bottom-width': r'{border-width}|inherit',
|
||||||
|
'border-left-width': r'{border-width}|inherit',
|
||||||
|
'border-width': r'{border-width}(\s+{border-width}){0,3}|inherit',
|
||||||
|
'border': r'{border-attrs}(\s+{border-attrs})*|inherit',
|
||||||
|
'bottom': r'{length}|{percentage}|auto|inherit',
|
||||||
|
'caption-side': r'top|bottom|inherit',
|
||||||
|
'clear': r'none|left|right|both|inherit',
|
||||||
|
'clip': r'{shape}|auto|inherit',
|
||||||
|
'color': r'{color}|inherit',
|
||||||
|
'content': r'normal|{content}(\s+{content})*|inherit',
|
||||||
|
'counter-increment': r'({identifier}(\s+{integer})?)(\s+({identifier}(\s+{integer})))*|none|inherit',
|
||||||
|
'counter-reset': r'({identifier}(\s+{integer})?)(\s+({identifier}(\s+{integer})))*|none|inherit',
|
||||||
|
'cue-after': r'{uri}|none|inherit',
|
||||||
|
'cue-before': r'{uri}|none|inherit',
|
||||||
|
'cue': r'({uri}|none|inherit){1,2}|inherit',
|
||||||
|
'cursor': r'((({uri}{w},{w})*)?(auto|crosshair|default|pointer|move|(e|ne|nw|n|se|sw|s|w)-resize|text|wait|help|progress))|inherit',
|
||||||
|
'direction': r'ltr|rtl|inherit',
|
||||||
|
'display': r'inline|block|list-item|run-in|inline-block|table|inline-table|table-row-group|table-header-group|table-footer-group|table-row|table-column-group|table-column|table-cell|table-caption|none|inherit',
|
||||||
|
'elevation': r'{angle}|below|level|above|higher|lower|inherit',
|
||||||
|
'empty-cells': r'show|hide|inherit',
|
||||||
|
'float': r'left|right|none|inherit',
|
||||||
|
'font-family': r'{font-family}',
|
||||||
|
'font-size': r'{font-size}',
|
||||||
|
'font-style': r'{font-style}',
|
||||||
|
'font-variant': r'{font-variant}',
|
||||||
|
'font-weight': r'{font-weight}',
|
||||||
|
'font': r'({font-attrs}\s+)*{font-size}({w}/{w}{line-height})?\s+{font-family}|caption|icon|menu|message-box|small-caption|status-bar|inherit',
|
||||||
|
'height': r'{length}|{percentage}|auto|inherit',
|
||||||
|
'left': r'{length}|{percentage}|auto|inherit',
|
||||||
|
'letter-spacing': r'normal|{length}|inherit',
|
||||||
|
'line-height': r'{line-height}',
|
||||||
|
'list-style-image': r'{list-style-image}',
|
||||||
|
'list-style-position': r'{list-style-position}',
|
||||||
|
'list-style-type': r'{list-style-type}',
|
||||||
|
'list-style': r'{list-attrs}(\s+{list-attrs})*|inherit',
|
||||||
|
'margin-right': r'{margin-width}|inherit',
|
||||||
|
'margin-left': r'{margin-width}|inherit',
|
||||||
|
'margin-top': r'{margin-width}|inherit',
|
||||||
|
'margin-bottom': r'{margin-width}|inherit',
|
||||||
|
'margin': r'{margin-width}(\s+{margin-width}){0,3}|inherit',
|
||||||
|
'max-height': r'{length}|{percentage}|none|inherit',
|
||||||
|
'max-width': r'{length}|{percentage}|none|inherit',
|
||||||
|
'min-height': r'{length}|{percentage}|none|inherit',
|
||||||
|
'min-width': r'{length}|{percentage}|none|inherit',
|
||||||
|
'orphans': r'{integer}|inherit',
|
||||||
|
'outline-color': r'{outline-color}',
|
||||||
|
'outline-style': r'{outline-style}',
|
||||||
|
'outline-width': r'{outline-width}',
|
||||||
|
'outline': r'{outline-attrs}(\s+{outline-attrs})*|inherit',
|
||||||
|
'overflow': r'visible|hidden|scroll|auto|inherit',
|
||||||
|
'padding-top': r'{padding-width}|inherit',
|
||||||
|
'padding-right': r'{padding-width}|inherit',
|
||||||
|
'padding-bottom': r'{padding-width}|inherit',
|
||||||
|
'padding-left': r'{padding-width}|inherit',
|
||||||
|
'padding': r'{padding-width}(\s+{padding-width}){0,3}|inherit',
|
||||||
|
'page-break-after': r'auto|always|avoid|left|right|inherit',
|
||||||
|
'page-break-before': r'auto|always|avoid|left|right|inherit',
|
||||||
|
'page-break-inside': r'avoid|auto|inherit',
|
||||||
|
'pause-after': r'{time}|{percentage}|inherit',
|
||||||
|
'pause-before': r'{time}|{percentage}|inherit',
|
||||||
|
'pause': r'({time}|{percentage}){1,2}|inherit',
|
||||||
|
'pitch-range': r'{number}|inherit',
|
||||||
|
'pitch': r'{frequency}|x-low|low|medium|high|x-high|inherit',
|
||||||
|
'play-during': r'{uri}(\s+(mix|repeat))*|auto|none|inherit',
|
||||||
|
'position': r'static|relative|absolute|fixed|inherit',
|
||||||
|
'quotes': r'({string}\s+{string})(\s+{string}\s+{string})*|none|inherit',
|
||||||
|
'richness': r'{number}|inherit',
|
||||||
|
'right': r'{length}|{percentage}|auto|inherit',
|
||||||
|
'speak-header': r'once|always|inherit',
|
||||||
|
'speak-numeral': r'digits|continuous|inherit',
|
||||||
|
'speak-punctuation': r'code|none|inherit',
|
||||||
|
'speak': r'normal|none|spell-out|inherit',
|
||||||
|
'speech-rate': r'{number}|x-slow|slow|medium|fast|x-fast|faster|slower|inherit',
|
||||||
|
'stress': r'{number}|inherit',
|
||||||
|
'table-layout': r'auto|fixed|inherit',
|
||||||
|
'text-align': r'left|right|center|justify|inherit',
|
||||||
|
'text-decoration': r'none|{text-attrs}(\s+{text-attrs})*|inherit',
|
||||||
|
'text-indent': r'{length}|{percentage}|inherit',
|
||||||
|
'text-transform': r'capitalize|uppercase|lowercase|none|inherit',
|
||||||
|
'top': r'{length}|{percentage}|auto|inherit',
|
||||||
|
'unicode-bidi': r'normal|embed|bidi-override|inherit',
|
||||||
|
'vertical-align': r'baseline|sub|super|top|text-top|middle|bottom|text-bottom|{percentage}|{length}|inherit',
|
||||||
|
'visibility': r'visible|hidden|collapse|inherit',
|
||||||
|
'voice-family': r'({specific-voice}|{generic-voice}{w},{w})*({specific-voice}|{generic-voice})|inherit',
|
||||||
|
'volume': r'{number}|{percentage}|silent|x-soft|soft|medium|loud|x-loud|inherit',
|
||||||
|
'white-space': r'normal|pre|nowrap|pre-wrap|pre-line|inherit',
|
||||||
|
'widows': r'{integer}|inherit',
|
||||||
|
'width': r'{length}|{percentage}|auto|inherit',
|
||||||
|
'word-spacing': r'normal|{length}|inherit',
|
||||||
|
'z-index': r'auto|{integer}|inherit',
|
||||||
|
}
|
||||||
|
|
||||||
|
# CSS Color Module Level 3
|
||||||
|
css3colormacros = {
|
||||||
|
# orange and transparent in CSS 2.1
|
||||||
|
'namedcolor': r'(currentcolor|transparent|orange|black|green|silver|lime|gray|olive|white|yellow|maroon|navy|red|blue|purple|teal|fuchsia|aqua)',
|
||||||
|
# orange?
|
||||||
|
'rgbacolor': r'rgba\({w}{int}{w},{w}{int}{w},{w}{int}{w},{w}{int}{w}\)|rgba\({w}{num}%{w},{w}{num}%{w},{w}{num}%{w},{w}{num}{w}\)',
|
||||||
|
'hslcolor': r'hsl\({w}{int}{w},{w}{num}%{w},{w}{num}%{w}\)|hsla\({w}{int}{w},{w}{num}%{w},{w}{num}%{w},{w}{num}{w}\)',
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
css3color = {
|
||||||
|
'color': r'{namedcolor}|{hexcolor}|{rgbcolor}|{rgbacolor}|{hslcolor}|inherit',
|
||||||
|
'opacity': r'{num}|inherit'
|
||||||
|
}
|
||||||
|
|
||||||
|
class NoSuchProfileException(Exception):
|
||||||
|
"""Raised if no profile with given name is found"""
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class Profiles(object):
|
||||||
|
"""
|
||||||
|
A dictionary of::
|
||||||
|
|
||||||
|
profilename: {
|
||||||
|
propname: propvalue_regex*
|
||||||
|
}
|
||||||
|
|
||||||
|
Predefined profiles are:
|
||||||
|
|
||||||
|
- 'CSS level 2': Properties defined by CSS2
|
||||||
|
|
||||||
|
"""
|
||||||
|
basicmacros = {
|
||||||
|
'ident': r'[-]?{nmstart}{nmchar}*',
|
||||||
|
'name': r'{nmchar}+',
|
||||||
|
'nmstart': r'[_a-z]|{nonascii}|{escape}',
|
||||||
|
'nonascii': r'[^\0-\177]',
|
||||||
|
'unicode': r'\\[0-9a-f]{1,6}(\r\n|[ \n\r\t\f])?',
|
||||||
|
'escape': r'{unicode}|\\[ -~\200-\777]',
|
||||||
|
# 'escape': r'{unicode}|\\[ -~\200-\4177777]',
|
||||||
|
'int': r'[-]?\d+',
|
||||||
|
'nmchar': r'[\w-]|{nonascii}|{escape}',
|
||||||
|
'num': r'[-]?\d+|[-]?\d*\.\d+',
|
||||||
|
'number': r'{num}',
|
||||||
|
'string': r'{string1}|{string2}',
|
||||||
|
'string1': r'"(\\\"|[^\"])*"',
|
||||||
|
'uri': r'url\({w}({string}|(\\\)|[^\)])+){w}\)',
|
||||||
|
'string2': r"'(\\\'|[^\'])*'",
|
||||||
|
'nl': r'\n|\r\n|\r|\f',
|
||||||
|
'w': r'\s*',
|
||||||
|
}
|
||||||
|
generalmacros = {
|
||||||
|
'hexcolor': r'#[0-9a-f]{3}|#[0-9a-f]{6}',
|
||||||
|
'rgbcolor': r'rgb\({w}{int}{w},{w}{int}{w},{w}{int}{w}\)|rgb\({w}{num}%{w},{w}{num}%{w},{w}{num}%{w}\)',
|
||||||
|
'namedcolor': r'(transparent|orange|maroon|red|orange|yellow|olive|purple|fuchsia|white|lime|green|navy|blue|aqua|teal|black|silver|gray)',
|
||||||
|
'uicolor': r'(ActiveBorder|ActiveCaption|AppWorkspace|Background|ButtonFace|ButtonHighlight|ButtonShadow|ButtonText|CaptionText|GrayText|Highlight|HighlightText|InactiveBorder|InactiveCaption|InactiveCaptionText|InfoBackground|InfoText|Menu|MenuText|Scrollbar|ThreeDDarkShadow|ThreeDFace|ThreeDHighlight|ThreeDLightShadow|ThreeDShadow|Window|WindowFrame|WindowText)',
|
||||||
|
'color': r'{namedcolor}|{hexcolor}|{rgbcolor}|{uicolor}',
|
||||||
|
#'color': r'(maroon|red|orange|yellow|olive|purple|fuchsia|white|lime|green|navy|blue|aqua|teal|black|silver|gray|ActiveBorder|ActiveCaption|AppWorkspace|Background|ButtonFace|ButtonHighlight|ButtonShadow|ButtonText|CaptionText|GrayText|Highlight|HighlightText|InactiveBorder|InactiveCaption|InactiveCaptionText|InfoBackground|InfoText|Menu|MenuText|Scrollbar|ThreeDDarkShadow|ThreeDFace|ThreeDHighlight|ThreeDLightShadow|ThreeDShadow|Window|WindowFrame|WindowText)|#[0-9a-f]{3}|#[0-9a-f]{6}|rgb\({w}{int}{w},{w}{int}{w},{w}{int}{w}\)|rgb\({w}{num}%{w},{w}{num}%{w},{w}{num}%{w}\)',
|
||||||
|
'integer': r'{int}',
|
||||||
|
'length': r'0|{num}(em|ex|px|in|cm|mm|pt|pc)',
|
||||||
|
'angle': r'0|{num}(deg|grad|rad)',
|
||||||
|
'time': r'0|{num}m?s',
|
||||||
|
'frequency': r'0|{num}k?Hz',
|
||||||
|
'percentage': r'{num}%',
|
||||||
|
}
|
||||||
|
|
||||||
|
CSS_LEVEL_2 = 'CSS Level 2.1'
|
||||||
|
CSS_COLOR_LEVEL_3 = 'CSS Color Module Level 3'
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
self._log = cssutils.log
|
||||||
|
self._profilenames = [] # to keep order, REFACTOR!
|
||||||
|
self._profiles = {}
|
||||||
|
self.addProfile(self.CSS_LEVEL_2, css2, css2macros)
|
||||||
|
self.addProfile(self.CSS_COLOR_LEVEL_3, css3color, css3colormacros)
|
||||||
|
|
||||||
|
def _expand_macros(self, dictionary, macros):
|
||||||
|
"""Expand macros in token dictionary"""
|
||||||
|
def macro_value(m):
|
||||||
|
return '(?:%s)' % macros[m.groupdict()['macro']]
|
||||||
|
for key, value in dictionary.items():
|
||||||
|
if not callable(value):
|
||||||
|
while re.search(r'{[a-z][a-z0-9-]*}', value):
|
||||||
|
value = re.sub(r'{(?P<macro>[a-z][a-z0-9-]*)}',
|
||||||
|
macro_value, value)
|
||||||
|
dictionary[key] = value
|
||||||
|
return dictionary
|
||||||
|
|
||||||
|
def _compile_regexes(self, dictionary):
|
||||||
|
"""Compile all regular expressions into callable objects"""
|
||||||
|
for key, value in dictionary.items():
|
||||||
|
if not callable(value):
|
||||||
|
value = re.compile('^(?:%s)$' % value, re.I).match
|
||||||
|
dictionary[key] = value
|
||||||
|
|
||||||
|
return dictionary
|
||||||
|
|
||||||
|
profiles = property(lambda self: sorted(self._profiles.keys()),
|
||||||
|
doc=u'Names of all profiles.')
|
||||||
|
|
||||||
|
def addProfile(self, profile, properties, macros=None):
|
||||||
|
"""Add a new profile with name ``profile`` (e.g. 'CSS level 2')
|
||||||
|
and the given ``properties``. ``macros`` are
|
||||||
|
|
||||||
|
``profile``
|
||||||
|
The new profile's name
|
||||||
|
``properties``
|
||||||
|
A dictionary of ``{ property-name: propery-value }`` items where
|
||||||
|
property-value is a regex which may use macros defined in given
|
||||||
|
``macros`` or the standard macros Profiles.tokens and
|
||||||
|
Profiles.generalvalues.
|
||||||
|
|
||||||
|
``propery-value`` may also be a function which takes a single
|
||||||
|
argument which is the value to validate and which should return
|
||||||
|
True or False.
|
||||||
|
Any exceptions which may be raised during this custom validation
|
||||||
|
are reported or raised as all other cssutils exceptions depending
|
||||||
|
on cssutils.log.raiseExceptions which e.g during parsing normally
|
||||||
|
is False so the exceptions would be logged only.
|
||||||
|
"""
|
||||||
|
if not macros:
|
||||||
|
macros = {}
|
||||||
|
m = self.basicmacros
|
||||||
|
m.update(self.generalmacros)
|
||||||
|
m.update(macros)
|
||||||
|
properties = self._expand_macros(properties, m)
|
||||||
|
self._profilenames.append(profile)
|
||||||
|
self._profiles[profile] = self._compile_regexes(properties)
|
||||||
|
|
||||||
|
def propertiesByProfile(self, profiles=None):
|
||||||
|
"""Generator: Yield property names, if no profile(s) is given all
|
||||||
|
profile's properties are used."""
|
||||||
|
if not profiles:
|
||||||
|
profiles = self.profiles
|
||||||
|
elif isinstance(profiles, basestring):
|
||||||
|
profiles = (profiles, )
|
||||||
|
|
||||||
|
try:
|
||||||
|
for profile in sorted(profiles):
|
||||||
|
for name in sorted(self._profiles[profile].keys()):
|
||||||
|
yield name
|
||||||
|
except KeyError, e:
|
||||||
|
raise NoSuchProfileException(e)
|
||||||
|
|
||||||
|
def validate(self, name, value):
|
||||||
|
"""Check if value is valid for given property name using any profile."""
|
||||||
|
for profile in self.profiles:
|
||||||
|
if name in self._profiles[profile]:
|
||||||
|
try:
|
||||||
|
# custom validation errors are caught
|
||||||
|
r = bool(self._profiles[profile][name](value))
|
||||||
|
except Exception, e:
|
||||||
|
self._log.error(e, error=Exception)
|
||||||
|
return False
|
||||||
|
if r:
|
||||||
|
return r
|
||||||
|
return False
|
||||||
|
|
||||||
|
def validateWithProfile(self, name, value):
|
||||||
|
"""Check if value is valid for given property name returning
|
||||||
|
(valid, valid_in_profile).
|
||||||
|
|
||||||
|
You may want to check if valid_in_profile is what you expected.
|
||||||
|
|
||||||
|
Example: You might expect a valid Profiles.CSS_LEVEL_2 value but
|
||||||
|
e.g. ``validateWithProfile('color', 'rgba(1,1,1,1)')`` returns
|
||||||
|
(True, Profiles.CSS_COLOR_LEVEL_3)
|
||||||
|
"""
|
||||||
|
for profilename in self._profilenames:
|
||||||
|
if name in self._profiles[profilename]:
|
||||||
|
try:
|
||||||
|
# custom validation errors are caught
|
||||||
|
r = (bool(self._profiles[profilename][name](value)),
|
||||||
|
profilename)
|
||||||
|
except Exception, e:
|
||||||
|
self._log.error(e, error=Exception)
|
||||||
|
r = False, None
|
||||||
|
if r[0]:
|
||||||
|
return r
|
||||||
|
return False, None
|
||||||
|
|
||||||
|
|
||||||
|
profiles = Profiles()
|
||||||
|
|
@ -5,9 +5,10 @@
|
|||||||
"""
|
"""
|
||||||
__all__ = ['CSSSerializer', 'Preferences']
|
__all__ = ['CSSSerializer', 'Preferences']
|
||||||
__docformat__ = 'restructuredtext'
|
__docformat__ = 'restructuredtext'
|
||||||
__version__ = '$Id: serialize.py 1419 2008-08-09 19:28:06Z cthedot $'
|
__version__ = '$Id: serialize.py 1472 2008-09-15 21:14:54Z cthedot $'
|
||||||
import codecs
|
import codecs
|
||||||
import re
|
import re
|
||||||
|
import xml.dom
|
||||||
import cssutils
|
import cssutils
|
||||||
|
|
||||||
def _escapecss(e):
|
def _escapecss(e):
|
||||||
@ -193,17 +194,24 @@ class Out(object):
|
|||||||
- some other vals
|
- some other vals
|
||||||
add ``*spacer`` except ``space=False``
|
add ``*spacer`` except ``space=False``
|
||||||
"""
|
"""
|
||||||
if val or 'STRING' == typ:
|
if val or typ in ('STRING', 'URI'):
|
||||||
# PRE
|
# PRE
|
||||||
if 'COMMENT' == typ:
|
if 'COMMENT' == typ:
|
||||||
if self.ser.prefs.keepComments:
|
if self.ser.prefs.keepComments:
|
||||||
val = val.cssText
|
val = val.cssText
|
||||||
else:
|
else:
|
||||||
return
|
return
|
||||||
elif typ in ('Property', cssutils.css.CSSRule.UNKNOWN_RULE):
|
elif hasattr(val, 'cssText'):
|
||||||
val = val.cssText
|
val = val.cssText
|
||||||
|
# elif typ in ('Property', cssutils.css.CSSRule.UNKNOWN_RULE):
|
||||||
|
# val = val.cssText
|
||||||
elif 'S' == typ and not keepS:
|
elif 'S' == typ and not keepS:
|
||||||
return
|
return
|
||||||
|
elif typ in ('NUMBER', 'DIMENSION', 'PERCENTAGE') and val == '0':
|
||||||
|
# remove sign + or - if value is zero
|
||||||
|
# TODO: only for lenghts!
|
||||||
|
if self.out and self.out[-1] in u'+-':
|
||||||
|
del self.out[-1]
|
||||||
elif 'STRING' == typ:
|
elif 'STRING' == typ:
|
||||||
# may be empty but MUST not be None
|
# may be empty but MUST not be None
|
||||||
if val is None:
|
if val is None:
|
||||||
@ -211,7 +219,7 @@ class Out(object):
|
|||||||
val = self.ser._string(val)
|
val = self.ser._string(val)
|
||||||
elif 'URI' == typ:
|
elif 'URI' == typ:
|
||||||
val = self.ser._uri(val)
|
val = self.ser._uri(val)
|
||||||
elif val in u'+>~,:{;)]':
|
elif val in u'+>~,:{;)]/':
|
||||||
self._remove_last_if_S()
|
self._remove_last_if_S()
|
||||||
|
|
||||||
# APPEND
|
# APPEND
|
||||||
@ -235,12 +243,13 @@ class Out(object):
|
|||||||
self.out.append(self.ser.prefs.lineSeparator)
|
self.out.append(self.ser.prefs.lineSeparator)
|
||||||
elif u';' == val: # end or prop or block
|
elif u';' == val: # end or prop or block
|
||||||
self.out.append(self.ser.prefs.lineSeparator)
|
self.out.append(self.ser.prefs.lineSeparator)
|
||||||
elif val not in u'}[]()' and space:
|
elif val not in u'}[]()/' and typ not in ('FUNCTION',) and space:
|
||||||
self.out.append(self.ser.prefs.spacer)
|
self.out.append(self.ser.prefs.spacer)
|
||||||
|
|
||||||
def value(self, delim=u'', end=None):
|
def value(self, delim=u'', end=None, keepS=False):
|
||||||
"returns all items joined by delim"
|
"returns all items joined by delim"
|
||||||
self._remove_last_if_S()
|
if not keepS:
|
||||||
|
self._remove_last_if_S()
|
||||||
if end:
|
if end:
|
||||||
self.out.append(end)
|
self.out.append(end)
|
||||||
return delim.join(self.out)
|
return delim.join(self.out)
|
||||||
@ -253,8 +262,9 @@ class CSSSerializer(object):
|
|||||||
To use your own serializing method the easiest is to subclass CSS
|
To use your own serializing method the easiest is to subclass CSS
|
||||||
Serializer and overwrite the methods you like to customize.
|
Serializer and overwrite the methods you like to customize.
|
||||||
"""
|
"""
|
||||||
# chars not in URI without quotes around
|
# chars not in URI without quotes around as problematic with other stuff
|
||||||
__forbidden_in_uri_matcher = re.compile(ur'''.*?[\)\s\;]''', re.U).match
|
# really ","?
|
||||||
|
__forbidden_in_uri_matcher = re.compile(ur'''.*?[\(\)\s\;,]''', re.U).match
|
||||||
|
|
||||||
def __init__(self, prefs=None):
|
def __init__(self, prefs=None):
|
||||||
"""
|
"""
|
||||||
@ -825,30 +835,100 @@ class CSSSerializer(object):
|
|||||||
return u''.join(out).strip()
|
return u''.join(out).strip()
|
||||||
|
|
||||||
def do_css_CSSValue(self, cssvalue):
|
def do_css_CSSValue(self, cssvalue):
|
||||||
"""
|
"""Serializes a CSSValue"""
|
||||||
serializes a CSSValue
|
|
||||||
"""
|
|
||||||
# TODO: use Out()
|
|
||||||
# TODO: use self._valid(cssvalue)?
|
# TODO: use self._valid(cssvalue)?
|
||||||
|
|
||||||
if not cssvalue:
|
if not cssvalue:
|
||||||
return u''
|
return u''
|
||||||
else:
|
else:
|
||||||
sep = u',%s' % self.prefs.listItemSpacer
|
out = Out(self)
|
||||||
out = []
|
for item in cssvalue.seq:
|
||||||
for part in cssvalue.seq:
|
type_, val = item.type, item.value
|
||||||
if hasattr(part, 'cssText'):
|
if type_ in (cssutils.css.CSSColor,
|
||||||
# comments or CSSValue if a CSSValueList
|
cssutils.css.CSSValue):
|
||||||
out.append(part.cssText)
|
# CSSColor or CSSValue if a CSSValueList
|
||||||
elif isinstance(part, basestring) and part == u',':
|
out.append(val.cssText, type_, space=False, keepS=True)
|
||||||
out.append(sep)
|
|
||||||
else:
|
else:
|
||||||
# TODO: escape func parameter if STRING!
|
if val and val[0] == val[-1] and val[0] in '\'"':
|
||||||
if part and part[0] == part[-1] and part[0] in '\'"':
|
val = self._string(val[1:-1])
|
||||||
# string has " " around it in CSSValue!
|
# S must be kept! in between values but no extra space
|
||||||
part = self._string(part[1:-1])
|
out.append(val, type_, space=False, keepS=True)
|
||||||
out.append(part)
|
return out.value()
|
||||||
return (u''.join(out)).strip()
|
|
||||||
|
def do_css_CSSPrimitiveValue(self, cssvalue):
|
||||||
|
"""Serialize a CSSPrimitiveValue"""
|
||||||
|
# TODO: use self._valid(cssvalue)?
|
||||||
|
if not cssvalue:
|
||||||
|
return u''
|
||||||
|
else:
|
||||||
|
out = Out(self)
|
||||||
|
|
||||||
|
unary = None
|
||||||
|
for item in cssvalue.seq:
|
||||||
|
type_, val = item.type, item.value
|
||||||
|
if 'CHAR' == type_ and val in u'+-':
|
||||||
|
# save for next round
|
||||||
|
unary = val
|
||||||
|
continue
|
||||||
|
if cssutils.css.CSSColor == type_:
|
||||||
|
# Comment or CSSColor
|
||||||
|
val = val.cssText
|
||||||
|
elif type_ in ('DIMENSION', 'NUMBER', 'PERCENTAGE'):
|
||||||
|
# handle saved unary and add to number
|
||||||
|
try:
|
||||||
|
# NUMBER or DIMENSION and is it 0?
|
||||||
|
if 0 == cssvalue.getFloatValue():
|
||||||
|
val = u'0'
|
||||||
|
else:
|
||||||
|
# add unary to val if not 0
|
||||||
|
# TODO: only for lengths!
|
||||||
|
if u'-' == unary:
|
||||||
|
val = unary + val
|
||||||
|
except xml.dom.InvalidAccessErr, e:
|
||||||
|
pass
|
||||||
|
unary = None
|
||||||
|
elif unary:
|
||||||
|
# or simple add
|
||||||
|
out.append(unary, 'CHAR', space=False, keepS=True)
|
||||||
|
unary = None
|
||||||
|
|
||||||
|
out.append(val, type_)
|
||||||
|
# if hasattr(val, 'cssText'):
|
||||||
|
# # comments or CSSValue if a CSSValueList
|
||||||
|
# out.append(val.cssText, type_)
|
||||||
|
# else:
|
||||||
|
# out.append(val, type_) #?
|
||||||
|
|
||||||
|
return out.value()
|
||||||
|
|
||||||
|
def do_css_CSSColor(self, cssvalue):
|
||||||
|
"""Serialize a CSSColor value"""
|
||||||
|
if not cssvalue:
|
||||||
|
return u''
|
||||||
|
else:
|
||||||
|
out = Out(self)
|
||||||
|
unary = None
|
||||||
|
for item in cssvalue.seq:
|
||||||
|
type_, val = item.type, item.value
|
||||||
|
|
||||||
|
# prepare
|
||||||
|
if 'HASH' == type_:
|
||||||
|
# TODO: add pref for this!
|
||||||
|
if len(val) == 7 and val[1] == val[2] and \
|
||||||
|
val[3] == val[4] and val[5] == val[6]:
|
||||||
|
val = u'#%s%s%s' % (val[1], val[3], val[5])
|
||||||
|
elif 'CHAR' == type_ and val in u'+-':
|
||||||
|
# save - for next round
|
||||||
|
if u'-' == val:
|
||||||
|
# omit +
|
||||||
|
unary = val
|
||||||
|
continue
|
||||||
|
elif unary:
|
||||||
|
val = unary + val.cssText
|
||||||
|
unary = None
|
||||||
|
|
||||||
|
out.append(val, type_)
|
||||||
|
|
||||||
|
return out.value()
|
||||||
|
|
||||||
def do_stylesheets_medialist(self, medialist):
|
def do_stylesheets_medialist(self, medialist):
|
||||||
"""
|
"""
|
@ -2,7 +2,7 @@
|
|||||||
"""
|
"""
|
||||||
__all__ = []
|
__all__ = []
|
||||||
__docformat__ = 'restructuredtext'
|
__docformat__ = 'restructuredtext'
|
||||||
__version__ = '$Id: util.py 1429 2008-08-11 19:01:52Z cthedot $'
|
__version__ = '$Id: util.py 1453 2008-09-08 20:57:19Z cthedot $'
|
||||||
|
|
||||||
import codecs
|
import codecs
|
||||||
from itertools import ifilter
|
from itertools import ifilter
|
||||||
@ -13,6 +13,7 @@ import xml.dom
|
|||||||
from helper import normalize
|
from helper import normalize
|
||||||
import tokenize2
|
import tokenize2
|
||||||
import cssutils
|
import cssutils
|
||||||
|
import encutils
|
||||||
|
|
||||||
class Base(object):
|
class Base(object):
|
||||||
"""
|
"""
|
||||||
@ -153,7 +154,7 @@ class Base(object):
|
|||||||
"""
|
"""
|
||||||
if token:
|
if token:
|
||||||
value = token[1][4:-1].strip()
|
value = token[1][4:-1].strip()
|
||||||
if (value[0] in '\'"') and (value[0] == value[-1]):
|
if value and (value[0] in '\'"') and (value[0] == value[-1]):
|
||||||
# a string "..." or '...'
|
# a string "..." or '...'
|
||||||
value = value.replace('\\'+value[0], value[0])[1:-1]
|
value = value.replace('\\'+value[0], value[0])[1:-1]
|
||||||
return value
|
return value
|
||||||
@ -326,7 +327,7 @@ class Base(object):
|
|||||||
return p
|
return p
|
||||||
|
|
||||||
def _parse(self, expected, seq, tokenizer, productions, default=None,
|
def _parse(self, expected, seq, tokenizer, productions, default=None,
|
||||||
new=None):
|
new=None, initialtoken=None):
|
||||||
"""
|
"""
|
||||||
puts parsed tokens in seq by calling a production with
|
puts parsed tokens in seq by calling a production with
|
||||||
(seq, tokenizer, token)
|
(seq, tokenizer, token)
|
||||||
@ -343,13 +344,28 @@ class Base(object):
|
|||||||
default callback if tokentype not in productions
|
default callback if tokentype not in productions
|
||||||
new
|
new
|
||||||
used to init default productions
|
used to init default productions
|
||||||
|
initialtoken
|
||||||
|
will be used together with tokenizer running 1st this token
|
||||||
|
and then all tokens in tokenizer
|
||||||
|
|
||||||
returns (wellformed, expected) which the last prod might have set
|
returns (wellformed, expected) which the last prod might have set
|
||||||
"""
|
"""
|
||||||
wellformed = True
|
wellformed = True
|
||||||
if tokenizer:
|
|
||||||
|
if initialtoken:
|
||||||
|
# add initialtoken to tokenizer
|
||||||
|
def tokens():
|
||||||
|
"Build new tokenizer including initialtoken"
|
||||||
|
yield initialtoken
|
||||||
|
for item in tokenizer:
|
||||||
|
yield item
|
||||||
|
fulltokenizer = (t for t in tokens())
|
||||||
|
else:
|
||||||
|
fulltokenizer = tokenizer
|
||||||
|
|
||||||
|
if fulltokenizer:
|
||||||
prods = self._adddefaultproductions(productions, new)
|
prods = self._adddefaultproductions(productions, new)
|
||||||
for token in tokenizer:
|
for token in fulltokenizer:
|
||||||
p = prods.get(token[0], default)
|
p = prods.get(token[0], default)
|
||||||
if p:
|
if p:
|
||||||
expected = p(expected, seq, token, tokenizer)
|
expected = p(expected, seq, token, tokenizer)
|
||||||
@ -487,16 +503,36 @@ class Seq(object):
|
|||||||
else:
|
else:
|
||||||
self._seq[index] = Item(val, typ, line, col)
|
self._seq[index] = Item(val, typ, line, col)
|
||||||
|
|
||||||
|
def appendToVal(self, val=None, index=-1):
|
||||||
|
"""
|
||||||
|
if not readonly append to Item's value at index
|
||||||
|
"""
|
||||||
|
if self._readonly:
|
||||||
|
raise AttributeError('Seq is readonly.')
|
||||||
|
else:
|
||||||
|
old = self._seq[index]
|
||||||
|
self._seq[index] = Item(old.value + val, old.type,
|
||||||
|
old.line, old.col)
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
"returns a repr same as a list of tuples of (value, type)"
|
"returns a repr same as a list of tuples of (value, type)"
|
||||||
return u'cssutils.%s.%s([\n %s])' % (self.__module__,
|
return u'cssutils.%s.%s([\n %s])' % (self.__module__,
|
||||||
self.__class__.__name__,
|
self.__class__.__name__,
|
||||||
u',\n '.join([u'(%r, %r)' % (item.type, item.value)
|
u',\n '.join([u'%r' % item for item in self._seq]
|
||||||
for item in self._seq]
|
|
||||||
))
|
))
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return "<cssutils.%s.%s object length=%r at 0x%x>" % (
|
vals = []
|
||||||
self.__module__, self.__class__.__name__, len(self), id(self))
|
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 valuestring=%r at 0x%x>" % (
|
||||||
|
self.__module__, self.__class__.__name__, len(self),
|
||||||
|
u''.join(vals), id(self))
|
||||||
|
|
||||||
class Item(object):
|
class Item(object):
|
||||||
"""
|
"""
|
||||||
@ -740,7 +776,7 @@ def _defaultFetcher(url):
|
|||||||
if res:
|
if res:
|
||||||
mimeType, encoding = encutils.getHTTPInfo(res)
|
mimeType, encoding = encutils.getHTTPInfo(res)
|
||||||
if mimeType != u'text/css':
|
if mimeType != u'text/css':
|
||||||
cssutils.log.error(u'Expected "text/css" mime type for url=%s but found: %r' %
|
cssutils.log.error(u'Expected "text/css" mime type for url=%r but found: %r' %
|
||||||
(url, mimeType), error=ValueError)
|
(url, mimeType), error=ValueError)
|
||||||
return encoding, res.read()
|
return encoding, res.read()
|
||||||
|
|
Loading…
x
Reference in New Issue
Block a user