Edit book: new tool to automatically generate an inline (HTML) Table of Contents based on the current NCX Table of Contents. Accessed via Tools->Table of Contents->Insert inline Table of Contents.

This commit is contained in:
Kovid Goyal 2014-02-12 21:08:41 +05:30
parent b821dbd9b8
commit 62ce1a4e92
5 changed files with 107 additions and 6 deletions

View File

@ -235,7 +235,7 @@ Edit the Table of Contents
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
There is a dedicated tool to ease editing of the Table of Contents. Launch it
with :guilabel:`Tools->Edit Table of Contents`.
with :guilabel:`Tools->Table of Contents->Edit Table of Contents`.
.. image:: images/tocedit.png
:alt: The Edit Table of Contents tool
@ -366,6 +366,20 @@ broken HTML/CSS. Therefore, if you dont want any auto-fixing to be performed,
first use the Check Book tool to correct all problems and only then run
beautify. Accessed via :guilabel:`Tools->Beautify all files`.
Insert inline Table of Contents
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Normally in ebooks, the Table of Contents is separate from the main text and is
typically accessed via a special Table of Contents button/menu in the ebook
reading device. You can also have |app| automatically generate an *inline*
Table of Contents that becomes part of the text of the book. It is
generated based on the currently defined Table of Contents.
If you use this tool multiple times, each invocation will cause the previously
created inline Table of Contents to be replaced. The tool can be accessed via
:guilabel:`Tools->Table of Contents->Insert inline Table of Contents`.
.. _checkpoints:
Checkpoints

View File

