Converting Latex \label to HTML ID attribute

Our organization has their reference manual published in LaTeX. It is a basic 300 page reference manual with tables, figures and sections. The organization also has publications database to track the submission and process flow of documents. In the database, their are help windows. They want to link the help to specific parts of the Latex reference manual so when the user clicks on the help button, the specific part of the manual displays. I have looked at PDF destination links but I think that is too labour intensive and will not always display the proper location. The htlatex utility seems to be the way to go. Here's the question:

What is the best way to markup the LaTeX file so the \label converts to the HTML ID attribute?

Below is the example of my LaTeX file:

\section{Section heading}\label{sec:intro}

Blah blah blah blah blah blah blah blah blah blah blah blah blah blah
blah blah blah blah blah blah blah blah blah blah blah blah blah blah
blah blah blah blah blah blah.  Here is a citation\,\cite{EXAMPLE}.

Blah blah blah blah blah blah blah blah blah blah blah blah blah blah
blah blah blah blah blah blah blah blah blah blah blah blah blah blah
blah blah blah blah blah blah.

This is a test to link to Section this text is for searching in HTML Karen heading \ref{sec:intro}.


Below is my HTML output:


<a id="x1-2r1"></a>
<!--l. 80--><p class="noindent" ><span class="phvb8t-x-x-164">1</span>
<span class="phvb8t-x-x-164">Section</span>
<a id="Q1-1-0"></a><!--l. 80--><p class="noindent"

>__________________________________________________________________________________
<!--l. 82--><p class="noindent" >Blah blah blah blah blah blah blah blah blah blah blah blah blah blah blah blah blah blah blah blah blah blah blah blah blah blah blah blah blah blah blah blah blah blah. Here is a
citation <span class="cite">[<a href="#XEXAMPLE">1</a>]</span>.
<!--l. 86--><p class="noindent" >Blah blah blah blah blah blah blah blah blah blah blah blah blah blah blah blah blah blah blah blah blah blah blah blah blah blah blah blah blah blah blah blah blah blah.
<!--l. 90--><p class="noindent" >This is a test to link to Section this text is for searching in HTML Karen heading
<a href="#x1-2r1">1<!--tex4ht:ref: sec:intro --></a>.

I would like the id on <a to <a id="sec:intro">

Thank you for any recommendations and help.

• Welcome to TeX.SE... Can you post your MWE in a workable format, i.e., from \documentclass to \end{document}... – MadyYuvi May 19 at 13:37
• You're using pandoc for the conversion? – Vincent Kuhlmann May 19 at 15:01
• Ah nevermind, you stated you used htlatex – Vincent Kuhlmann May 19 at 15:08
• HTML id attribute cannot contain colon, you would get validation error. – michal.h21 May 19 at 15:48
• @VincentKuhlmann sorry, I was wrong. it is not actually prohibited, but it will cause problems when you want to use such ID in CSS file, because colons have special meaning here. that probably confused me. – michal.h21 May 19 at 16:45

You can try the following configuration file, config.cfg:

\Preamble{xhtml}
\catcode\:=11
% redefine \label to output link with the current label as id
% \o:label: calls the original label
\HLet\label\:tempa
% configure \ref
% \Link{\RefArg}{} starts link. \RefArg contains the label
% we use \:gobble to remove the id generated by TeX4ht, it would
% end in the hyperlinked text otherwise
\catcode\:=12
\begin{document}
\EndPreamble


You can use it with the following command:

htlatex filename.tex "config"


or, preferably, as htlatex is deprecated with

make4ht -c config.cfg filename.tex


You can read about the differences about the two here

In the configuration file, we redefine \label to insert extra <a id="[label]"></a> tag. It uses the \label argument to construct the id that you can link to. To use this id from \ref command, we need to provide a \Configure{ref} command. Detailed description is in the comments in the configuration file.

Now, there is one issue. As \label is used after \section, the destination link is inserted after section, which will result that user will end below section when he clicks on the hyperlink to the label. See the generated HTML code:

 <h3 class='sectionHead' id='section-heading'><span class='titlemark'>1   </span> <a id='x1-10001'></a>Section heading</h3>
<!-- l. 4 --><p class='noindent'><a id='sec:intro'></a>
...
</p><!-- l. 14 --><p class='indent'>   This is a test to link to Section this text is for searching in HTML Karen heading
<a href='#sec:intro'>1</a>. </p>


This issue is difficult to solve on TeX level, so I would suggest another option, which is to use make4ht build files.

make4ht supports Lua build files. They can be used to post-process the generated HTML files. It even supports LuaXML DOM object, so we can easily traverse the document and update all <a> elements.

We can use information stored in the .aux file. The mapping between label and the id assigned by TeX4ht is stored here in the following form:

\newlabel{sec:intro}{{\rEfLiNK{x1-10001}{1}}{\rEfLiNK{x1-10001}{1}}}


You can see that the label is stored in the first parameter to \newlabel, ID is the first parameter to \rEfLiNK.

Here is the build file, build.lua:

local domfilter = require "make4ht-domfilter"

-- we need to parse the aux file to get mapping between labels and id attribute
-- of the corresponding <a> element
local function parse_aux(aux_filename)
local mapping = {}
local f = io.open(aux_filename, "r")
local content = f:read("*all")
f:close()
-- labels are stored in this form:
for label, id in content:gmatch("newlabel{(.-)}{{.-{(.-)}") do
mapping[id] = label
end
return mapping
end

-- this will hold table with mapping between existing ids and labels
local aux

