1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
|
local mkutils = require "mkutils"
local log = logging.new("tocid")
-- Unicode data distributed with ConTeXt
-- defines "characters" table
if not mkutils.isModuleAvailable("char-def") then
log:warning("char-def module not found")
log:warning("cannot fix section id's")
return function(dom) return dom end
end
require "char-def"
local chardata = characters.data or {}
local toc = nil
local function is_letter(info)
-- test if character is letter
local category = info.category or ""
return category:match("^l")
end
local function is_space(info)
local category = info.category or ""
return category == "zs"
end
local uchar = utf8.char
local function normalize_letter(char, result)
local info = chardata[char] or {}
-- first get lower case of the letter
local lowercase = info.lccode or char
-- remove accents. the base letter is in the shcode field
local lowerinfo = chardata[lowercase] or {}
-- when no shcode, use the current lowercase char
local shcode = lowerinfo.shcode or lowercase
-- shcode can be table if it contains multiple characters
-- normaliz it to a table, so we can add all letters to
-- the resulting string
if type(shcode) ~= "table" then shcode = {shcode} end
for _, x in ipairs(shcode) do
result[#result+1] = uchar(x)
end
end
local escape_name = function(name)
local result = {}
-- remove LaTeX commands
name = name:gsub("\\[%a]+", "")
name = name:gsub("^%s+", ""):gsub("%s+$", "")
for _,char in utf8.codes(name) do
local info = chardata[char] or {}
if is_space(info) then
result[#result+1] = " "
elseif is_letter(info) then
normalize_letter(char, result)
end
end
--- convert table with normalized characters to string
local name = table.concat(result)
-- remove spaces
return name:gsub("%s+", "-")
end
local function parse_toc_line(line)
-- the section ids and titles are saved in the following format:
-- \csname a:TocLink\endcsname{1}{x1-20001}{QQ2-1-2}{Nazdar světe}
-- ............................... id ................. title ...
local id, name = line:match("a:TocLink.-{.-}{(.-)}{.-}(%b{})")
if id then
return id, escape_name(name)
end
end
local used = {}
local function parse_toc(filename)
local toc = {}
if not mkutils.file_exists(filename) then return nil, "Cannot open TOC file " .. filename end
for line in io.lines(filename) do
local id, name = parse_toc_line(line)
local orig_name = name
-- not all lines in the .4tc file contains TOC entries
if id then
-- test if the same name was used already. user should be notified
if used[name] then
-- update
name = name .. used[name]
log:debug("Duplicate id found: ".. orig_name .. ". New id: " .. name)
end
used[orig_name] = (used[orig_name] or 0) + 1
toc[id] = name
end
end
return toc
end
-- we don't want to change the original id, as there may be links to it from the outside
-- so we will set it to the parent element (which should be h[1-6])
local function set_id(el, id)
local section = el:get_parent()
local section_id = section:get_attribute("id")
if section_id and section_id~=id then -- if it already has id, we don't override it, but create dummy child instead
local new = section:create_element("span", {id=id})
section:add_child_node(new,1)
else
section:set_attribute("id", id)
end
end
return function(dom, par)
local msg
toc, msg = toc or parse_toc(par.input .. ".4tc")
msg = msg or "Cannot load TOC"
-- don't do anyting if toc cannot be found
if not toc then
log:warning(msg)
return dom
end
-- if user selects the "notoc" option on the command line, we
-- will not update href links
local notoc = false
if par["tex4ht_sty_par"]:match("notoc") then notoc = true end
-- the HTML file can already contain ID that we want to assign
-- we will not set duplicate id from TOC in that case
local toc_ids = {}
for _, el in ipairs(dom:query_selector("[id]")) do
local id = el:get_attribute("id")
toc_ids[id] = true
end
-- process all elements with id atribute or <a href>
for _, el in ipairs(dom:query_selector "[id],a[href]") do
local id, href = el:get_attribute("id"), el:get_attribute("href")
if id then
local name = toc[id]
-- replace id with new section id
if name and not toc_ids[name] then
set_id(el, name)
else
if name then
log:debug("Document already contains id: " .. name)
end
end
end
if href and notoc == false then
-- replace links to sections with new id
local base, anchor = href:match("^(.*)%#(.+)")
local name = toc[anchor]
if name then
el:set_attribute("href", base .. "#" .. name)
end
end
end
return dom
end
|