@ -171,7 +171,7 @@ class Container(object): # {{{
ans = 'application/xhtml+xml'
return ans
def add_file(self, name, data, media_type=None):
def add_file(self, name, data, media_type=None, spine_index=None):
''' Add a file to this container. Entries for the file are
automatically created in the OPF manifest and spine
(if the file is a text document) '''
@ -209,7 +209,7 @@ class Container(object): # {{{
if mt in OEB_DOCS:
spine = self.opf_xpath('//opf:spine')[0]
si = manifest.makeelement(OPF('itemref'), idref=item_id)
self.insert_into_xml(spine, si)
self.insert_into_xml(spine, si, index=spine_index)
def rename(self, current_name, new_name):
''' Renames a file from current_name to new_name. It automatically

View File

@ -14,10 +14,12 @@ from functools import partial
from operator import itemgetter
from lxml import etree
from lxml.builder import ElementMaker
from calibre import __version__
from calibre.ebooks.oeb.base import XPath, uuid_id, xml2text, NCX, NCX_NS, XML, XHTML
from calibre.ebooks.oeb.base import XPath, uuid_id, xml2text, NCX, NCX_NS, XML, XHTML, XHTML_NS, serialize
from calibre.ebooks.oeb.polish.utils import guess_type
from calibre.ebooks.oeb.polish.pretty import pretty_html_tree
from calibre.utils.localization import get_lang, canonicalize_lang, lang_as_iso639_1
ns = etree.FunctionNamespace('calibre_xpath_extensions')
@ -457,3 +459,71 @@ def remove_names_from_toc(container, names):
commit_toc(container, toc)
return True
return False
def find_inline_toc(container):
for name, linear in container.spine_names:
if container.parsed(name).xpath('//*[local-name()="body" and @id="calibre_generated_inline_toc"]'):
return name
def create_inline_toc(container, title=None):
title = title or _('Table of Contents')
toc = get_toc(container)
if len(toc) == 0:
return None
toc_name = find_inline_toc(container)
def process_node(html_parent, toc, level=1, indent=' '):
li = html_parent.makeelement(XHTML('li'))
li.tail = '\n'+ (indent*level)
html_parent.append(li)
name, frag = toc.dest, toc.frag
href = '#'
if name:
href = container.name_to_href(name, toc_name)
if frag:
href += '#' + frag
a = li.makeelement(XHTML('a'), href=href)
a.text = toc.title
li.append(a)
if len(toc) > 0:
parent = li.makeelement(XHTML('ul'))
li.append(parent)
a.tail = '\n\n' + (indent*(level+2))
parent.text = '\n'+(indent*(level+3))
parent.tail = '\n\n' + (indent*(level+1))
for child in toc:
process_node(parent, child, level+3)
parent[-1].tail = '\n' + (indent*(level+2))
E = ElementMaker(namespace=XHTML_NS, nsmap={None:XHTML_NS})
html = E.html(
E.head(
E.title(title),
E.style('''
li { list-style-type: none; padding-left: 2em; margin-left: 0}
a { text-decoration: none }
a:hover { color: red }''', type='text/css'),
),
E.body(
E.h2(title),
E.ul(),
id="calibre_generated_inline_toc",
)
)
name = toc_name
for child in toc:
process_node(html[1][1], child)
pretty_html_tree(container, html)
raw = serialize(html, 'text/html')
if name is None:
name, c = 'toc.xhtml', 0
while container.has_name(name):
c += 1
name = 'toc%d.xhtml' % c
container.add_file(name, raw, spine_index=0)
else:
with container.open(name, 'wb') as f:
f.write(raw)
return name

View File

@ -23,7 +23,7 @@ from calibre.ebooks.oeb.polish.cover import mark_as_cover, mark_as_titlepage
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.split import split, merge, AbortError, multisplit
from calibre.ebooks.oeb.polish.toc import remove_names_from_toc, find_existing_toc
from calibre.ebooks.oeb.polish.toc import remove_names_from_toc, find_existing_toc, create_inline_toc
from calibre.ebooks.oeb.polish.utils import link_stylesheets, setup_cssutils_serialization as scs
from calibre.gui2 import error_dialog, choose_files, question_dialog, info_dialog, choose_save_file
from calibre.gui2.dialogs.confirm_delete import confirm
@ -365,6 +365,19 @@ class Boss(QObject):
self.update_editors_from_container()
self.gui.toc_view.update_if_visible()
def insert_inline_toc(self):
self.commit_all_editors_to_container()
self.add_savepoint(_('Before: Insert inline Table of Contents'))
name = create_inline_toc(current_container())
if name is None:
self.rewind_savepoint()
return error_dialog(self.gui, _('No Table of Contents'), _(
'Cannot create an inline Table of Contents as this book has no existing'
' Table of Contents. You must first create a Table of Contents using the'
' Edit Table of Contents tool.'), show=True)
self.apply_container_update_to_gui()
self.edit_file(name, 'html')
def polish(self, action, name):
with BusyCursor():
self.add_savepoint(_('Before: %s') % name)

View File

@ -322,6 +322,8 @@ class Main(MainWindow):
# Tool actions
group = _('Tools')
self.action_toc = reg('toc.png', _('&Edit Table of Contents'), self.boss.edit_toc, 'edit-toc', (), _('Edit Table of Contents'))
self.action_inline_toc = reg('chapters.png', _('&Insert inline Table of Contents'),
self.boss.insert_inline_toc, 'insert-inline-toc', (), _('Insert inline Table of Contents'))
self.action_fix_html_current = reg('html-fix.png', _('&Fix HTML'), partial(self.boss.fix_html, True), 'fix-html-current', (),
_('Fix HTML in the current file'))
self.action_fix_html_all = reg('html-fix.png', _('&Fix HTML - all files'), partial(self.boss.fix_html, False), 'fix-html-all', (),
@ -450,7 +452,9 @@ class Main(MainWindow):
e.addAction(self.action_preferences)
e = b.addMenu(_('&Tools'))
e.addAction(self.action_toc)
tm = e.addMenu(_('Table of Contents'))
tm.addAction(self.action_toc)
tm.addAction(self.action_inline_toc)
e.addAction(self.action_embed_fonts)
e.addAction(self.action_subset_fonts)
e.addAction(self.action_smarten_punctuation)