diff --git a/src/calibre/ebooks/docx/to_html.py b/src/calibre/ebooks/docx/to_html.py
index 22186bdbb8..bb021c1376 100644
--- a/src/calibre/ebooks/docx/to_html.py
+++ b/src/calibre/ebooks/docx/to_html.py
@@ -6,7 +6,7 @@ from __future__ import (unicode_literals, division, absolute_import,
__license__ = 'GPL v3'
__copyright__ = '2013, Kovid Goyal '
-import sys, os, re, math, errno
+import sys, os, re, math, errno, uuid
from collections import OrderedDict, defaultdict
from lxml import html
@@ -105,6 +105,7 @@ class Convert(object):
self.anchor_map = {}
self.link_map = defaultdict(list)
self.link_source_map = {}
+ self.toc_anchor = None
self.block_runs = []
paras = []
@@ -364,9 +365,13 @@ class Convert(object):
opf.create_spine(['index.html'])
if self.cover_image is not None:
opf.guide.set_cover(self.cover_image)
+ def process_guide(E, guide):
+ if self.toc_anchor is not None:
+ guide.append(E.reference(
+ href='index.html#' + self.toc_anchor, title=_('Table of Contents'), type='toc'))
toc_file = os.path.join(self.dest_dir, 'toc.ncx')
with open(os.path.join(self.dest_dir, 'metadata.opf'), 'wb') as of, open(toc_file, 'wb') as ncx:
- opf.render(of, ncx, 'toc.ncx')
+ opf.render(of, ncx, 'toc.ncx', process_guide=process_guide)
if os.path.getsize(toc_file) == 0:
os.remove(toc_file)
return os.path.join(self.dest_dir, 'metadata.opf')
@@ -413,7 +418,7 @@ class Convert(object):
except AttributeError:
break
- for x in self.namespace.descendants(p, 'w:r', 'w:bookmarkStart', 'w:hyperlink'):
+ for x in self.namespace.descendants(p, 'w:r', 'w:bookmarkStart', 'w:hyperlink', 'w:instrText'):
if p_parent(x) is not p:
continue
if x.tag.endswith('}r'):
@@ -445,6 +450,16 @@ class Convert(object):
self.anchor_map[a] = current_anchor
elif x.tag.endswith('}hyperlink'):
current_hyperlink = x
+ elif x.tag.endswith('}instrText') and x.text and x.text.strip().startswith('TOC '):
+ old_anchor = current_anchor
+ anchor = str(uuid.uuid4())
+ self.anchor_map[anchor] = current_anchor = generate_anchor('toc', frozenset(self.anchor_map.itervalues()))
+ self.toc_anchor = current_anchor
+ if old_anchor is not None:
+ # The previous anchor was not applied to any element
+ for a, t in tuple(self.anchor_map.iteritems()):
+ if t == old_anchor:
+ self.anchor_map[a] = current_anchor
m = re.match(r'heading\s+(\d+)$', style.style_name or '', re.IGNORECASE)
if m is not None:
diff --git a/src/calibre/ebooks/metadata/opf2.py b/src/calibre/ebooks/metadata/opf2.py
index 89d40b549a..262d59aa82 100644
--- a/src/calibre/ebooks/metadata/opf2.py
+++ b/src/calibre/ebooks/metadata/opf2.py
@@ -1396,7 +1396,7 @@ class OPFCreator(Metadata):
self.guide.set_basedir(self.base_path)
def render(self, opf_stream=sys.stdout, ncx_stream=None,
- ncx_manifest_entry=None, encoding=None):
+ ncx_manifest_entry=None, encoding=None, process_guide=None):
if encoding is None:
encoding = 'utf-8'
toc = getattr(self, 'toc', None)
@@ -1519,6 +1519,8 @@ class OPFCreator(Metadata):
if ref.title:
item.set('title', ref.title)
guide.append(item)
+ if process_guide is not None:
+ process_guide(E, guide)
serialize_user_metadata(metadata, self.get_all_user_metadata(False))