With Michal’s hints, I’ve arrived at the following (overengineered-feeling) solution. It satisfies the following requirements:
- Chapters follow the format
Chapter 1: Chapter Title in the table of contents inside the document.
- Everything is part of the
<a> link instead of Chapter 1: being outside, as would normally be the case with TeX4ht.
- Chapters follow the format
Chapter 1: Chapter Title in the table of contents shown in the e-reader’s user interface.
- In addition, it lets you change the types of divisions that should be included in the table of contents in one place, and applies that everywhere.
It works by changing these things:
- Use TeX4ht’s
\TableOfContents in place of the default \tableofcontents.
\Configure{tableofcontents} to include e.g. likechapter.
- Use
\Configure{NavSection} and \peek_regex_replace_once to capture the division number.
- Add
Chapter , the captured division number, and : to the in-document table of contents using \Configure{TocLink}. (This regex solution is highly hacky – let me know if anyone knows of a better way to move the number into the <a> link!)
- Add
Chapter and : to the NCX table of contents with a deferred \Configure{NavSection}.

xhtml.cfg
\Preamble{xhtml}
% Edit this to change what the table of contents should record.
% (Doesn’t support KOMA-Script’s `\addchap` – this could probably be
% implemented, but let’s not unless someone needs it.)
\def\tableofcontentsincludes{part,likepart,appendix,chapter,likechapter,section,likesection}
% Replace LaTeX’s `\tableofcontents` (which doesn’t record `\chapter*`s)
% with TeX4Ht’s automatic `\TableOfContents` (which can).
\ExplSyntaxOn
\RenewDocumentCommand{\tableofcontents}{}{%
% https://tex.stackexchange.com/a/26489/392788
\exp_args:NNV \TableOfContents[\tableofcontentsincludes]%
}
\ExplSyntaxOff
% What we want is for the table of contents to say “Chapter 1: Chapter Title”
% instead of simply “1 Chapter Title”. The way EPUB 3 works, it has two tables
% of contents for the document: One in the document (X)HTML, and one in the
% NCX file. It also has two different places to *display* the table of contents
% for the document: Inside the actual book, and in the user interface.
% The table of contents inside the actual book is, of course, the one in the
% document (X)HTML. Testing, however, reveals that the user interface ToC comes
% from one of two sources: either from the NCX file, *or* from the document
% (X)HTML *if it has the attribute `epub:type="toc"`. (This might vary from
% e-reader to e-reader.) To that end, if we want “Chapter 1: Chapter Title”,
% we should make sure that it says so both in the document (X)HTML and the NCX
% file.
\catcode`\:=11
% Here we add a custom heading.
\ExplSyntaxOn
\Configure{NavMap}
{% Before the table of contents.
\ifvmode\IgnorePar\fi\EndP%
\boolfalse{tocnoempty}%
\global\advance\:toccount by1%
\HCode{<nav\space id="toc\the\:toccount"\space class="toc"\ifnum\:toccount<2 \space epub:type="toc"\space role="doc-toc"\fi>\Hnewline}%
% Comment the above line and uncomment the line below to use the NCX
% table of contents in the user interface.
% \HCode{<nav\space id="toc\the\:toccount"\space class="toc">\Hnewline<ol>}%
% Custom heading for the table of contents.
\HCode{<div\space class="heading\space chapter-heading\space without-number"><h2\space class="chapter-title">}\contentsname\HCode{</h2></div>\Hnewline}%
\HCode{<ol>}%
\opf:registerfilename{\FileName}%
\ifnum\:toccount<2 \opf:add:property{nav}\fi%
}
{% At the end of the table of contents.
\exp_args:Ne \usetoclevels{\tableofcontentsincludes}%
\ifbool{tocnoempty}{}{\HCode{<li><a href="\jobname.\:html">Document</a></li>}}%
\HCode{</ol></nav>}%
}
\ExplSyntaxOff
% Here, we configure the table of contents to treat `likechapter` (i.e.
% `\chapter*{…}` like it does `chapter` (i.e. `\chapter{…}`). At the time of
% writing, TeX4ebook does not do this by default.
\ExplSyntaxOn
\seq_new:N \tableofcontentsincludes_seq
\seq_new:N \cur_potential_divisions_to_close_seq
\seq_new:N \cur_divisions_to_close_seq
\seq_set_split:NnV \tableofcontentsincludes_seq {,}{\tableofcontentsincludes}
\Configure{tableofcontents}
{% Before ToC.
\a:NavMap%
%
\resettoclevels{part,likepart,appendix,chapter,likechapter,section,likesection,subsection,likesubsection,subsubsection,likesubsubsection}%
%
% We have to configure each division we want using `\navsection` (see
% `tex4ebook-epub3.4ht` in the TeX4ebook source).' Since we’ve made the
% divisions variable, we have to generate these dynamically.
\tl_new:N \null_tl% Throwaway variable.
\seq_set_eq:NN \cur_potential_divisions_to_close_seq \tableofcontentsincludes_seq%
\seq_set_eq:NN \cur_divisions_to_close_seq \tableofcontentsincludes_seq%
\seq_map_inline:Nn \tableofcontentsincludes_seq {%
% A little niggle is that, for example, `\navsection{likechapter}` should
% include `chapter`, i.e. `\navsection{likechapter}{chapter, likechapter, …}`.
% Thus, we only update the running list of sections to close when
% we encounter a non-`like` division.
\tl_if_regex_match:nnF {##1}{^like}{\seq_set_eq:NN \cur_divisions_to_close_seq \cur_potential_divisions_to_close_seq}%
\exp_args:Nne \navsection
{##1}
{\seq_use:Nn \cur_divisions_to_close_seq {,}}%
\seq_gpop:NN \cur_potential_divisions_to_close_seq \null_tl%
}%
%
\Configure{toTocLink}{}{}%
}
{\b:NavMap} % End of ToC.
{} % After ToC.
{} % Before non-indented \par.
{} % Before indented \par.
\ExplSyntaxOff
% We’ll need these for turning “1 Chapter Title” into “Chapter 1: Chapter
% Title”.
\ExplSyntaxOn
\NewDocumentCommand{\curr:sect:prefix}{}{%
\str_case_e:nnT{\curr:sect:type}{%
{part}{Part}
{chapter}{\chaptername}
}{\space}%
}
\NewDocumentCommand{\curr:sect:colon}{}{%
\str_case_e:nnT{\curr:sect:type}{%
{part}{:}
{chapter}{:}
{section}{:}
{subsection}{:}
{subsubsection}{:}
}{\space}%
}
\ExplSyntaxOff
% We want the label `Chapter 1:` to be part of the `<a>` link, not outside
% as it is by default.
\NewDocumentCommand{\curr:sect:number}{}{}
\Configure{TocLink}{%
\Link{#2}{#3}%
\curr:sect:prefix\curr:sect:number\curr:sect:colon #4%
\EndLink%
}
% This is a bit of a funky workaround: We’re capturing the division number
% using regex below. In order to do this, we need a single csname to call.
\NewDocumentCommand{\curr:sect:number:set}{m}{%
\RenewDocumentCommand{\curr:sect:number}{}{#1}%
}
% Here, we change “1 Chapter Title” to “Chapter 1: Chapter Title” in the
% in-document (X)HTML table of contents.
\ExplSyntaxOn
\Configure{NavSection}
{% Before the division number.
\booltrue{tocnoempty}%
\HCode{<li>}%
\peek_regex_replace_once:nnF{([^\d.]?[\d.]+)}
{\c{curr:sect:number:set}\{\0\}}
{\RenewDocumentCommand{\curr:sect:number}{}{}}%
}
{\HCode{<ol>\Hnewline}} % Between the division title and its children.
{} % In between division number and division title.
{\HCode{</ol></li>}} % After the division’s children.
\ExplSyntaxOff
% Here, we turn “1 Chapter Title” into “Chapter 1: Chapter Title” in the NCX
% table of contents. Truly an ugly hack among ugly hacks, but it works!
% We have to `\Configure{NavSection}` at the correct point in `tex4ebook.4ht`,
% because if we do it now, it’ll just get overwritten.
\makeatletter
\ExplSyntaxOn
\NewCommandCopy{\ncx:head@original}{\ncx:head}
\NewCommandCopy{\TableOfContents@original}{\TableOfContents}
\RenewDocumentCommand{\ncx:head}{}{%
\ncx:head@original%
%
\Configure{NavSection}
{%
\booltrue{tocnoempty}%
\HCode{\Hnewline<navPoint\space id="navPoint-}%
\stepnavpoint%
\HCode{"\space playOrder="}\the\navpoint%
\HCode{">\Hnewline}%
\HCode{<navLabel>\Hnewline<text><navmark\space type="\curr:sect:type">}%
%
\curr:sect:prefix%
}
{%
\HCode{</text>\Hnewline</navLabel>\Hnewline}%
\HCode{<content\space src="\navmapsrc"\space />}%
}
{%
\HCode{</navmark>}%
\curr:sect:colon%
}
{\HCode{</navPoint>\Hnewline}}%
%
\RenewDocumentCommand{\TableOfContents}{o}{%
\exp_args:NNV \TableOfContents@original[\tableofcontentsincludes]%
\RenewCommandCopy{\TableOfContents}{\TableOfContents@original}%
}%
}
\ExplSyntaxOff
\makeatother
\catcode`:=12
\begin{document}
\EndPreamble
test.tex
\documentclass{scrbook}
% Everything here in the preamble is only required for the PDF version.
% \iftexforht{[if true…]}{[if false…]}:
% Detect whether you’re running in `tex4ebook` (or similar) or not.
% Some typographical decisions may need to differ between a PDF and an EPUB
% edition. For example, to insert HTML, you may do:
% \iftexforht{\HCode{<cite>}The Time Machine\HCode{</cite>}}{\textit{The Time Machine}}
\makeatletter
\ifdefined\HCode
\let\iftexforht\@firstoftwo
\else
\let\iftexforht\@secondoftwo
\fi
\makeatother
% This lets us have `\chapter*` add to the table of contents in the PDF
% version. (This is handled separately for TeX4ht.)
\makeatletter
\NewCommandCopy{\chapter@original}{\chapter}
\RenewDocumentCommand{\chapter}{sm}{%
\IfBooleanTF{#1}{%
\chapter@original*{#2}%
\markboth{#2}{#2}%
% We have TeX4ht set up to add `\chapter*`s to the table of contents
% without using `\addcontentsline`. Using it would create a duplicate
% entry in the table of contents.
\iftexforht{}{\addcontentsline{toc}{chapter}{#2}}%
}{%
\chapter@original{#2}%
}%
}
\NewCommandCopy{\tableofcontents@original}{\tableofcontents}
\RenewDocumentCommand{\tableofcontents}{}{%
\iftexforht{%
\tableofcontents@original%
}{
{%
% This is required because `\tableofcontents` internally calls
% `\chapter*`, meaning it’ll add itself to the table of contents
% if we use our redefined version.
\let\chapter\chapter@original%
\tableofcontents@original%
}%
}
}
\makeatother
\begin{document}
\tableofcontents
\chapter*{Introduction}
\section{Prologue} % This shouldn’t be prefixed with “Chapter”.
\chapter{The Machine}
\end{document}
It’s a complex solution, for sure – I feel like there perhaps should be a more central way to set this format – but from my testing, it works as it should.
\addchapfor not numbered chapters with automatic ToC entry. It also provides commands, that allow to modify the ToC, e.g. prepend ”Chapter“ to the number of the chapter entries. But I don't know whether or not tex4ht supports these modification.