Translate TOC data to a Lua data-structure, and create custom table of content style

Given nested loops & data-structures are more elegant in Lua (than in TeX), I think plurality of table of contents style would be easier to write in Lua. For that to work, two things would be needed: A way to translate TOC data into a Lua data structure, and then traversing that data structure to do tex.print to typeset a table of contents with desired styling.

Below is an image of table of contents style that I came across while finding a pdf specimen on the web that uses a particular font. PDF metadata suggests that it was done in InDesign, I would like to recreate this style in LuaLaTeX. Some salient features of this style are:

• Its a multi-part book. Instead of explicitly stating part numbers, transition to a new part is implied from “all small caps” styling, and resetting the "section" number to 1 after transition to new part.
• Section numbers are unique to a part, and increment across chapters within a part.
• The indent before first section number of a chapter ensures that it falls in the same horizontal position if it were typeset after the last section of previous chapter. Look for instance transition between sections 82 -> 83 in part "pācittiya", and (26->27, 56->57, 72->73) in part "sekhiya". This indent rule applies only between chapters of belonging to same part.
• A part can directly have sections, without having underlying chapter.

I would imagine the values in each node of toc data-structure in lua would have relevant fields like: type (part/chapter/section/whatever), number (which can be contextual based on type), starting page number, tex string to be typeset (that can have line breaks, custom styling macros like \textcolor, \textbf etc), and a pointer to head of child nodes. Perhaps this can be a nested linkedlist data-structure like nodelist if the code from LuaTeX node library can be reused.

Note: If it helps to do so then the traditional naming/hierarchy of part-chapter-section could be replaced with something different that doesn't get into the way of using a selected latex document class.

