Switch from cssutils to css-parser

css-parser is a new fork of the unmaintained cssutils.
See https://github.com/ebook-utils/css-parser
This commit is contained in:
Kovid Goyal 2019-01-02 21:49:02 +05:30
parent 3f57acc815
commit dd7d8ea3c4
No known key found for this signature in database
GPG Key ID: 06BC317B515ACE7C
33 changed files with 91 additions and 121 deletions

View File

@ -8,7 +8,7 @@ __copyright__ = '2014, Kovid Goyal <kovid at kovidgoyal.net>'
import re import re
from PyQt5.Qt import QAction, QInputDialog from PyQt5.Qt import QAction, QInputDialog
from cssutils.css import CSSRule from css_parser.css import CSSRule
# The base class that all tools must inherit from # The base class that all tools must inherit from
from calibre.gui2.tweak_book.plugin import Tool from calibre.gui2.tweak_book.plugin import Tool
@ -17,6 +17,7 @@ from calibre import force_unicode
from calibre.gui2 import error_dialog from calibre.gui2 import error_dialog
from calibre.ebooks.oeb.polish.container import OEB_DOCS, OEB_STYLES, serialize from calibre.ebooks.oeb.polish.container import OEB_DOCS, OEB_STYLES, serialize
class DemoTool(Tool): class DemoTool(Tool):
#: Set this to a unique name it will be used as a key #: Set this to a unique name it will be used as a key
@ -79,7 +80,7 @@ class DemoTool(Tool):
# stylesheets, <style> tags and style="" attributes # stylesheets, <style> tags and style="" attributes
for name, media_type in container.mime_map.iteritems(): for name, media_type in container.mime_map.iteritems():
if media_type in OEB_STYLES: if media_type in OEB_STYLES:
# A stylesheet. Parsed stylesheets are cssutils CSSStylesheet # A stylesheet. Parsed stylesheets are css_parser CSSStylesheet
# objects. # objects.
self.magnify_stylesheet(container.parsed(name), factor) self.magnify_stylesheet(container.parsed(name), factor)
container.dirty(name) # Tell the container that we have changed the stylesheet container.dirty(name) # Tell the container that we have changed the stylesheet

View File

@ -262,7 +262,7 @@ class OutputProfile(Plugin):
touchscreen = False touchscreen = False
touchscreen_news_css = '' touchscreen_news_css = ''
#: A list of extra (beyond CSS 2.1) modules supported by the device #: A list of extra (beyond CSS 2.1) modules supported by the device
#: Format is a cssutils profile dictionary (see iPad for example) #: Format is a css_parser profile dictionary (see iPad for example)
extra_css_modules = [] extra_css_modules = []
#: If True, the date is appended to the title of downloaded news #: If True, the date is appended to the title of downloaded news
periodical_date_in_title = True periodical_date_in_title = True

View File

@ -2036,12 +2036,12 @@ class KOBOTOUCH(KOBO):
def get_extra_css(self): def get_extra_css(self):
extra_sheet = None extra_sheet = None
from cssutils.css import CSSRule from css_parser.css import CSSRule
if self.modifying_css(): if self.modifying_css():
extra_css_path = os.path.join(self._main_prefix, self.KOBO_EXTRA_CSSFILE) extra_css_path = os.path.join(self._main_prefix, self.KOBO_EXTRA_CSSFILE)
if os.path.exists(extra_css_path): if os.path.exists(extra_css_path):
from cssutils import parseFile as cssparseFile from css_parser import parseFile as cssparseFile
try: try:
extra_sheet = cssparseFile(extra_css_path) extra_sheet = cssparseFile(extra_css_path)
debug_print("KoboTouch:get_extra_css: Using extra CSS in {0} ({1} rules)".format(extra_css_path, len(extra_sheet.cssRules))) debug_print("KoboTouch:get_extra_css: Using extra CSS in {0} ({1} rules)".format(extra_css_path, len(extra_sheet.cssRules)))
@ -2068,7 +2068,7 @@ class KOBOTOUCH(KOBO):
return [r for r in sheet.cssRules.rulesOfType(css_rule)] return [r for r in sheet.cssRules.rulesOfType(css_rule)]
def get_extra_css_rules_widow_orphan(self, sheet): def get_extra_css_rules_widow_orphan(self, sheet):
from cssutils.css import CSSRule from css_parser.css import CSSRule
return [r for r in self.get_extra_css_rules(sheet, CSSRule.STYLE_RULE) return [r for r in self.get_extra_css_rules(sheet, CSSRule.STYLE_RULE)
if (r.style['widows'] or r.style['orphans'])] if (r.style['widows'] or r.style['orphans'])]
@ -2158,7 +2158,7 @@ class KOBOTOUCH(KOBO):
return True return True
def _modify_stylesheet(self, sheet, fileext, is_dirty=False): def _modify_stylesheet(self, sheet, fileext, is_dirty=False):
from cssutils.css import CSSRule from css_parser.css import CSSRule
# if fileext in (EPUB_EXT, KEPUB_EXT): # if fileext in (EPUB_EXT, KEPUB_EXT):

View File

@ -497,7 +497,7 @@ class EPUBOutput(OutputFormatPlugin):
if stylesheet is not None: if stylesheet is not None:
# ADE doesn't render lists correctly if they have left margins # ADE doesn't render lists correctly if they have left margins
from cssutils.css import CSSRule from css_parser.css import CSSRule
for lb in XPath('//h:ul[@class]|//h:ol[@class]')(root): for lb in XPath('//h:ul[@class]|//h:ol[@class]')(root):
sel = '.'+lb.get('class') sel = '.'+lb.get('class')
for rule in stylesheet.data.cssRules.rulesOfType(CSSRule.STYLE_RULE): for rule in stylesheet.data.cssRules.rulesOfType(CSSRule.STYLE_RULE):

