mirror of
https://github.com/kovidgoyal/calibre.git
synced 2025-07-09 03:04:10 -04:00
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:
parent
b821dbd9b8
commit
62ce1a4e92
@ -235,7 +235,7 @@ Edit the Table of Contents
|
|||||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
There is a dedicated tool to ease editing of the Table of Contents. Launch it
|
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
|
.. image:: images/tocedit.png
|
||||||
:alt: The Edit Table of Contents tool
|
: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
|
first use the Check Book tool to correct all problems and only then run
|
||||||
beautify. Accessed via :guilabel:`Tools->Beautify all files`.
|
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:
|
||||||
|
|
||||||
Checkpoints
|
Checkpoints
|
||||||
|
@ -171,7 +171,7 @@ class Container(object): # {{{
|
|||||||
ans = 'application/xhtml+xml'
|
ans = 'application/xhtml+xml'
|
||||||
return ans
|
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
|
''' Add a file to this container. Entries for the file are
|
||||||
automatically created in the OPF manifest and spine
|
automatically created in the OPF manifest and spine
|
||||||
(if the file is a text document) '''
|
(if the file is a text document) '''
|
||||||
@ -209,7 +209,7 @@ class Container(object): # {{{
|
|||||||
if mt in OEB_DOCS:
|
if mt in OEB_DOCS:
|
||||||
spine = self.opf_xpath('//opf:spine')[0]
|
spine = self.opf_xpath('//opf:spine')[0]
|
||||||
si = manifest.makeelement(OPF('itemref'), idref=item_id)
|
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):
|
def rename(self, current_name, new_name):
|
||||||
''' Renames a file from current_name to new_name. It automatically
|
''' Renames a file from current_name to new_name. It automatically
|
||||||
|
@ -14,10 +14,12 @@ from functools import partial
|
|||||||
from operator import itemgetter
|
from operator import itemgetter
|
||||||
|
|
||||||
from lxml import etree
|
from lxml import etree
|
||||||
|
from lxml.builder import ElementMaker
|
||||||
|
|
||||||
from calibre import __version__
|
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.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
|
from calibre.utils.localization import get_lang, canonicalize_lang, lang_as_iso639_1
|
||||||
|
|
||||||
ns = etree.FunctionNamespace('calibre_xpath_extensions')
|
ns = etree.FunctionNamespace('calibre_xpath_extensions')
|
||||||
@ -457,3 +459,71 @@ def remove_names_from_toc(container, names):
|
|||||||
commit_toc(container, toc)
|
commit_toc(container, toc)
|
||||||
return True
|
return True
|
||||||
return False
|
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
|
||||||
|
|
||||||
|
@ -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.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.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.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.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 import error_dialog, choose_files, question_dialog, info_dialog, choose_save_file
|
||||||
from calibre.gui2.dialogs.confirm_delete import confirm
|
from calibre.gui2.dialogs.confirm_delete import confirm
|
||||||
@ -365,6 +365,19 @@ class Boss(QObject):
|
|||||||
self.update_editors_from_container()
|
self.update_editors_from_container()
|
||||||
self.gui.toc_view.update_if_visible()
|
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):
|
def polish(self, action, name):
|
||||||
with BusyCursor():
|
with BusyCursor():
|
||||||
self.add_savepoint(_('Before: %s') % name)
|
self.add_savepoint(_('Before: %s') % name)
|
||||||
|
@ -322,6 +322,8 @@ class Main(MainWindow):
|
|||||||
# Tool actions
|
# Tool actions
|
||||||
group = _('Tools')
|
group = _('Tools')
|
||||||
self.action_toc = reg('toc.png', _('&Edit Table of Contents'), self.boss.edit_toc, 'edit-toc', (), _('Edit Table of Contents'))
|
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', (),
|
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'))
|
_('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', (),
|
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.addAction(self.action_preferences)
|
||||||
|
|
||||||
e = b.addMenu(_('&Tools'))
|
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_embed_fonts)
|
||||||
e.addAction(self.action_subset_fonts)
|
e.addAction(self.action_subset_fonts)
|
||||||
e.addAction(self.action_smarten_punctuation)
|
e.addAction(self.action_smarten_punctuation)
|
||||||
|
Loading…
x
Reference in New Issue
Block a user