mirror of
https://github.com/kovidgoyal/calibre.git
synced 2025-07-09 03:04:10 -04:00
Switch to using lxml.cssselect instead of my home-rolled selector.
This commit is contained in:
parent
f6c1a684c3
commit
30c2325913
@ -392,22 +392,6 @@ spacer {
|
|||||||
float: none ! important;
|
float: none ! important;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* focusable content: anything w/ tabindex >=0 is focusable */
|
|
||||||
abbr:focus, acronym:focus, address:focus, applet:focus, b:focus,
|
|
||||||
base:focus, big:focus, blockquote:focus, br:focus, canvas:focus, caption:focus,
|
|
||||||
center:focus, cite:focus, code:focus, col:focus, colgroup:focus, dd:focus,
|
|
||||||
del:focus, dfn:focus, dir:focus, div:focus, dl:focus, dt:focus, em:focus,
|
|
||||||
fieldset:focus, font:focus, form:focus, h1:focus, h2:focus, h3:focus, h4:focus,
|
|
||||||
h5:focus, h6:focus, hr:focus, i:focus, img:focus, ins:focus,
|
|
||||||
kbd:focus, label:focus, legend:focus, li:focus, link:focus, menu:focus,
|
|
||||||
object:focus, ol:focus, p:focus, pre:focus, q:focus, s:focus, samp:focus,
|
|
||||||
small:focus, span:focus, strike:focus, strong:focus, sub:focus, sup:focus,
|
|
||||||
table:focus, tbody:focus, td:focus, tfoot:focus, th:focus, thead:focus,
|
|
||||||
tr:focus, tt:focus, u:focus, ul:focus, var:focus {
|
|
||||||
/* Don't specify the outline-color, we should always use initial value. */
|
|
||||||
outline: 1px dotted;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* hidden elements */
|
/* hidden elements */
|
||||||
area, base, basefont, head, meta, script, style, title,
|
area, base, basefont, head, meta, script, style, title,
|
||||||
noembed, param, link {
|
noembed, param, link {
|
||||||
|
@ -21,12 +21,14 @@ import cssutils
|
|||||||
from cssutils.css import CSSStyleRule, CSSPageRule, CSSStyleDeclaration, \
|
from cssutils.css import CSSStyleRule, CSSPageRule, CSSStyleDeclaration, \
|
||||||
CSSValueList, cssproperties
|
CSSValueList, cssproperties
|
||||||
from lxml import etree
|
from lxml import etree
|
||||||
|
from lxml.cssselect import css_to_xpath, ExpressionError
|
||||||
from calibre.ebooks.oeb.base import XHTML, XHTML_NS, CSS_MIME, OEB_STYLES
|
from calibre.ebooks.oeb.base import XHTML, XHTML_NS, CSS_MIME, OEB_STYLES
|
||||||
from calibre.ebooks.oeb.base import barename, urlnormalize
|
from calibre.ebooks.oeb.base import barename, urlnormalize
|
||||||
from calibre.resources import html_css
|
from calibre.resources import html_css
|
||||||
|
|
||||||
HTML_CSS_STYLESHEET = cssutils.parseString(html_css)
|
|
||||||
XHTML_CSS_NAMESPACE = "@namespace url(%s);\n" % XHTML_NS
|
XHTML_CSS_NAMESPACE = "@namespace url(%s);\n" % XHTML_NS
|
||||||
|
HTML_CSS_STYLESHEET = cssutils.parseString(html_css)
|
||||||
|
HTML_CSS_STYLESHEET.namespaces['h'] = XHTML_NS
|
||||||
|
|
||||||
INHERITED = set(['azimuth', 'border-collapse', 'border-spacing',
|
INHERITED = set(['azimuth', 'border-collapse', 'border-spacing',
|
||||||
'caption-side', 'color', 'cursor', 'direction', 'elevation',
|
'caption-side', 'color', 'cursor', 'direction', 'elevation',
|
||||||
@ -97,6 +99,18 @@ XPNSMAP = {'h': XHTML_NS,}
|
|||||||
def xpath(elem, expr):
|
def xpath(elem, expr):
|
||||||
return elem.xpath(expr, namespaces=XPNSMAP)
|
return elem.xpath(expr, namespaces=XPNSMAP)
|
||||||
|
|
||||||
|
class CSSSelector(etree.XPath):
|
||||||
|
def __init__(self, css, namespaces=XPNSMAP):
|
||||||
|
path = css_to_xpath(css)
|
||||||
|
etree.XPath.__init__(self, path, namespaces=namespaces)
|
||||||
|
self.css = css
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return '<%s %s for %r>' % (
|
||||||
|
self.__class__.__name__,
|
||||||
|
hex(abs(id(self)))[2:],
|
||||||
|
self.css)
|
||||||
|
|
||||||
|
|
||||||
class Page(object):
|
class Page(object):
|
||||||
def __init__(self, width, height, dpi, fbase, fsizes):
|
def __init__(self, width, height, dpi, fbase, fsizes):
|
||||||
@ -132,6 +146,7 @@ class Stylizer(object):
|
|||||||
and elem.get('type', CSS_MIME) in OEB_STYLES:
|
and elem.get('type', CSS_MIME) in OEB_STYLES:
|
||||||
text = XHTML_CSS_NAMESPACE + elem.text
|
text = XHTML_CSS_NAMESPACE + elem.text
|
||||||
stylesheet = parser.parseString(text, href=cssname)
|
stylesheet = parser.parseString(text, href=cssname)
|
||||||
|
stylesheet.namespaces['h'] = XHTML_NS
|
||||||
stylesheets.append(stylesheet)
|
stylesheets.append(stylesheet)
|
||||||
elif elem.tag == XHTML('link') and elem.get('href') \
|
elif elem.tag == XHTML('link') and elem.get('href') \
|
||||||
and elem.get('rel', 'stylesheet') == 'stylesheet' \
|
and elem.get('rel', 'stylesheet') == 'stylesheet' \
|
||||||
@ -145,6 +160,7 @@ class Stylizer(object):
|
|||||||
data = XHTML_CSS_NAMESPACE
|
data = XHTML_CSS_NAMESPACE
|
||||||
data += oeb.manifest.hrefs[path].data
|
data += oeb.manifest.hrefs[path].data
|
||||||
stylesheet = parser.parseString(data, href=path)
|
stylesheet = parser.parseString(data, href=path)
|
||||||
|
stylesheet.namespaces['h'] = XHTML_NS
|
||||||
self.STYLESHEETS[path] = stylesheet
|
self.STYLESHEETS[path] = stylesheet
|
||||||
stylesheets.append(stylesheet)
|
stylesheets.append(stylesheet)
|
||||||
rules = []
|
rules = []
|
||||||
@ -160,6 +176,16 @@ class Stylizer(object):
|
|||||||
rules.sort()
|
rules.sort()
|
||||||
self.rules = rules
|
self.rules = rules
|
||||||
self._styles = {}
|
self._styles = {}
|
||||||
|
for _, _, cssdict, text, _ in rules:
|
||||||
|
try:
|
||||||
|
selector = CSSSelector(text)
|
||||||
|
except ExpressionError:
|
||||||
|
continue
|
||||||
|
for elem in selector(tree):
|
||||||
|
self.style(elem)._update_cssdict(cssdict)
|
||||||
|
for elem in tree.xpath('//*[@style]'):
|
||||||
|
self.style(elem)._apply_style_tag()
|
||||||
|
|
||||||
|
|
||||||
def flatten_rule(self, rule, href, index):
|
def flatten_rule(self, rule, href, index):
|
||||||
results = []
|
results = []
|
||||||
@ -236,9 +262,10 @@ class Stylizer(object):
|
|||||||
return style
|
return style
|
||||||
|
|
||||||
def style(self, element):
|
def style(self, element):
|
||||||
try: return self._styles[element]
|
try:
|
||||||
except: pass
|
return self._styles[element]
|
||||||
return Style(element, self)
|
except KeyError:
|
||||||
|
return Style(element, self)
|
||||||
|
|
||||||
def stylesheet(self, name, font_scale=None):
|
def stylesheet(self, name, font_scale=None):
|
||||||
rules = []
|
rules = []
|
||||||
@ -259,69 +286,17 @@ class Style(object):
|
|||||||
self._element = element
|
self._element = element
|
||||||
self._page = stylizer.page
|
self._page = stylizer.page
|
||||||
self._stylizer = stylizer
|
self._stylizer = stylizer
|
||||||
self._style = self._assemble_style(element, stylizer)
|
self._style = {}
|
||||||
stylizer._styles[element] = self
|
stylizer._styles[element] = self
|
||||||
|
|
||||||
|
def _update_cssdict(self, cssdict):
|
||||||
|
self._style.update(cssdict)
|
||||||
|
|
||||||
def _assemble_style(self, element, stylizer):
|
def _apply_style_tag(self):
|
||||||
result = {}
|
attrib = self._element.attrib
|
||||||
rules = stylizer.rules
|
if 'style' in attrib:
|
||||||
for _, selector, style, _, _ in rules:
|
style = CSSStyleDeclaration(attrib['style'])
|
||||||
if self._selects_element(element, selector):
|
self._style.update(self._stylizer.flatten_style(style))
|
||||||
result.update(style)
|
|
||||||
try:
|
|
||||||
style = CSSStyleDeclaration(element.attrib['style'])
|
|
||||||
result.update(stylizer.flatten_style(style))
|
|
||||||
except KeyError:
|
|
||||||
pass
|
|
||||||
return result
|
|
||||||
|
|
||||||
def _selects_element(self, element, selector):
|
|
||||||
def _selects_element(element, items, index):
|
|
||||||
if index == -1:
|
|
||||||
return True
|
|
||||||
item = items[index]
|
|
||||||
if item.type == 'universal':
|
|
||||||
pass
|
|
||||||
elif item.type == 'type-selector':
|
|
||||||
name1 = ("{%s}%s" % item.value).lower()
|
|
||||||
name2 = element.tag.lower()
|
|
||||||
if name1 != name2:
|
|
||||||
return False
|
|
||||||
elif item.type == 'id':
|
|
||||||
name1 = item.value[1:]
|
|
||||||
name2 = element.get('id', '')
|
|
||||||
if name1 != name2:
|
|
||||||
return False
|
|
||||||
elif item.type == 'class':
|
|
||||||
name = item.value[1:].lower()
|
|
||||||
classes = element.get('class', '').lower().split()
|
|
||||||
if name not in classes:
|
|
||||||
return False
|
|
||||||
elif item.type == 'child':
|
|
||||||
parent = element.getparent()
|
|
||||||
if parent is None:
|
|
||||||
return False
|
|
||||||
element = parent
|
|
||||||
elif item.type == 'descendant':
|
|
||||||
element = element.getparent()
|
|
||||||
while element is not None:
|
|
||||||
if _selects_element(element, items, index - 1):
|
|
||||||
return True
|
|
||||||
element = element.getparent()
|
|
||||||
return False
|
|
||||||
elif item.type == 'pseudo-class':
|
|
||||||
if item.value == ':first-child':
|
|
||||||
e = element.getprevious()
|
|
||||||
if e is not None:
|
|
||||||
return False
|
|
||||||
else:
|
|
||||||
return False
|
|
||||||
elif item.type == 'pseudo-element':
|
|
||||||
return False
|
|
||||||
else:
|
|
||||||
return False
|
|
||||||
return _selects_element(element, items, index - 1)
|
|
||||||
return _selects_element(element, selector, len(selector) - 1)
|
|
||||||
|
|
||||||
def _has_parent(self):
|
def _has_parent(self):
|
||||||
parent = self._element.getparent()
|
parent = self._element.getparent()
|
||||||
|
Loading…
x
Reference in New Issue
Block a user