From fdf4252b45766136e7b69211e1ed9212dc1f613f Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Fri, 27 Apr 2012 00:07:39 +0530 Subject: [PATCH] KF8 Output: Add an inline ToC if one is not already present in the input document --- src/calibre/ebooks/mobi/writer8/main.py | 6 ++ src/calibre/ebooks/mobi/writer8/toc.py | 106 ++++++++++++++++++++++++ 2 files changed, 112 insertions(+) create mode 100644 src/calibre/ebooks/mobi/writer8/toc.py diff --git a/src/calibre/ebooks/mobi/writer8/main.py b/src/calibre/ebooks/mobi/writer8/main.py index b237e39b3d..97ed31a2e3 100644 --- a/src/calibre/ebooks/mobi/writer8/main.py +++ b/src/calibre/ebooks/mobi/writer8/main.py @@ -28,6 +28,7 @@ from calibre.ebooks.mobi.writer8.index import (NCXIndex, SkelIndex, ChunkIndex, GuideIndex) from calibre.ebooks.mobi.writer8.mobi import KF8Book from calibre.ebooks.mobi.writer8.tbs import apply_trailing_byte_sequences +from calibre.ebooks.mobi.writer8.toc import TOCAdder XML_DOCS = OEB_DOCS | {SVG_MIME} @@ -42,6 +43,9 @@ class KF8Writer(object): self.compress = not self.opts.dont_compress self.has_tbs = False self.log.info('Creating KF8 output') + + # Create an inline ToC if one does not already exist + self.toc_adder = TOCAdder(oeb, opts) self.used_images = set() self.resources = resources self.flows = [None] # First flow item is reserved for the text @@ -62,6 +66,8 @@ class KF8Writer(object): self.create_fdst_records() self.create_indices() self.create_guide() + # We do not want to use this ToC for MOBI 6, so remove it + self.toc_adder.remove_generated_toc() def dup_data(self): ''' Duplicate data so that any changes we make to markup/CSS only diff --git a/src/calibre/ebooks/mobi/writer8/toc.py b/src/calibre/ebooks/mobi/writer8/toc.py new file mode 100644 index 0000000000..b253018196 --- /dev/null +++ b/src/calibre/ebooks/mobi/writer8/toc.py @@ -0,0 +1,106 @@ +#!/usr/bin/env python +# vim:fileencoding=UTF-8:ts=4:sw=4:sta:et:sts=4:ai +from __future__ import (unicode_literals, division, absolute_import, + print_function) + +__license__ = 'GPL v3' +__copyright__ = '2012, Kovid Goyal ' +__docformat__ = 'restructuredtext en' + +from lxml import etree + +from calibre.ebooks.oeb.base import (urlnormalize, XPath, XHTML_NS, XHTML, + XHTML_MIME) + +DEFAULT_TITLE = __('Table of Contents') + +TEMPLATE = ''' + + + {title} + + + +

{title}

+ + + +''' + +class TOCAdder(object): + + def __init__(self, oeb, opts): + self.oeb, self.opts, self.log = oeb, opts, oeb.log + self.title = opts.toc_title or DEFAULT_TITLE + self.at_start = opts.mobi_toc_at_start + self.generated_item = None + self.added_toc_guide_entry = False + self.has_toc = oeb.toc and oeb.toc.count() > 1 + + if 'toc' in oeb.guide: + # Remove spurious toc entry from guide if it is not in spine or it + # does not have any hyperlinks + href = urlnormalize(oeb.guide['toc'].href) + if href in oeb.manifest.hrefs: + item = oeb.manifest.hrefs[href] + if (hasattr(item.data, 'xpath') and + XPath('//h:a[@href]')(item.data)): + if oeb.spine.index(item) < 0: + oeb.spine.add(item, linear=False) + return + elif self.has_toc: + oeb.guide.remove('toc') + else: + oeb.guide.remove('toc') + + if not self.has_toc or 'toc' in oeb.guide: + return + + self.log('\tGenerating in-line ToC') + + root = etree.fromstring(TEMPLATE.format(xhtmlns=XHTML_NS, + title=self.title)) + parent = XPath('//h:ul')(root)[0] + parent.text = '\n\t' + for child in self.oeb.toc: + self.process_toc_node(child, parent) + + id, href = oeb.manifest.generate('contents', 'contents.xhtml') + item = self.generated_item = oeb.manifest.add(id, href, XHTML_MIME, + data=root) + if opts.mobi_toc_at_start == 'end': + oeb.spine.insert(0, item, linear=True) + else: + oeb.spine.add(item, linear=False) + + oeb.guide.add('toc', 'Table of Contents', href) + + def process_toc_node(self, toc, parent, level=0): + li = parent.makeelement(XHTML('li')) + li.tail = '\n'+ ('\t'*level) + parent.append(li) + a = parent.makeelement(XHTML('a'), href=toc.href or '#') + a.text = toc.title + li.append(a) + if toc.count() > 0: + parent = li.makeelement(XHTML('ul')) + li.append(parent) + a.tail = '\n' + ('\t'*level) + parent.text = '\n'+('\t'*(level+1)) + parent.tail = '\n' + ('\t'*level) + for child in toc: + self.process_toc_node(child, parent, level+1) + + def remove_generated_toc(self): + if self.generated_item is not None: + self.oeb.manifest.remove(self.generated_item) + self.generated_item = None + if self.added_toc_guide_entry: + self.oeb.guide.remove('toc') + self.added_toc_guide_entry = False +