diff --git a/manual/edit.rst b/manual/edit.rst index 3d65331e0d..0aef380af8 100644 --- a/manual/edit.rst +++ b/manual/edit.rst @@ -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 diff --git a/src/calibre/ebooks/oeb/polish/container.py b/src/calibre/ebooks/oeb/polish/container.py index 757815e520..163759431e 100644 --- a/src/calibre/ebooks/oeb/polish/container.py +++ b/src/calibre/ebooks/oeb/polish/container.py @@ -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 diff --git a/src/calibre/ebooks/oeb/polish/toc.py b/src/calibre/ebooks/oeb/polish/toc.py index b459d91dba..72ac577a66 100644 --- a/src/calibre/ebooks/oeb/polish/toc.py +++ b/src/calibre/ebooks/oeb/polish/toc.py @@ -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 + diff --git a/src/calibre/gui2/tweak_book/boss.py b/src/calibre/gui2/tweak_book/boss.py index 49fd3cbbce..36f84502e6 100644 --- a/src/calibre/gui2/tweak_book/boss.py +++ b/src/calibre/gui2/tweak_book/boss.py @@ -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) diff --git a/src/calibre/gui2/tweak_book/ui.py b/src/calibre/gui2/tweak_book/ui.py index 8a78e73b45..deffc27908 100644 --- a/src/calibre/gui2/tweak_book/ui.py +++ b/src/calibre/gui2/tweak_book/ui.py @@ -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)