Preserve existing nav markup when converting from EPUB 3 to EPUB 3

This commit is contained in:
Kovid Goyal 2018-05-24 10:26:24 +05:30
parent b7d88ff218
commit 09ffa06cc4
No known key found for this signature in database
GPG Key ID: 06BC317B515ACE7C
5 changed files with 33 additions and 23 deletions

View File

@ -289,7 +289,7 @@ class EPUBInput(InputFormatPlugin):
epub3_nav = opf.epub3_nav
if epub3_nav is not None:
self.convert_epub3_nav(epub3_nav, opf, log)
self.convert_epub3_nav(epub3_nav, opf, log, options)
if len(parts) > 1 and parts[0]:
delta = '/'.join(parts[:-1])+'/'
@ -346,11 +346,11 @@ class EPUBInput(InputFormatPlugin):
return os.path.abspath(u'content.opf')
def convert_epub3_nav(self, nav_path, opf, log):
def convert_epub3_nav(self, nav_path, opf, log, opts):
from lxml import etree
from calibre.ebooks.chardet import xml_to_unicode
from calibre.ebooks.oeb.polish.parsing import parse
from calibre.ebooks.oeb.base import EPUB_NS, XHTML, NCX_MIME, NCX
from calibre.ebooks.oeb.base import EPUB_NS, XHTML, NCX_MIME, NCX, urlnormalize
from calibre.ebooks.oeb.polish.toc import first_child
from tempfile import NamedTemporaryFile
with lopen(nav_path, 'rb') as f:
@ -401,6 +401,9 @@ class EPUBInput(InputFormatPlugin):
ncx_id = opf.add_path_to_manifest(f.name, NCX_MIME)
for spine in opf.root.xpath('//*[local-name()="spine"]'):
spine.set('toc', ncx_id)
href = os.path.relpath(nav_path).replace(os.sep, '/')
opts.epub3_nav_href = urlnormalize(href)
opts.epub3_nav_parsed = root
def postprocess_book(self, oeb, opts, log):
rc = getattr(self, 'removed_cover', None)

View File

@ -295,7 +295,8 @@ class EPUBOutput(OutputFormatPlugin):
from calibre.ebooks.oeb.polish.container import EpubContainer
container = EpubContainer(tdir, self.log)
from calibre.ebooks.oeb.polish.upgrade import epub_2_to_3
epub_2_to_3(container, self.log.info)
existing_nav = getattr(self.opts, 'epub3_nav_parsed', None)
epub_2_to_3(container, self.log.info, previous_nav=existing_nav)
container.commit()
os.remove(f.name)
try:

View File

@ -655,14 +655,17 @@ def ensure_single_nav_of_type(root, ntype='toc'):
return nav
def commit_nav_toc(container, toc, lang=None, landmarks=None):
def commit_nav_toc(container, toc, lang=None, landmarks=None, previous_nav=None):
from calibre.ebooks.oeb.polish.pretty import pretty_xml_tree
tocname = find_existing_nav_toc(container)
if tocname is None:
item = container.generate_item('nav.xhtml', id_prefix='nav')
item.set('properties', 'nav')
tocname = container.href_to_name(item.get('href'), base=container.opf_name)
root = container.parse_xhtml(P('templates/new_nav.html', data=True).decode('utf-8'))
if previous_nav is not None:
root = previous_nav
else:
root = container.parse_xhtml(P('templates/new_nav.html', data=True).decode('utf-8'))
container.replace(tocname, root)
else:
root = container.parsed(tocname)

View File

@ -100,7 +100,7 @@ guide_epubtype_map = {
}
def create_nav(container, toc, landmarks):
def create_nav(container, toc, landmarks, previous_nav=None):
lang = get_book_language(container)
if lang == 'und':
lang = None
@ -109,10 +109,10 @@ def create_nav(container, toc, landmarks):
entry['type'] = guide_epubtype_map.get(entry['type'].lower())
if entry['type'] == 'cover' and container.mime_map.get(entry['dest'], '').lower() in OEB_DOCS:
container.apply_unique_properties(entry['dest'], 'calibre:title-page')
commit_nav_toc(container, toc, lang=lang, landmarks=landmarks)
commit_nav_toc(container, toc, lang=lang, landmarks=landmarks, previous_nav=previous_nav)
def epub_2_to_3(container, report):
def epub_2_to_3(container, report, previous_nav=None):
upgrade_metadata(container.opf)
collect_properties(container)
toc = get_toc(container)
@ -123,7 +123,7 @@ def epub_2_to_3(container, report):
landmarks = get_landmarks(container)
for guide in container.opf_xpath('./opf:guide'):
guide.getparent().remove(guide)
create_nav(container, toc, landmarks)
create_nav(container, toc, landmarks, previous_nav)
container.opf.set('version', '3.0')
fix_font_mime_types(container)
container.dirty(container.opf_name)