View File

@ -73,8 +73,8 @@ class FB2Input(InputFormatPlugin):
css += etree.tostring(s, encoding=unicode, method='text', css += etree.tostring(s, encoding=unicode, method='text',
with_tail=False) + '\n\n' with_tail=False) + '\n\n'
if css: if css:
import cssutils, logging import css_parser, logging
parser = cssutils.CSSParser(fetcher=None, parser = css_parser.CSSParser(fetcher=None,
log=logging.getLogger('calibre.css')) log=logging.getLogger('calibre.css'))
XHTML_CSS_NAMESPACE = '@namespace "%s";\n' % XHTML_NS XHTML_CSS_NAMESPACE = '@namespace "%s";\n' % XHTML_NS

View File

@ -109,8 +109,8 @@ class HTMLInput(InputFormatPlugin):
from calibre.ebooks.html.input import get_filelist from calibre.ebooks.html.input import get_filelist
from calibre.ebooks.metadata import string_to_authors from calibre.ebooks.metadata import string_to_authors
from calibre.utils.localization import canonicalize_lang from calibre.utils.localization import canonicalize_lang
import cssutils, logging import css_parser, logging
cssutils.log.setLevel(logging.WARN) css_parser.log.setLevel(logging.WARN)
self.OEB_STYLES = OEB_STYLES self.OEB_STYLES = OEB_STYLES
oeb = create_oebbook(log, None, opts, self, oeb = create_oebbook(log, None, opts, self,
encoding=opts.input_encoding, populate=False) encoding=opts.input_encoding, populate=False)
@ -189,7 +189,7 @@ class HTMLInput(InputFormatPlugin):
if href == item.href: if href == item.href:
dpath = os.path.dirname(path) dpath = os.path.dirname(path)
break break
cssutils.replaceUrls(item.data, css_parser.replaceUrls(item.data,
partial(self.resource_adder, base=dpath)) partial(self.resource_adder, base=dpath))
toc = self.oeb.toc toc = self.oeb.toc

View File