-- chain of functions that will process all generated HTML files
local process = domfilter {
function(dom, properties)
-- because there can be multiple HTML file, we want to reuse existing
-- mapping when we process subsequent files
aux = aux or parse_aux(properties.input .. ".aux")
-- process all <a> elements and replace ids generated by TeX4ht by original labels
for _, a in ipairs(dom:query_selector("a")) do
-- update id attributes
local id = a:get_attribute("id")
if aux[id] then a:set_attribute("id", aux[id]) end
local href = a:get_attribute("href")
if href then
-- get url and id from the hyperlink
local url, id = href:match("(.*)#(.+)")
if id and aux[id] then
a:set_attribute("href", string.format("%s#%s", url or "", aux[id]))
end
end
end
return dom
end
}

Make:match("html\$", process)


This build file first parses the AUX file and produces mapping between ID attributes assigned by TeX4ht and the original label. It then loops over all <a> elements and updates IDs and hyperlinks.

You can compile your document using this command:

make4ht -e build.lua filename.tex


This is the result:

<h3 class='sectionHead'><span class='titlemark'>1   </span> <a id='sec:intro'></a>Section heading</h3>
...
<p class='indent'>   This is a test to link to Section this text is for searching in HTML Karen heading
<a href='#sec:intro'>1<!-- tex4ht:ref: sec:intro  --></a>. </p>


Closing remarks

I can see that you probably use a custom class, as your HTML snippet produces <p> element instead of <h3> for section. You will probably want to create a configuration file for your class, to get a better structured HTML file.

When you produce correct tags for sections, the current version of make4ht (modern replacement for htlatex) produces the following code:

<h3 id='section-heading' class='sectionHead'><span class='titlemark'>1   </span> <a id='x1-10001'></a>Section heading</h3>
...
<p class='indent'>   This is a test to link to Section this text is for searching in HTML Karen heading
<a href='#section-heading'>1<!-- tex4ht:ref: sec:intro  --></a>. </p>


So it generates stable hyperlink destinations based on section titles. "Section title" becomes <h3 id="section-title">. This may be even better for your use case than to use \label names.

The version of make4ht that supports the above feature is not on CTAN yet, so you may really want to use the labels all hyperlinks produced by \label at the moment using one of methods I suggested.

That was quite a challenge you put me up to! I find Michal's approach quite intriguing too, but this one is without the extra step of Lua, and requires only the htlatex you've been using.

\makeatletter
% We are going to modify \section to include a div with the label as id.
\let\orig@section\section
\renewcommand\section[1]{%
\def\@sectiontitle{#1}%
\futurelet\@aftersectioncommand\resolvesection
}

% In case you disable the code, don't have the aux throw errors.
\def\writeSetHrefFallback{
\write\@auxout{\protect\ifx\protect\sethrefaccessible\protect\undefined
\protect\typeout{HTMLLabels: Fallback was used!}
\protect\def\protect\sethrefaccessible \string##1{}
\protect\fi}%
}

% This is triggered when on previous compilation, a div with the label as id of #1 had been placed.
\def\hrefaccessible{}

\def\sethrefaccessible#1{
\expandafter\global\expandafter\let\csname hrefaccessible@#1\endcsname\hrefaccessible
}

% Check if there is a \label following the \section
\def\resolvesection{%
\let\@storedlabel\relax
\ifx\@aftersectioncommand\label
\typeout{HTMLLabels: Section followed by label}%
\let\orig@label\label
% Consume the label
\def\label##1{%
\def\@storedlabel{##1}%
\def\curhypid{##1}%
\write\@auxout{\protect\sethrefaccessible{##1}}%
% We have acquired the necessary information, now relay everything back to the normal commands
\let\label\orig@label
\expandafter\orig@section\expandafter{\@sectiontitle}%
\label{##1}%
}%
\else
% Fallback: \label not following \section directly
\let\curhypid\relax
\typeout{HTMLLabels: Section not followed by label}%
%
\expandafter\orig@section\expandafter{\@sectiontitle}%
\fi
}

% A variation of ref which can use labels as href to ids
\let\orig@ref\ref
\newcommand\htmllabelref[1]{%
\expandafter\ifx\csname hrefaccessible@#1\endcsname\hrefaccessible
% Upon previous compilation, the label had been assigned as an id to a div. We disable the normal link of \ref, and put one in ourselves.
\begingroup
\orig@ref{#1}%
\endgroup
\else
\orig@ref{#1}%
\fi
}
\makeatother


and in the document body

\makeatletter
\writeSetHrefFallback

\def\curhypid{sec:unknown}

\Configure{section}
{}{}
{%
\HCode{<h3>}
\ifx\curhypid\relax
\PackageWarning{HTMLLabels}{Section misses label!}
\else
\fi
\TitleMark\space}
{\HCode{</h3>}}

\ConfigureMark{section}{\thesection}

%% BEGIN This regions sets all \ref to act as \htmllabelref
\def\ref{\htmllabelref}

\let\orig@T@ref\T@ref
\def\alt@T@ref#1{%
\let\T@ref\orig@T@ref
\htmllabelref{#1}%
\let\T@ref\alt@T@ref
}
\let\T@ref\alt@T@ref
%% END

\makeatother


This setup is currently only for \section-commands. I unfortunately don't have the time right now to investigate how to do it consistently across the board. Also, this method relies on the \label coming right after the \section (which was the case in your MWE), because when placing the \section, we need to know the id to be attached there. If the \label doesn't come right after, then the whole code falls back safely to using the autogenerated id's.

The full code in which I got things working: https://pastebin.com/y1rD5yPE