View File

@ -179,6 +179,11 @@ class CSSFlattener(object):
titlepage = titlepage.item
if titlepage is not None and titlepage not in self.items:
self.items.append(titlepage)
epub3_nav = None
if getattr(self.opts, 'epub3_nav_href', None):
epub3_nav = self.oeb.manifest.hrefs.get(self.opts.epub3_nav_href)
if epub3_nav is not None and epub3_nav not in self.items:
self.items.append(epub3_nav)
self.filter_css = frozenset()
if self.opts.filter_css:
@ -210,6 +215,8 @@ class CSSFlattener(object):
self.sbase = self.baseline_spine() if self.fbase else None
self.fmap = FontMapper(self.sbase, self.fbase, self.fkey)
self.flatten_spine()
if epub3_nav is not None:
self.opts.epub3_nav_parsed = epub3_nav.data
def get_embed_font_info(self, family, failure_critical=True):
efi = []
@ -436,12 +443,11 @@ class CSSFlattener(object):
cssdict['font-weight'] = 'normal' # ADE chokes on font-weight medium
fsize = font_size
is_drop_cap = (cssdict.get('float', None) == 'left' and 'font-size' in
cssdict and len(node) == 0 and node.text and
(len(node.text) == 1 or (len(node.text) == 2 and 0x2000 <= ord(node.text[0]) <= 0x206f)))
is_drop_cap = (cssdict.get('float', None) == 'left' and 'font-size' in cssdict and len(node) == 0 and node.text and (
len(node.text) == 1 or (len(node.text) == 2 and 0x2000 <= ord(node.text[0]) <= 0x206f)))
# Detect drop caps generated by the docx input plugin
if (node.tag and node.tag.endswith('}p') and len(node) == 0 and node.text and len(node.text.strip()) == 1 and
not node.tail and 'line-height' in cssdict and 'font-size' in cssdict):
if node.tag and node.tag.endswith('}p') and len(node) == 0 and node.text and len(node.text.strip()) == 1 and \
not node.tail and 'line-height' in cssdict and 'font-size' in cssdict:
dp = node.getparent()
if dp.tag and dp.tag.endswith('}div') and len(dp) == 1 and not dp.text:
if stylizer.style(dp).cssdict().get('float', None) == 'left':
@ -473,8 +479,8 @@ class CSSFlattener(object):
if cssdict:
for x in self.filter_css:
popval = cssdict.pop(x, None)
if (self.body_font_family and popval and x == 'font-family' and
popval.partition(',')[0][1:-1] == self.body_font_family.partition(',')[0][1:-1]):
if self.body_font_family and popval and x == 'font-family' \
and popval.partition(',')[0][1:-1] == self.body_font_family.partition(',')[0][1:-1]:
cssdict[x] = popval
if cssdict:
@ -499,8 +505,7 @@ class CSSFlattener(object):
lineh = self.lineh / psize
cssdict['line-height'] = "%0.5fem" % lineh
if (self.context.remove_paragraph_spacing or
self.context.insert_blank_line) and tag in ('p', 'div'):
if (self.context.remove_paragraph_spacing or self.context.insert_blank_line) and tag in ('p', 'div'):
if item_id != 'calibre_jacket' or self.context.output_profile.name == 'Kindle':
for prop in ('margin', 'padding', 'border'):
for edge in ('top', 'bottom'):
@ -510,8 +515,7 @@ class CSSFlattener(object):
'%fem'%self.context.insert_blank_line_size
indent_size = self.context.remove_paragraph_spacing_indent_size
keep_indents = indent_size < 0.0
if (self.context.remove_paragraph_spacing and not keep_indents and
cssdict.get('text-align', None) not in ('center', 'right')):
if (self.context.remove_paragraph_spacing and not keep_indents and cssdict.get('text-align', None) not in ('center', 'right')):
cssdict['text-indent'] = "%1.1fem" % indent_size
pseudo_classes = style.pseudo_classes(self.filter_css)
@ -618,8 +622,7 @@ class CSSFlattener(object):
items = sorted(stylizer.page_rule.items())
css = ';\n'.join("%s: %s" % (key, val) for key, val in items)
css = ('@page {\n%s\n}\n'%css) if items else ''
rules = [r.cssText for r in stylizer.font_face_rules +
self.embed_font_rules]
rules = [r.cssText for r in stylizer.font_face_rules + self.embed_font_rules]
raw = '\n\n'.join(rules)
css += '\n\n' + raw
global_css[css].append(item)