@ -1051,8 +1051,8 @@ OptionRecommendation(name='search_replace',
if self.opts.embed_all_fonts or self.opts.embed_font_family: if self.opts.embed_all_fonts or self.opts.embed_font_family:
# Start the threaded font scanner now, for performance # Start the threaded font scanner now, for performance
from calibre.utils.fonts.scanner import font_scanner # noqa from calibre.utils.fonts.scanner import font_scanner # noqa
import cssutils, logging import css_parser, logging
cssutils.log.setLevel(logging.WARN) css_parser.log.setLevel(logging.WARN)
get_types_map() # Ensure the mimetypes module is intialized get_types_map() # Ensure the mimetypes module is intialized
if self.opts.debug_pipeline is not None: if self.opts.debug_pipeline is not None:

View File

@ -8,7 +8,7 @@ from functools import partial
from collections import OrderedDict from collections import OrderedDict
import operator import operator
from cssutils.css import Property, CSSRule from css_parser.css import Property, CSSRule
from calibre import force_unicode from calibre import force_unicode
from calibre.ebooks import parse_css_length from calibre.ebooks import parse_css_length
@ -345,7 +345,7 @@ def export_rules(serialized_rules):
def import_rules(raw_data): def import_rules(raw_data):
import regex import regex
pat = regex.compile('\s*(\S+)\s*:\s*(.+)', flags=regex.VERSION1) pat = regex.compile(r'\s*(\S+)\s*:\s*(.+)', flags=regex.VERSION1)
current_rule = {} current_rule = {}
def sanitize(r): def sanitize(r):

View File

@ -327,28 +327,6 @@ def upshift_markup(parts):
parts[i] = part parts[i] = part
def handle_media_queries(raw):
# cssutils cannot handle CSS 3 media queries. We look for media queries
# that use amzn-mobi or amzn-kf8 and map them to a simple @media screen
# rule. See https://bugs.launchpad.net/bugs/1406708 for an example
import tinycss
parser = tinycss.make_full_parser()
def replace(m):
sheet = parser.parse_stylesheet(m.group() + '}')
if len(sheet.rules) > 0:
for mq in sheet.rules[0].media:
# Only accept KF8 media types
if (mq.media_type, mq.negated) in {('amzn-mobi', True), ('amzn-kf8', False)}:
return '@media screen {'
else:
# Empty sheet, doesn't matter what we use
return '@media screen {'
return m.group()
return re.sub(r'@media\s[^{;]*?[{;]', replace, raw)
def expand_mobi8_markup(mobi8_reader, resource_map, log): def expand_mobi8_markup(mobi8_reader, resource_map, log):
# First update all internal links that are based on offsets # First update all internal links that are based on offsets
parts = update_internal_links(mobi8_reader, log) parts = update_internal_links(mobi8_reader, log)
@ -390,8 +368,6 @@ def expand_mobi8_markup(mobi8_reader, resource_map, log):
if not os.path.exists(fi.dir): if not os.path.exists(fi.dir):
os.mkdir(fi.dir) os.mkdir(fi.dir)
with open(os.path.join(fi.dir, fi.fname), 'wb') as f: with open(os.path.join(fi.dir, fi.fname), 'wb') as f:
if fi.fname.endswith('.css') and '@media' in flow:
flow = handle_media_queries(flow)
f.write(flow.encode('utf-8')) f.write(flow.encode('utf-8'))
return spine return spine

View File

@ -13,8 +13,8 @@ from collections import defaultdict, namedtuple
from io import BytesIO from io import BytesIO
from struct import pack from struct import pack
import cssutils import css_parser
from cssutils.css import CSSRule from css_parser.css import CSSRule
from lxml import etree from lxml import etree
from calibre import isbytestring, force_unicode from calibre import isbytestring, force_unicode
@ -77,9 +77,9 @@ class KF8Writer(object):
''' Duplicate data so that any changes we make to markup/CSS only ''' Duplicate data so that any changes we make to markup/CSS only
affect KF8 output and not MOBI 6 output ''' affect KF8 output and not MOBI 6 output '''
self._data_cache = {} self._data_cache = {}
# Suppress cssutils logging output as it is duplicated anyway earlier # Suppress css_parser logging output as it is duplicated anyway earlier
# in the pipeline # in the pipeline
cssutils.log.setLevel(logging.CRITICAL) css_parser.log.setLevel(logging.CRITICAL)
for item in self.oeb.manifest: for item in self.oeb.manifest:
if item.media_type in XML_DOCS: if item.media_type in XML_DOCS:
self._data_cache[item.href] = copy.deepcopy(item.data) self._data_cache[item.href] = copy.deepcopy(item.data)
@ -87,7 +87,7 @@ class KF8Writer(object):
# I can't figure out how to make an efficient copy of the # I can't figure out how to make an efficient copy of the
# in-memory CSSStylesheet, as deepcopy doesn't work (raises an # in-memory CSSStylesheet, as deepcopy doesn't work (raises an
# exception) # exception)
self._data_cache[item.href] = cssutils.parseString( self._data_cache[item.href] = css_parser.parseString(
item.data.cssText, validate=False) item.data.cssText, validate=False)
def data(self, item): def data(self, item):
@ -138,9 +138,9 @@ class KF8Writer(object):
for tag in XPath('//h:style')(root): for tag in XPath('//h:style')(root):
if tag.text: if tag.text:
sheet = cssutils.parseString(tag.text, validate=False) sheet = css_parser.parseString(tag.text, validate=False)
replacer = partial(pointer, item) replacer = partial(pointer, item)
cssutils.replaceUrls(sheet, replacer, css_parser.replaceUrls(sheet, replacer,
ignoreImportRules=True) ignoreImportRules=True)
repl = sheet.cssText repl = sheet.cssText
if isbytestring(repl): if isbytestring(repl):
@ -150,7 +150,7 @@ class KF8Writer(object):
elif item.media_type in OEB_STYLES: elif item.media_type in OEB_STYLES:
sheet = self.data(item) sheet = self.data(item)
replacer = partial(pointer, item) replacer = partial(pointer, item)
cssutils.replaceUrls(sheet, replacer, ignoreImportRules=True) css_parser.replaceUrls(sheet, replacer, ignoreImportRules=True)
def extract_css_into_flows(self): def extract_css_into_flows(self):
inlines = defaultdict(list) # Ensure identical <style>s not repeated inlines = defaultdict(list) # Ensure identical <style>s not repeated
@ -194,7 +194,7 @@ class KF8Writer(object):
if not raw or not raw.strip(): if not raw or not raw.strip():
extract(tag) extract(tag)
continue continue
sheet = cssutils.parseString(raw, validate=False) sheet = css_parser.parseString(raw, validate=False)
if fix_import_rules(sheet): if fix_import_rules(sheet):
raw = force_unicode(sheet.cssText, 'utf-8') raw = force_unicode(sheet.cssText, 'utf-8')

View File

@ -9,8 +9,8 @@ Convert an ODT file into a Open Ebook
import os, logging import os, logging
from lxml import etree from lxml import etree
from cssutils import CSSParser from css_parser import CSSParser
from cssutils.css import CSSRule from css_parser.css import CSSRule
from odf.odf2xhtml import ODF2XHTML from odf.odf2xhtml import ODF2XHTML
from odf.opendocument import load as odLoad from odf.opendocument import load as odLoad
@ -184,8 +184,8 @@ class Extract(ODF2XHTML):
x.set('class', orig + ' ' + ' '.join(extra)) x.set('class', orig + ' ' + ' '.join(extra))
def do_filter_css(self, css): def do_filter_css(self, css):
from cssutils import parseString from css_parser import parseString
from cssutils.css import CSSRule from css_parser.css import CSSRule
sheet = parseString(css, validate=False) sheet = parseString(css, validate=False)
rules = list(sheet.cssRules.rulesOfType(CSSRule.STYLE_RULE)) rules = list(sheet.cssRules.rulesOfType(CSSRule.STYLE_RULE))
sel_map = {} sel_map = {}
@ -301,7 +301,3 @@ class Extract(ODF2XHTML):
with open('metadata.opf', 'wb') as f: with open('metadata.opf', 'wb') as f:
opf.render(f) opf.render(f)
return os.path.abspath('metadata.opf') return os.path.abspath('metadata.opf')

View File

@ -217,7 +217,7 @@ def rewrite_links(root, link_repl_func, resolve_base_href=False):
If the ``link_repl_func`` returns None, the attribute or If the ``link_repl_func`` returns None, the attribute or
tag text will be removed completely. tag text will be removed completely.
''' '''
from cssutils import replaceUrls, log, CSSParser from css_parser import replaceUrls, log, CSSParser
log.setLevel(logging.WARN) log.setLevel(logging.WARN)
log.raiseExceptions = False log.raiseExceptions = False
@ -269,7 +269,7 @@ def rewrite_links(root, link_repl_func, resolve_base_href=False):
try: try:
stext = parser.parseStyle(text, validate=False) stext = parser.parseStyle(text, validate=False)
except Exception: except Exception:
# Parsing errors are raised by cssutils # Parsing errors are raised by css_parser
continue continue
replaceUrls(stext, link_repl_func) replaceUrls(stext, link_repl_func)
repl = stext.cssText.replace('\n', ' ').replace('\r', repl = stext.cssText.replace('\n', ' ').replace('\r',
@ -971,8 +971,8 @@ class Manifest(object):
return self._parse_xhtml(convert_markdown(data, title=title)) return self._parse_xhtml(convert_markdown(data, title=title))
def _parse_css(self, data): def _parse_css(self, data):
from cssutils import CSSParser, log, resolveImports from css_parser import CSSParser, log, resolveImports
from cssutils.css import CSSRule from css_parser.css import CSSRule
log.setLevel(logging.WARN) log.setLevel(logging.WARN)
log.raiseExceptions = False log.raiseExceptions = False
self.oeb.log.debug('Parsing', self.href, '...') self.oeb.log.debug('Parsing', self.href, '...')
@ -1011,7 +1011,7 @@ class Manifest(object):
convert and return as an lxml.etree element in the XHTML convert and return as an lxml.etree element in the XHTML
namespace. namespace.
- XML content is parsed and returned as an lxml.etree element. - XML content is parsed and returned as an lxml.etree element.
- CSS and CSS-variant content is parsed and returned as a cssutils - CSS and CSS-variant content is parsed and returned as a css_parser
CSS DOM stylesheet. CSS DOM stylesheet.
- All other content is returned as a :class:`str` object with no - All other content is returned as a :class:`str` object with no
special parsing. special parsing.

View File

@ -9,11 +9,8 @@ __copyright__ = '2013, Kovid Goyal <kovid at kovidgoyal.net>'
from polyglot.builtins import zip from polyglot.builtins import zip
from functools import wraps from functools import wraps
try: from css_parser.css import PropertyValue
from cssutils.css import PropertyValue from css_parser import profile as cssprofiles, CSSParser
except ImportError:
raise RuntimeError('You need cssutils >= 0.9.9 for calibre')
from cssutils import profile as cssprofiles, CSSParser
from tinycss.fonts3 import parse_font, serialize_font_family from tinycss.fonts3 import parse_font, serialize_font_family
DEFAULTS = {'azimuth': 'center', 'background-attachment': 'scroll', # {{{ DEFAULTS = {'azimuth': 'center', 'background-attachment': 'scroll', # {{{
@ -269,7 +266,7 @@ def condense_sheet(sheet):
def test_normalization(return_tests=False): # {{{ def test_normalization(return_tests=False): # {{{
import unittest import unittest
from cssutils import parseStyle from css_parser import parseStyle
from itertools import product from itertools import product
class TestNormalization(unittest.TestCase): class TestNormalization(unittest.TestCase):

View File

@ -10,7 +10,7 @@ from itertools import count
from operator import itemgetter from operator import itemgetter
import re import re
from cssutils.css import CSSStyleSheet, CSSRule, Property from css_parser.css import CSSStyleSheet, CSSRule, Property
from css_selectors import Select, INAPPROPRIATE_PSEUDO_CLASSES, SelectorError from css_selectors import Select, INAPPROPRIATE_PSEUDO_CLASSES, SelectorError
from calibre import as_unicode from calibre import as_unicode
@ -45,7 +45,8 @@ def iterrules(container, sheet_name, rules=None, media_rule_ok=media_allowed, ru
:param sheet_name: The name of the sheet in the container (in case of inline style sheets, the name of the html file) :param sheet_name: The name of the sheet in the container (in case of inline style sheets, the name of the html file)
:param media_rule_ok: A function to test if a @media rule is allowed :param media_rule_ok: A function to test if a @media rule is allowed
:param rule_index_counter: A counter object, rule numbers will be calculated by incrementing the counter. :param rule_index_counter: A counter object, rule numbers will be calculated by incrementing the counter.
:param rule_type: Only yield rules of this type, where type is a string type name, see cssutils.css.CSSRule for the names (by default all rules are yielded) :param rule_type: Only yield rules of this type, where type is a string type name, see css_parser.css.CSSRule for the names (
by default all rules are yielded)
:return: (CSSRule object, the name of the sheet from which it comes, rule index - a monotonically increasing number) :return: (CSSRule object, the name of the sheet from which it comes, rule index - a monotonically increasing number)
''' '''
@ -104,7 +105,7 @@ def iterdeclaration(decl):
class Values(tuple): class Values(tuple):
''' A tuple of `cssutils.css.Value ` (and its subclasses) objects. Also has a ''' A tuple of `css_parser.css.Value ` (and its subclasses) objects. Also has a
`sheet_name` attribute that is the canonical name relative to which URLs `sheet_name` attribute that is the canonical name relative to which URLs
for this property should be resolved. ''' for this property should be resolved. '''

View File

@ -6,7 +6,7 @@ from __future__ import (unicode_literals, division, absolute_import,
__license__ = 'GPL v3' __license__ = 'GPL v3'
__copyright__ = '2013, Kovid Goyal <kovid at kovidgoyal.net>' __copyright__ = '2013, Kovid Goyal <kovid at kovidgoyal.net>'
from cssutils.css import CSSRule from css_parser.css import CSSRule
from calibre import force_unicode from calibre import force_unicode
from calibre.ebooks.oeb.base import OEB_DOCS, OEB_STYLES from calibre.ebooks.oeb.base import OEB_DOCS, OEB_STYLES

View File

@ -50,7 +50,7 @@ def run_checks(container):
if err.level > WARN: if err.level > WARN:
return errors return errors
# cssutils is not thread safe # css_parser is not thread safe
for name, mt, raw in stylesheets: for name, mt, raw in stylesheets:
if not raw: if not raw:
errors.append(EmptyFile(name)) errors.append(EmptyFile(name))
@ -107,4 +107,3 @@ def fix_errors(container, errors):
# better to have a false positive than a false negative) # better to have a false positive than a false negative)
changed = True changed = True
return changed return changed

View File

@ -9,7 +9,7 @@ __copyright__ = '2013, Kovid Goyal <kovid at kovidgoyal.net>'
import re import re
from lxml.etree import XMLParser, fromstring, XMLSyntaxError from lxml.etree import XMLParser, fromstring, XMLSyntaxError
import cssutils import css_parser
from calibre import force_unicode, human_readable, prepare_string_for_xml from calibre import force_unicode, human_readable, prepare_string_for_xml
from calibre.ebooks.chardet import replace_encoding_declarations, find_declared_encoding from calibre.ebooks.chardet import replace_encoding_declarations, find_declared_encoding
@ -432,7 +432,7 @@ class BareTextInBody(BaseError):
class ErrorHandler(object): class ErrorHandler(object):
' Replacement logger to get useful error/warning info out of cssutils during parsing ' ' Replacement logger to get useful error/warning info out of css_parser during parsing '
def __init__(self, name): def __init__(self, name):
# may be disabled during setting of known valid items # may be disabled during setting of known valid items
@ -467,7 +467,7 @@ class ErrorHandler(object):
def check_css_parsing(name, raw, line_offset=0, is_declaration=False): def check_css_parsing(name, raw, line_offset=0, is_declaration=False):
log = ErrorHandler(name) log = ErrorHandler(name)
parser = cssutils.CSSParser(fetcher=lambda x: (None, None), log=log) parser = css_parser.CSSParser(fetcher=lambda x: (None, None), log=log)
if is_declaration: if is_declaration:
parser.parseStyle(raw, validate=True) parser.parseStyle(raw, validate=True)
else: else:

View File

@ -19,7 +19,7 @@ from io import BytesIO
from itertools import count from itertools import count
from urlparse import urlparse from urlparse import urlparse
from cssutils import getUrls, replaceUrls from css_parser import getUrls, replaceUrls
from lxml import etree from lxml import etree
from calibre import CurrentDir, walk from calibre import CurrentDir, walk
@ -589,7 +589,7 @@ class Container(ContainerBase): # {{{
def parsed(self, name): def parsed(self, name):
''' Return a parsed representation of the file specified by name. For ''' Return a parsed representation of the file specified by name. For
HTML and XML files an lxml tree is returned. For CSS files a cssutils HTML and XML files an lxml tree is returned. For CSS files a css_parser
stylesheet is returned. Note that parsed objects are cached for stylesheet is returned. Note that parsed objects are cached for
performance. If you make any changes to the parsed object, you must performance. If you make any changes to the parsed object, you must
call :meth:`dirty` so that the container knows to update the cache. See also :meth:`replace`.''' call :meth:`dirty` so that the container knows to update the cache. See also :meth:`replace`.'''
@ -605,7 +605,7 @@ class Container(ContainerBase): # {{{
def replace(self, name, obj): def replace(self, name, obj):
''' '''
Replace the parsed object corresponding to name with obj, which must be Replace the parsed object corresponding to name with obj, which must be
a similar object, i.e. an lxml tree for HTML/XML or a cssutils a similar object, i.e. an lxml tree for HTML/XML or a css_parser
stylesheet for a CSS file. stylesheet for a CSS file.
''' '''
self.parsed_cache[name] = obj self.parsed_cache[name] = obj

View File

@ -9,7 +9,7 @@ __copyright__ = '2014, Kovid Goyal <kovid at kovidgoyal.net>'
from collections import defaultdict from collections import defaultdict
from functools import partial from functools import partial
from cssutils.css import CSSRule, CSSStyleDeclaration from css_parser.css import CSSRule, CSSStyleDeclaration
from css_selectors import parse, SelectorSyntaxError from css_selectors import parse, SelectorSyntaxError
from calibre import force_unicode from calibre import force_unicode
@ -208,7 +208,7 @@ def filter_declaration(style, properties=()):
def filter_sheet(sheet, properties=()): def filter_sheet(sheet, properties=()):
from cssutils.css import CSSRule from css_parser.css import CSSRule
changed = False changed = False
remove = [] remove = []
for rule in sheet.cssRules.rulesOfType(CSSRule.STYLE_RULE): for rule in sheet.cssRules.rulesOfType(CSSRule.STYLE_RULE):

View File

@ -8,7 +8,7 @@ __copyright__ = '2016, Kovid Goyal <kovid at kovidgoyal.net>'
from functools import partial from functools import partial
from cssutils import parseStyle from css_parser import parseStyle
from calibre.constants import iswindows from calibre.constants import iswindows
from calibre.ebooks.oeb.base import OEB_STYLES, OEB_DOCS from calibre.ebooks.oeb.base import OEB_STYLES, OEB_DOCS

View File

@ -16,9 +16,9 @@ def guess_type(x):
return _guess_type(x)[0] or 'application/octet-stream' return _guess_type(x)[0] or 'application/octet-stream'
def setup_cssutils_serialization(tab_width=2): def setup_css_parser_serialization(tab_width=2):
import cssutils import css_parser
prefs = cssutils.ser.prefs prefs = css_parser.ser.prefs
prefs.indent = tab_width * ' ' prefs.indent = tab_width * ' '
prefs.indentClosingBrace = False prefs.indentClosingBrace = False
prefs.omitLastSemicolon = False prefs.omitLastSemicolon = False
@ -163,7 +163,7 @@ def parse_css(data, fname='<string>', is_declaration=False, decode=None, log_lev
if log_level is None: if log_level is None:
import logging import logging
log_level = logging.WARNING log_level = logging.WARNING
from cssutils import CSSParser, log from css_parser import CSSParser, log
from calibre.ebooks.oeb.base import _css_logger from calibre.ebooks.oeb.base import _css_logger
log.setLevel(log_level) log.setLevel(log_level)
log.raiseExceptions = False log.raiseExceptions = False

View File

@ -185,7 +185,7 @@ class OEBReader(object):
return bad return bad
def _manifest_add_missing(self, invalid): def _manifest_add_missing(self, invalid):
import cssutils import css_parser
manifest = self.oeb.manifest manifest = self.oeb.manifest
known = set(manifest.hrefs) known = set(manifest.hrefs)
unchecked = set(manifest.values()) unchecked = set(manifest.values())
@ -225,7 +225,7 @@ class OEBReader(object):
new.add(href) new.add(href)
elif item.media_type in OEB_STYLES: elif item.media_type in OEB_STYLES:
try: try:
urls = list(cssutils.getUrls(data)) urls = list(css_parser.getUrls(data))
except: except:
urls = [] urls = []
for url in urls: for url in urls:

View File

@ -11,10 +11,10 @@ __copyright__ = '2008, Marshall T. Vandegrift <llasram@gmail.com>'
import os, re, logging, copy, unicodedata import os, re, logging, copy, unicodedata
from weakref import WeakKeyDictionary from weakref import WeakKeyDictionary
from xml.dom import SyntaxErr as CSSSyntaxError from xml.dom import SyntaxErr as CSSSyntaxError
from cssutils.css import (CSSStyleRule, CSSPageRule, CSSFontFaceRule, from css_parser.css import (CSSStyleRule, CSSPageRule, CSSFontFaceRule,
cssproperties) cssproperties)
from cssutils import (profile as cssprofiles, parseString, parseStyle, log as from css_parser import (profile as cssprofiles, parseString, parseStyle, log as
cssutils_log, CSSParser, profiles, replaceUrls) css_parser_log, CSSParser, profiles, replaceUrls)
from calibre import force_unicode, as_unicode from calibre import force_unicode, as_unicode
from calibre.ebooks import unit_convert from calibre.ebooks import unit_convert
from calibre.ebooks.oeb.base import XHTML, XHTML_NS, CSS_MIME, OEB_STYLES, xpath, urlnormalize from calibre.ebooks.oeb.base import XHTML, XHTML_NS, CSS_MIME, OEB_STYLES, xpath, urlnormalize
@ -22,7 +22,7 @@ from calibre.ebooks.oeb.normalize_css import DEFAULTS, normalizers
from css_selectors import Select, SelectorError, INAPPROPRIATE_PSEUDO_CLASSES from css_selectors import Select, SelectorError, INAPPROPRIATE_PSEUDO_CLASSES
from tinycss.media3 import CSSMedia3Parser from tinycss.media3 import CSSMedia3Parser
cssutils_log.setLevel(logging.WARN) css_parser_log.setLevel(logging.WARN)
_html_css_stylesheet = None _html_css_stylesheet = None
@ -123,7 +123,7 @@ class Stylizer(object):
stylesheets.append(parseString(base_css, validate=False)) stylesheets.append(parseString(base_css, validate=False))
style_tags = xpath(tree, '//*[local-name()="style" or local-name()="link"]') style_tags = xpath(tree, '//*[local-name()="style" or local-name()="link"]')
# Add cssutils parsing profiles from output_profile # Add css_parser parsing profiles from output_profile
for profile in self.opts.output_profile.extra_css_modules: for profile in self.opts.output_profile.extra_css_modules:
cssprofiles.addProfile(profile['name'], cssprofiles.addProfile(profile['name'],
profile['props'], profile['props'],

View File

@ -9,7 +9,7 @@ __copyright__ = '2013, Kovid Goyal <kovid at kovidgoyal.net>'
import logging import logging
from collections import defaultdict from collections import defaultdict
import cssutils import css_parser
from lxml import etree from lxml import etree
from calibre import guess_type from calibre import guess_type
@ -97,7 +97,7 @@ class EmbedFonts(object):
self.sheet_cache = {} self.sheet_cache = {}
self.find_style_rules() self.find_style_rules()
self.find_embedded_fonts() self.find_embedded_fonts()
self.parser = cssutils.CSSParser(loglevel=logging.CRITICAL, log=logging.getLogger('calibre.css')) self.parser = css_parser.CSSParser(loglevel=logging.CRITICAL, log=logging.getLogger('calibre.css'))
self.warned = set() self.warned = set()
self.warned2 = set() self.warned2 = set()
self.newly_embedded_fonts = set() self.newly_embedded_fonts = set()

View File

@ -25,7 +25,7 @@ class RenameFiles(object): # {{{
self.renamed_items_map = renamed_items_map self.renamed_items_map = renamed_items_map
def __call__(self, oeb, opts): def __call__(self, oeb, opts):
import cssutils import css_parser
self.log = oeb.logger self.log = oeb.logger
self.opts = opts self.opts = opts
self.oeb = oeb self.oeb = oeb
@ -35,7 +35,7 @@ class RenameFiles(object): # {{{
if etree.iselement(item.data): if etree.iselement(item.data):
rewrite_links(self.current_item.data, self.url_replacer) rewrite_links(self.current_item.data, self.url_replacer)
elif hasattr(item.data, 'cssText'): elif hasattr(item.data, 'cssText'):
cssutils.replaceUrls(item.data, self.url_replacer) css_parser.replaceUrls(item.data, self.url_replacer)
if self.oeb.guide: if self.oeb.guide:
for ref in self.oeb.guide.values(): for ref in self.oeb.guide.values():
@ -184,4 +184,3 @@ class FlatFilenames(object): # {{{
renamer = RenameFiles(self.rename_map, self.renamed_items_map) renamer = RenameFiles(self.rename_map, self.renamed_items_map)
renamer(oeb, opts) renamer(oeb, opts)
# }}} # }}}

View File

@ -11,8 +11,8 @@ from collections import defaultdict
from xml.dom import SyntaxErr from xml.dom import SyntaxErr
from lxml import etree from lxml import etree
import cssutils import css_parser
from cssutils.css import Property from css_parser.css import Property
from calibre import guess_type from calibre import guess_type
from calibre.ebooks import unit_convert from calibre.ebooks import unit_convert
@ -139,7 +139,7 @@ class EmbedFontsCSSRules(object):
iid, href = oeb.manifest.generate(u'page_styles', u'page_styles.css') iid, href = oeb.manifest.generate(u'page_styles', u'page_styles.css')
rules = [x.cssText for x in self.rules] rules = [x.cssText for x in self.rules]
rules = u'\n\n'.join(rules) rules = u'\n\n'.join(rules)
sheet = cssutils.parseString(rules, validate=False) sheet = css_parser.parseString(rules, validate=False)
self.href = oeb.manifest.add(iid, href, guess_type(href)[0], self.href = oeb.manifest.add(iid, href, guess_type(href)[0],
data=sheet).href data=sheet).href
return self.href return self.href
@ -203,7 +203,7 @@ class CSSFlattener(object):
# Make all links to resources absolute, as these sheets will be # Make all links to resources absolute, as these sheets will be
# consolidated into a single stylesheet at the root of the document # consolidated into a single stylesheet at the root of the document
if item.media_type in OEB_STYLES: if item.media_type in OEB_STYLES:
cssutils.replaceUrls(item.data, item.abshref, css_parser.replaceUrls(item.data, item.abshref,
ignoreImportRules=True) ignoreImportRules=True)
self.body_font_family, self.embed_font_rules = self.get_embed_font_info( self.body_font_family, self.embed_font_rules = self.get_embed_font_info(
@ -278,7 +278,7 @@ class CSSFlattener(object):
cfont[k] = font[k] cfont[k] = font[k]
rule = '@font-face { %s }'%('; '.join(u'%s:%s'%(k, v) for k, v in rule = '@font-face { %s }'%('; '.join(u'%s:%s'%(k, v) for k, v in
cfont.iteritems())) cfont.iteritems()))
rule = cssutils.parseString(rule) rule = css_parser.parseString(rule)
efi.append(rule) efi.append(rule)
return body_font_family, efi return body_font_family, efi
@ -615,7 +615,7 @@ class CSSFlattener(object):
if item.media_type in OEB_STYLES: if item.media_type in OEB_STYLES:
manifest.remove(item) manifest.remove(item)
id, href = manifest.generate('css', 'stylesheet.css') id, href = manifest.generate('css', 'stylesheet.css')
sheet = cssutils.parseString(css, validate=False) sheet = css_parser.parseString(css, validate=False)
if self.transform_css_rules: if self.transform_css_rules:
from calibre.ebooks.css_transform_rules import transform_sheet from calibre.ebooks.css_transform_rules import transform_sheet
transform_sheet(self.transform_css_rules, sheet) transform_sheet(self.transform_css_rules, sheet)
@ -647,7 +647,7 @@ class CSSFlattener(object):
href = None href = None
if css.strip(): if css.strip():
id_, href = manifest.generate('page_css', 'page_styles.css') id_, href = manifest.generate('page_css', 'page_styles.css')
sheet = cssutils.parseString(css, validate=False) sheet = css_parser.parseString(css, validate=False)
if self.transform_css_rules: if self.transform_css_rules:
from calibre.ebooks.css_transform_rules import transform_sheet from calibre.ebooks.css_transform_rules import transform_sheet
transform_sheet(self.transform_css_rules, sheet) transform_sheet(self.transform_css_rules, sheet)

View File

@ -65,7 +65,7 @@ class RemoveFakeMargins(object):
stylesheet = stylesheet.data stylesheet = stylesheet.data
from cssutils.css import CSSRule from css_parser.css import CSSRule
for rule in stylesheet.cssRules.rulesOfType(CSSRule.STYLE_RULE): for rule in stylesheet.cssRules.rulesOfType(CSSRule.STYLE_RULE):
self.selector_map[rule.selectorList.selectorText] = rule.style self.selector_map[rule.selectorList.selectorText] = rule.style

View File

@ -23,7 +23,7 @@ class ManifestTrimmer(object):
return cls() return cls()
def __call__(self, oeb, context): def __call__(self, oeb, context):
import cssutils import css_parser
oeb.logger.info('Trimming unused files from manifest...') oeb.logger.info('Trimming unused files from manifest...')
self.opts = context self.opts = context
used = set() used = set()
@ -60,7 +60,7 @@ class ManifestTrimmer(object):
if found not in used: if found not in used:
new.add(found) new.add(found)
elif item.media_type == CSS_MIME: elif item.media_type == CSS_MIME:
for href in cssutils.getUrls(item.data): for href in css_parser.getUrls(item.data):
href = item.abshref(urlnormalize(href)) href = item.abshref(urlnormalize(href))
if href in oeb.manifest.hrefs: if href in oeb.manifest.hrefs:
found = oeb.manifest.hrefs[href] found = oeb.manifest.hrefs[href]

View File

@ -26,7 +26,7 @@ from calibre.ebooks.oeb.polish.pretty import fix_all_html, pretty_all
from calibre.ebooks.oeb.polish.replace import rename_files, replace_file, get_recommended_folders, rationalize_folders from calibre.ebooks.oeb.polish.replace import rename_files, replace_file, get_recommended_folders, rationalize_folders
from calibre.ebooks.oeb.polish.split import split, merge, AbortError, multisplit from calibre.ebooks.oeb.polish.split import split, merge, AbortError, multisplit
from calibre.ebooks.oeb.polish.toc import remove_names_from_toc, create_inline_toc from calibre.ebooks.oeb.polish.toc import remove_names_from_toc, create_inline_toc
from calibre.ebooks.oeb.polish.utils import link_stylesheets, setup_cssutils_serialization as scs from calibre.ebooks.oeb.polish.utils import link_stylesheets, setup_css_parser_serialization as scs
from calibre.gui2 import error_dialog, choose_files, question_dialog, info_dialog, choose_save_file, open_url, choose_dir, add_to_recent_docs from calibre.gui2 import error_dialog, choose_files, question_dialog, info_dialog, choose_save_file, open_url, choose_dir, add_to_recent_docs
from calibre.gui2.dialogs.confirm_delete import confirm from calibre.gui2.dialogs.confirm_delete import confirm
from calibre.gui2.tweak_book import ( from calibre.gui2.tweak_book import (
@ -61,7 +61,7 @@ def get_container(*args, **kwargs):
return container return container
def setup_cssutils_serialization(): def setup_css_parser_serialization():
scs(tprefs['editor_tab_stop_width']) scs(tprefs['editor_tab_stop_width'])
@ -91,7 +91,7 @@ class Boss(QObject):
self.save_manager.check_for_completion.connect(self.check_terminal_save) self.save_manager.check_for_completion.connect(self.check_terminal_save)
self.doing_terminal_save = False self.doing_terminal_save = False
self.ignore_preview_to_editor_sync = False self.ignore_preview_to_editor_sync = False
setup_cssutils_serialization() setup_css_parser_serialization()
get_boss.boss = self get_boss.boss = self
self.gui = parent self.gui = parent
completion_worker().result_callback = self.handle_completion_result_signal.emit completion_worker().result_callback = self.handle_completion_result_signal.emit
@ -172,7 +172,7 @@ class Boss(QObject):
bar.setIconSize(QSize(tprefs['toolbar_icon_size'], tprefs['toolbar_icon_size'])) bar.setIconSize(QSize(tprefs['toolbar_icon_size'], tprefs['toolbar_icon_size']))
if ret == p.Accepted: if ret == p.Accepted:
setup_cssutils_serialization() setup_css_parser_serialization()
self.gui.apply_settings() self.gui.apply_settings()
self.refresh_file_list() self.refresh_file_list()
if ret == p.Accepted or p.dictionaries_changed: if ret == p.Accepted or p.dictionaries_changed:

View File

@ -53,9 +53,9 @@ def beautify_text(raw, syntax):
elif syntax == 'css': elif syntax == 'css':
import logging import logging
from calibre.ebooks.oeb.base import serialize, _css_logger from calibre.ebooks.oeb.base import serialize, _css_logger
from calibre.ebooks.oeb.polish.utils import setup_cssutils_serialization from calibre.ebooks.oeb.polish.utils import setup_css_parser_serialization
from cssutils import CSSParser, log from css_parser import CSSParser, log
setup_cssutils_serialization(tprefs['editor_tab_stop_width']) setup_css_parser_serialization(tprefs['editor_tab_stop_width'])
log.setLevel(logging.WARN) log.setLevel(logging.WARN)
log.raiseExceptions = False log.raiseExceptions = False
parser = CSSParser(loglevel=logging.WARNING, parser = CSSParser(loglevel=logging.WARNING,

View File

@ -10,7 +10,7 @@ import sys, re
from operator import itemgetter from operator import itemgetter
from itertools import chain from itertools import chain
from cssutils import parseStyle from css_parser import parseStyle
from PyQt5.Qt import QTextEdit, Qt, QTextCursor from PyQt5.Qt import QTextEdit, Qt, QTextCursor
from calibre import prepare_string_for_xml, xml_entity_to_unicode from calibre import prepare_string_for_xml, xml_entity_to_unicode

View File

@ -13,8 +13,8 @@ from polyglot.builtins import map
from urlparse import urlparse from urlparse import urlparse
from urllib import quote from urllib import quote
from cssutils import replaceUrls from css_parser import replaceUrls
from cssutils.css import CSSRule from css_parser.css import CSSRule
from calibre import prepare_string_for_xml, force_unicode from calibre import prepare_string_for_xml, force_unicode
from calibre.ebooks import parse_css_length from calibre.ebooks import parse_css_length

View File

@ -50,7 +50,7 @@ def serialize_single_font_family(x):
xl = 'sans-serif' xl = 'sans-serif'
return xl return xl
if SIMPLE_NAME_PAT.match(x) is not None and not x.lower().startswith('and'): if SIMPLE_NAME_PAT.match(x) is not None and not x.lower().startswith('and'):
# cssutils dies if a font name starts with and # css_parser dies if a font name starts with and
return x return x
return '"%s"' % x.replace('"', r'\"') return '"%s"' % x.replace('"', r'\"')
@ -58,6 +58,7 @@ def serialize_single_font_family(x):
def serialize_font_family(families): def serialize_font_family(families):
return ', '.join(map(serialize_single_font_family, families)) return ', '.join(map(serialize_single_font_family, families))
GLOBAL_IDENTS = frozenset('inherit initial unset normal'.split()) GLOBAL_IDENTS = frozenset('inherit initial unset normal'.split())
STYLE_IDENTS = frozenset('italic oblique'.split()) STYLE_IDENTS = frozenset('italic oblique'.split())
VARIANT_IDENTS = frozenset(('small-caps',)) VARIANT_IDENTS = frozenset(('small-caps',))