• It's not clear how Lua can help really, by the time you have written the data to Lua you could have written the form for the toc to be printed, but basically you just need to define \l@chapter, \l@section etc to add to a Lua table as well as their existing use of writing to the toc file. Oct 27 '20 at 9:28
• In pdflatex .aux file are creared, and via outer script you can read anything from it for chapter, section, etc., (after first running) and generate own toc file and input it. May be in LuaLaTeX you can do this too. Oct 27 '20 at 14:12
• It seems to me that you will need to change the structure of the document, not just the ToC. Why reset section numbers to 1 after each \part when you say that section numbers are unique to a part (i.e., no part can have the same section numbers within it)? What are the styles of a \section number with and without a preceding \chapter? Etc. Oct 27 '20 at 18:41
• @PeterWilson Not sure why structure of the document needs to be changed. This TOC is from a book that divided "topics" of a "part" across "chapters". Its a matter of stylistic effect to have topic numbers increment across chapters. I would assume there is nothing special about name "section" or "topic", and call it section as thats the next in hierarchy below chapter (in spirit of Shakespear's quote "Whats in a name?..."). Nov 4 '20 at 7:26

Below is a solution to keeping a Lua version of TOC. I use inspect.lua to show the structure of toc variable. Here is an excerpt of it, you can see the full string in the log file. Each entity in the tree is associated with a label field so that one can create links to it or access its page number.

I have a rough idea of how to recreate the TOC layout, but since you might be more interested in the Lua TOC part, I am not diving into that now. If you need help, please let me know.

{ {
arga = "",
argb = "Part 1",
content = { {
arga = "",
argb = "Chapter 1",
content = { {
arga = "",
argb = "Section I",
content = {},
index = 1,
label = "5371A59F385D7156E74C245C6A1225D3DAC02A8ED95DFBEF",
level = 3
}, {
arga = "",
argb = "Section II",
content = {},
index = 2,
label = "7F84FBD64E18D211C25EE7F86B00322F8602CE9093259EDD",
level = 3
}, {
arga = "",
argb = "Section III",
content = {},
index = 3,
level = 3
}, {...

\documentclass{scrbook}
\usepackage[T1]{fontenc}
\usepackage{expl3}
\usepackage{etoolbox}
\usepackage{blindtext}
\usepackage{luacode}
\usepackage{xparse}

\begin{luacode*}
--https://github.com/kikito/inspect.lua
inspect = require "inspect"

toc = {}
most_recent_hash = ""

function hash_and_store(parts)
local str = ""
for _, stuff in pairs(parts) do
str = str .. tostring(stuff)
end
local bin_hash = sha2.digest256(str)
local hex_hash = ""
for i=1,#bin_hash do
hex_hash = hex_hash .. string.format("%02X", bin_hash:byte(i))
end
most_recent_hash = hex_hash:sub(1, 48)
end

function get_toc_item(tbl)
local keys = {"level", "index", "label", "arga", "argb", "content"}
local default_vals = {0, 0, "", "", "", {}}
local item = {}

local temp = nil
for ind, key in pairs(keys) do
temp = tbl[key]
if temp == nil then
rawset(item, key, default_vals[ind])
else
rawset(item, key, temp)
end
end

return item
end

function insert_to_toc(level, arga, argb)

local search_level = 1
local tbl = toc
local hash_path = {}

while search_level < level do
table.insert(hash_path, #tbl)
if #tbl == 0 then
local item = get_toc_item{level=search_level, index=0}
table.insert(tbl, item)
tbl = item.content
else
tbl = tbl[#tbl].content
end
search_level = search_level + 1
end

table.insert(hash_path, argb)
hash_and_store(hash_path)

local item_index = #tbl + 1
local item = get_toc_item{level=level, index=item_index, label=most_recent_hash,arga=arga,argb=argb}
table.insert(tbl, item)

end

\end{luacode*}

% redefine commands to link Lua and TeX
\ExplSyntaxOn

\tl_new:N \l_doc_tmpa_tl

\cs_set:Npn \doc_patch_command:nn #1#2 {
\cs_gset_eq:cc {__doc_old_#1} {#1}
\exp_args:Nc \RenewDocumentCommand{#1}{som}{
\IfBooleanTF{##1}{
% starred commands are not numbered
\IfValueTF{##2}{
\use:c {__doc_old_#1} * [##2] {##3}
}{
\use:c {__doc_old_#1} * {##3}
}
}{
\IfValueTF{##2}{
\directlua{
insert_to_toc(#2, "\luaescapestring{##2}", "\luaescapestring{##3}")
}
\use:c {__doc_old_#1} [##2] {##3}
\label{\directlua{tex.print(most_recent_hash)}}
}{
\directlua{
insert_to_toc(#2, "", "\luaescapestring{##3}")
}
\use:c {__doc_old_#1} {##3}
\directlua{texio.write(most_recent_hash)}
\label{\directlua{tex.print(most_recent_hash)}}
}
}
}
}

\AtBeginDocument{
\doc_patch_command:nn {part}{1}
\doc_patch_command:nn {chapter}{2}
\doc_patch_command:nn {section}{3}
}

\ExplSyntaxOff

\begin{document}

\tableofcontents

% define some commands for creating a dummy document
\ExplSyntaxOn
\int_new:N \g_doc_part_int
\int_new:N \g_doc_chapter_int
\int_new:N \g_doc_section_int

\sys_gset_rand_seed:n {1}
\int_gset:Nn \g_doc_part_int {1}
\int_gset:Nn \g_doc_chapter_int {1}
\int_gset:Nn \g_doc_section_int {1}

\cs_set:Nn \doc_rand_text: {
\tl_set:Nx \l_tmpa_tl {\exp_not:N\Blindtext[\int_rand:nn {1}{12}]}
\tl_use:N \l_tmpa_tl
}

\newcommand{\dummypart}{
\exp_args:Nx \part{Part~\int_use:N \g_doc_part_int}
\int_gincr:N \g_doc_part_int
}

\newcommand{\dummychapter}{
\exp_args:Nx \chapter{Chapter~\int_use:N \g_doc_chapter_int}
\int_gincr:N \g_doc_chapter_int
}

\newcommand{\dummysection}{
\exp_args:Nx \section{Section~\int_to_Roman:n {\g_doc_section_int}}
\int_gincr:N \g_doc_section_int
}

\int_step_inline:nn {3} {
\dummypart
\int_set:Nn \l_tmpa_int {\int_rand:nn {2}{4}}
\int_step_inline:nn {\l_tmpa_int} {
\dummychapter
\int_set:Nn \l_tmpb_int {\int_rand:nn {5}{10}}
\int_step_inline:nn {\l_tmpb_int} {
\dummysection
\doc_rand_text:
}
}
}

\ExplSyntaxOff

\directlua{
texio.write(inspect(toc))
}

\end{document}