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
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
|
local domobject = require "luaxml-domobject"
local filter = require "make4ht-filter"
local log = logging.new "collapsetoc"
local toc_levels = {"partToc", "chapterToc", "sectionToc", "subsectionToc", "subsubsectionToc"}
local debug_print = function(...) log:debug(...) end
-- return toc element type and it's id
local function get_id(el)
local name = el:get_attribute "class"
local id
local a = el:query_selector "a" or {}
local first = a[1]
if first then
local href = first:get_attribute "href"
id = href:match("#(.+)$")
end
return name, id
end
local function remove_sections(part_elements, currentpart)
-- we need to remove toc entries from the previous part if the
-- current document isn't part of it
if currentpart == false then
for _, part in ipairs(part_elements) do
part:remove_node()
end
end
end
local function make_toc_selector(toc_levels)
local level_classes = {}
for _, l in ipairs(toc_levels) do
level_classes[#level_classes+1] = "." .. l
end
return table.concat(level_classes, ", ")
end
local function find_toc_levels(toc)
-- find toc levels used in the document
-- it ecpects that sectioning levels appears in the TOC in the descending order
local levels, used = {}, {}
local level = 1
-- we still expect the standard class names
local toc_selector = make_toc_selector(toc_levels)
for _, el in ipairs(toc:query_selector(toc_selector)) do
local class = el:get_attribute("class")
if not used[class] then
table.insert(levels, class)
used[class] = level
level = level + 1
end
end
return levels, used
end
local function remove_levels(toc, matched_ids)
-- sort the matched sections according to their levels
local levels, level_numbers = find_toc_levels(toc)
debug_print("remove levels", #levels)
-- for _, level in ipairs(levels) do
-- print(level, level_numbers[level], matched_ids[level])
-- end
local keep_branch = false
local matched_levels = {}
local toc_selector = make_toc_selector(toc_levels)
for _, el in ipairs(toc:query_selector(toc_selector)) do
local name, id = get_id(el)
-- get the current toc hiearchy level
local level = level_numbers[name]
-- get the matched id for the current level
local level_id = matched_ids[name]
local matched = level_id == id
local remove = true
-- we will use this for toc elements at lower hiearchy than is the top sectioning level on the page
if matched then keep_branch = true end
-- find the parent level to the current section level
local parent_level = toc_levels[level - 1]
local parent_matched = matched_levels[parent_level]
if matched then
debug_print("match",name, id, level_id, level, #levels)
keep_branch = true
remove = false
elseif level==1 then
-- don't remove the top level
debug_print("part",name, id, level_id, level)
remove = false
matched_levels = {}
if not matched then keep_branch = false end
elseif keep_branch then
-- if level >= (#levels - 1) then
if level > matched_ids._levels then
debug_print("level",name, id, level_id, level, parent_level, parent_matched)
remove = false
elseif matched_ids.ids[id] then
debug_print("matched id",name, id, level_id, level, parent_level, parent_matched)
remove = false
elseif parent_matched then
debug_print("parent_matched",name, id, level_id, level, parent_level, parent_matched)
keep_branch = false
remove = false
end
-- else
-- print("remove", name, id, level_id, level, #matched_ids)
-- end
elseif parent_matched then
debug_print("parent_matched alternative",name, id, level_id, level, parent_level, parent_matched)
remove = false
else
debug_print("else",name, id, level_id, level, keep_branch)
keep_branch = false
end
matched_levels[name] = matched
if remove then
el:remove_node()
--print(name,id, level_id, matched)
end
end
end
-- local process = filter{ function(s)
-- local dom = domobject.parse(s)
local function collapse_toc(dom, par)
-- set options
local options = get_filter_settings "collapsetoc"
local toc_query = par.toc_query or options.toc_query or ".tableofcontents"
local title_query = par.title_query or options.title_query or ".partHead a, .chapterHead a, .sectionHead a, .subsectionHead a"
toc_levels = par.toc_levels or options.toc_levels or toc_levels
-- keep track of current id of each sectioning level
local current_ids, matched_ids = {}, {_levels = 0, ids = {}}
-- search sectioning elements
local titles = dom:query_selector(title_query)
local section_ids = {}
for _, x in ipairs(titles) do
-- get their id attributes and save them in a table
section_ids[#section_ids+1] = x:get_attribute("id")
end
-- we need to retrieve the first table of contents
local toctables = dom:query_selector(toc_query) or {}
-- process only when we got a TOC
debug_print("toc query", toc_query, #toctables)
if #toctables > 0 then
local tableofcontents = toctables[1]
-- all toc entries are in span elements
local toc = tableofcontents:query_selector("span")
local currentpart = false
local part_elements = {}
for _, el in ipairs(toc) do
-- get sectioning level and id of the current TOC entry
local name, id = get_id(el)
-- set the id of the current sectioning level
current_ids[name] = id
for _, sectid in ipairs(section_ids) do
-- detect if the current TOC entry match some sectioning element in the current document
if id == sectid then
currentpart = true
-- save the current id as a matched id
matched_ids.ids[id] = true
-- copy the TOC hiearchy for the current toc level
for i, level in ipairs(toc_levels) do
-- print("xxx",i, level, current_ids[level])
matched_ids[level] = current_ids[level]
-- set the maximum matched level
if i > matched_ids._levels then matched_ids._levels = i end
if level == name then break end
end
debug_print("match", id)
end
end
end
remove_levels(tableofcontents, matched_ids)
-- remove sections from the last part
-- remove_sections(part_elements,currentpart)
-- remove unneeded br elements
local br = tableofcontents:query_selector("br")
for _, el in ipairs(br) do el:remove_node() end
-- remove unneded whitespace
for _, el in ipairs(tableofcontents:get_children()) do
if el:is_text() then el:remove_node() end
end
end
return dom
end
return collapse_toc
-- Make:match("html$", process)
|