%PDF- %PDF-
| Direktori : /lib/calibre/calibre/gui2/tweak_book/ |
| Current File : //lib/calibre/calibre/gui2/tweak_book/jump_to_class.py |
#!/usr/bin/env python3
# License: GPL v3 Copyright: 2021, Kovid Goyal <kovid at kovidgoyal.net>
from contextlib import suppress
from css_parser.css import CSSRule
from typing import List, NamedTuple, Optional, Tuple
from calibre.ebooks.oeb.parse_utils import barename
from calibre.ebooks.oeb.polish.container import get_container
from calibre.ebooks.oeb.polish.parsing import parse
from css_selectors import Select, SelectorError
class NoMatchingTagFound(KeyError):
pass
class NoMatchingRuleFound(KeyError):
pass
class RuleLocation(NamedTuple):
rule_address: List[int]
file_name: str
style_tag_address: Optional[Tuple[int, List[int]]] = None
def rule_matches_elem(rule, elem, select, class_name):
for selector in rule.selectorList:
if class_name in selector.selectorText:
with suppress(SelectorError):
if elem in select(selector.selectorText):
return True
return False
def find_first_rule_that_matches_elem(
container,
elem,
select,
class_name,
rules,
current_file_name,
recursion_level=0,
rule_address=None
):
# iterate over rules handling @import and @media rules returning a rule
# address for matching rule
if recursion_level > 16:
return None
rule_address = rule_address or []
num_comment_rules = 0
for i, rule in enumerate(rules):
if rule.type == CSSRule.STYLE_RULE:
if rule_matches_elem(rule, elem, select, class_name):
return RuleLocation(rule_address + [i - num_comment_rules], current_file_name)
elif rule.type == CSSRule.COMMENT:
num_comment_rules += 1
elif rule.type == CSSRule.MEDIA_RULE:
res = find_first_rule_that_matches_elem(
container, elem, select, class_name, rule.cssRules,
current_file_name, recursion_level + 1, rule_address + [i - num_comment_rules]
)
if res is not None:
return res
elif rule.type == CSSRule.IMPORT_RULE:
if not rule.href:
continue
sname = container.href_to_name(rule.href, current_file_name)
if sname:
try:
sheet = container.parsed(sname)
except Exception:
continue
if not hasattr(sheet, 'cssRules'):
continue
res = find_first_rule_that_matches_elem(
container, elem, select, class_name, sheet.cssRules, sname,
recursion_level + 1
)
if res is not None:
return res
return None
def find_first_matching_rule(
container, html_file_name, raw_html, class_data, lnum_attr='data-lnum'
):
lnum, tags = class_data['sourceline_address']
class_name = class_data['class']
root = parse(
raw_html,
decoder=lambda x: x.decode('utf-8'),
line_numbers=True,
linenumber_attribute=lnum_attr
)
tags_on_line = root.xpath(f'//*[@{lnum_attr}={lnum}]')
barenames = [barename(tag.tag) for tag in tags_on_line]
if barenames[:len(tags)] != tags:
raise NoMatchingTagFound(
f'No tag matching the specification was found in {html_file_name}'
)
target_elem = tags_on_line[len(tags) - 1]
select = Select(root, ignore_inappropriate_pseudo_classes=True)
for tag in root.iter('*'):
tn = barename(tag.tag)
if tn == 'style' and tag.text and tag.get('type', 'text/css') == 'text/css':
try:
sheet = container.parse_css(tag.text)
except Exception:
continue
res = find_first_rule_that_matches_elem(
container, target_elem, select, class_name, sheet.cssRules,
html_file_name
)
if res is not None:
return res._replace(style_tag_address=(int(tag.get(lnum_attr)), ['style']))
elif tn == 'link' and tag.get('href') and tag.get('rel') == 'stylesheet':
sname = container.href_to_name(tag.get('href'), html_file_name)
try:
sheet = container.parsed(sname)
except Exception:
continue
if not hasattr(sheet, 'cssRules'):
continue
res = find_first_rule_that_matches_elem(
container, target_elem, select, class_name, sheet.cssRules, sname
)
if res is not None:
return res
raise NoMatchingRuleFound(
f'No CSS rules that apply to the specified tag in {html_file_name} with the class {class_name} found'
)
def develop():
container = get_container('/t/demo.epub', tweak_mode=True)
fname = 'index_split_002.html'
data = {'class': 'xxx', 'sourceline_address': (13, ['body'])}
print(
find_first_matching_rule(
container, fname,
container.open(fname).read(), data
)
)