1if not modules then modules = { } end modules ['strc-bkm'] = {
2 version = 0.200,
3 comment = "companion to strc-bkm.mkiv",
4 author = "Hans Hagen, PRAGMA-ADE, Hasselt NL",
5 copyright = "PRAGMA ADE / ConTeXt Development Team",
6 license = "see context related readme files"
7}
8
9
10
11
12
13
14
15
16
17
18local next, type = next, type
19local gsub, lower = string.gsub, string.lower
20local concat = table.concat
21local settings_to_hash = utilities.parsers.settings_to_hash
22
23local trace_bookmarks = false trackers.register("references.bookmarks", function(v) trace_bookmarks = v end)
24local report_bookmarks = logs.reporter("structure","bookmarks")
25
26local structures = structures
27
28structures.bookmarks = structures.bookmarks or { }
29
30local bookmarks = structures.bookmarks
31local sections = structures.sections
32local lists = structures.lists
33local levelmap = sections.levelmap
34local variables = interfaces.variables
35local implement = interfaces.implement
36local codeinjections = backends.codeinjections
37
38bookmarks.method = "internal"
39
40local names = { }
41local opened = { }
42local forced = { }
43local numbered = { }
44
45function bookmarks.setopened(key,value)
46 if value == nil then
47 value = true
48 end
49 if type(key) == "table" then
50 for i=1,#key do
51 opened[key[i]] = value
52 end
53 else
54 opened[key] = value
55 end
56end
57
58function bookmarks.register(settings)
59 local force = settings.force == variables.yes
60 local number = settings.number == variables.yes
61 local allopen = settings.opened == variables.all
62 for k, v in next, settings_to_hash(settings.names or "") do
63 names[k] = true
64 if force then
65 forced[k] = true
66 if allopen then
67 opened[k] = true
68 end
69 end
70 if number then
71 numbered[k] = true
72 end
73 end
74 if not allopen then
75 for k, v in next, settings_to_hash(settings.opened or "") do
76 opened[k] = true
77 end
78 end
79end
80
81function bookmarks.overload(name,text)
82 local l, ls = lists.tobesaved, nil
83 if #l == 0 then
84
85 elseif name == "" then
86 ls = l[#l]
87 else
88 for i=#l,0,-1 do
89 local li = l[i]
90 local metadata = li.metadata
91 if metadata and not metadata.nolist and metadata.name == name then
92 ls = li
93 break
94 end
95 end
96 end
97 if ls then
98 local titledata = ls.titledata
99 if titledata then
100 titledata.bookmark = text
101 end
102 end
103
104
105end
106
107local function stripped(str)
108 str = gsub(str,"\\([A-Z]+)","%1")
109 str = gsub(str,"\\ "," ")
110 str = gsub(str,"\\([A-Za-z]+) *{(.-)}","%2")
111 str = gsub(str," +"," ")
112 return str
113end
114
115
116
117local numberspec = { }
118
119function bookmarks.setup(spec)
120
121 for k, v in next, spec do
122 numberspec[k] = v
123 end
124end
125
126function bookmarks.place()
127 if next(names) then
128 local levels = { }
129 local noflevels = 0
130 local lastlevel = 1
131 local nofblocks = #lists.sectionblocks
132 local showblocktitle = toboolean(numberspec.showblocktitle,true)
133
134 local allblocks = sections.sectionblockdata
135 for i=1,nofblocks do
136 local block = lists.sectionblocks[i]
137 local blockdone = nofblocks == 1
138 local list = lists.filter {
139 names = names,
140 criterium = block .. ":all",
141 forced = forced,
142 }
143 for i=1,#list do
144 local li = list[i]
145 local metadata = li.metadata
146 local name = metadata.name
147 if not metadata.nolist or forced[name] then
148 local titledata = li.titledata
149
150 if not titledata then
151 local userdata = li.userdata
152 if userdata then
153 local first = userdata.first
154 local second = userdata.second
155 if first then
156 if second then
157 titledata = { title = first .. " " .. second }
158 else
159 titledata = { title = first }
160 end
161 elseif second then
162 titledata = { title = second }
163 else
164
165 end
166 end
167 end
168
169 if titledata then
170 if not blockdone then
171 if showblocktitle then
172
173 local blockdata = allblocks[block]
174 local references = li.references
175 noflevels = noflevels + 1
176 levels[noflevels] = {
177 level = 1,
178 title = stripped(blockdata.bookmark ~= "" and blockdata.bookmark or block),
179 reference = references,
180 opened = allopen or opened[name],
181 realpage = references and references.realpage or 0,
182 usedpage = true,
183 }
184 end
185 blockdone = true
186 end
187 local structural = levelmap[name]
188 lastlevel = structural or lastlevel
189 if nofblocks > 1 then
190
191 lastlevel = lastlevel + 1
192 end
193 local title = titledata.bookmark
194 if not title or title == "" then
195
196
197
198
199 title = titledata.title or "?"
200
201 end
202
203
204
205
206
207
208
209
210
211
212
213
214
215if numbered[name] then
216 local numberdata = li.numberdata
217 if numberdata and not numberdata.hidenumber then
218
219 local number = sections.typesetnumber(numberdata,"direct",numberspec,numberdata)
220 if number and #number > 0 then
221 title = concat(number) .. " " .. title
222 end
223 end
224end
225 noflevels = noflevels + 1
226 local references = li.references
227 levels[noflevels] = {
228 level = lastlevel,
229 title = stripped(title),
230 reference = references,
231 opened = allopen or opened[name],
232 realpage = references and references.realpage or 0,
233 usedpage = true,
234 structural = structural,
235 name = name,
236 }
237 end
238 end
239 end
240 end
241
242 bookmarks.finalize(levels)
243 function bookmarks.place() end
244 end
245end
246
247function bookmarks.flatten(levels)
248 if not levels then
249
250 return { }
251 end
252
253
254
255
256 local noflevels = #levels
257 if noflevels > 1 then
258 local function showthem()
259 for i=1,noflevels do
260 local level = levels[i]
261
262
263
264 report_bookmarks("%i > %s > %s > %s",level.level,level.reference.block,level.name,level.title)
265
266 end
267 end
268 if trace_bookmarks then
269 report_bookmarks("checking structure")
270 showthem()
271 end
272 local skip = false
273 local done = 0
274 local start = 1
275 local one = levels[1]
276 local first = one.level
277 local block = one.reference.block
278 for i=2,noflevels do
279 local current = levels[i]
280 local new = current.level
281 local reference = current.reference
282 local newblock = type(reference) == "table" and current.reference.block or block
283 if newblock ~= block then
284 first = new
285 block = newblock
286 start = i
287 skip = false
288 elseif skip then
289
290 elseif new > first then
291 skip = true
292 elseif new < first then
293 for j=start,i-1 do
294 local previous = levels[j]
295 local old = previous.level
296 previous.level = new
297 if trace_bookmarks then
298 report_bookmarks("promoting entry %a from level %a to %a: %s",j,old,new,previous.title)
299 end
300 done = done + 1
301 end
302 skip = true
303 end
304 end
305 if trace_bookmarks then
306 if done > 0 then
307 report_bookmarks("%a entries promoted")
308 showthem()
309 else
310 report_bookmarks("nothing promoted")
311 end
312 end
313 end
314 return levels
315end
316
317local extras = { }
318local lists = { }
319local names = { }
320
321bookmarks.extras = extras
322
323local function cleanname(name)
324 return lower(file.basename(name))
325end
326
327function extras.register(name,levels)
328 if name and levels then
329 name = cleanname(name)
330 local found = names[name]
331 if found then
332 lists[found].levels = levels
333 else
334 lists[#lists+1] = {
335 name = name,
336 levels = levels,
337 }
338 names[name] = #lists
339 end
340 end
341end
342
343function extras.get(name)
344 if name then
345 local found = names[cleanname(name)]
346 if found then
347 return lists[found].levels
348 end
349 else
350 return lists
351 end
352end
353
354function extras.reset(name)
355 local l, n = { }, { }
356 if name then
357 name = cleanname(name)
358 for i=1,#lists do
359 local li = lists[i]
360 local ln = li.name
361 if name == ln then
362
363 else
364 local m = #l + 1
365 l[m] = li
366 n[ln] = m
367 end
368 end
369 end
370 lists, names = l, n
371end
372
373local function checklists()
374 for i=1,#lists do
375 local levels = lists[i].levels
376 for j=1,#levels do
377 local entry = levels[j]
378 local pageindex = entry.pageindex
379 if pageindex then
380 entry.reference = figures.getrealpage(pageindex)
381 entry.pageindex = nil
382 end
383 end
384 end
385end
386
387function extras.tosections(levels)
388 local sections = { }
389 local noflists = #lists
390 for i=1,noflists do
391 local levels = lists[i].levels
392 local data = { }
393 sections[i] = data
394 for j=1,#levels do
395 local entry = levels[j]
396 if entry.usedpage then
397 local section = entry.section
398 local d = data[section]
399 if d then
400 d[#d+1] = entry
401 else
402 data[section] = { entry }
403 end
404 end
405 end
406 end
407 return sections
408end
409
410function extras.mergesections(levels,sections)
411 if not sections or #sections == 0 then
412 return levels
413 elseif not levels then
414 return { }
415 else
416 local merge = { }
417 local noflists = #lists
418 if #levels == 0 then
419 local level = 0
420 local section = 0
421 for i=1,noflists do
422 local entries = sections[i][0]
423 if entries then
424 for i=1,#entries do
425 local entry = entries[i]
426 merge[#merge+1] = entry
427 entry.level = entry.level + level
428 end
429 end
430 end
431 else
432 for j=1,#levels do
433 local entry = levels[j]
434 merge[#merge+1] = entry
435 local section = entry.reference.section
436 local level = entry.level
437 entry.section = section
438 for i=1,noflists do
439 local entries = sections[i][section]
440 if entries then
441 for i=1,#entries do
442 local entry = entries[i]
443 merge[#merge+1] = entry
444 entry.level = entry.level + level
445 end
446 end
447 end
448 end
449 end
450 return merge
451 end
452end
453
454function bookmarks.merge(levels,mode)
455 return extras.mergesections(levels,extras.tosections())
456end
457
458local sequencers = utilities.sequencers
459local appendgroup = sequencers.appendgroup
460local appendaction = sequencers.appendaction
461
462local bookmarkactions = sequencers.new {
463 arguments = "levels,method",
464 returnvalues = "levels",
465 results = "levels",
466}
467
468appendgroup(bookmarkactions,"before")
469appendgroup(bookmarkactions,"system")
470appendgroup(bookmarkactions,"after" )
471
472appendaction(bookmarkactions,"system",bookmarks.flatten)
473appendaction(bookmarkactions,"system",bookmarks.merge)
474
475function bookmarks.finalize(levels)
476 local method = bookmarks.method or "internal"
477 checklists()
478 levels = bookmarkactions.runner(levels,method)
479 if levels and #levels > 0 then
480
481 local purged = { }
482 for i=1,#levels do
483 local l = levels[i]
484 if l.usedpage ~= false then
485 purged[#purged+1] = l
486 end
487 end
488
489 codeinjections.addbookmarks(purged,method)
490 else
491
492 end
493end
494
495function bookmarks.installhandler(what,where,func)
496 if not func then
497 where, func = "after", where
498 end
499 if where == "before" or where == "after" then
500 sequencers.appendaction(bookmarkactions,where,func)
501 else
502 report_tex("installing bookmark %a handlers in %a is not possible",what,tostring(where))
503 end
504end
505
506
507
508implement {
509 name = "setupbookmarks",
510 actions = bookmarks.setup,
511 arguments = {
512 {
513 { "separatorset" },
514 { "conversionset" },
515 { "starter" },
516 { "stopper" },
517 { "segments" },
518 { "showblocktitle" },
519 }
520 }
521}
522
523implement {
524 name = "registerbookmark",
525 actions = bookmarks.register,
526 arguments = {
527 {
528 { "names" },
529 { "opened" },
530 { "force" },
531 { "number" },
532 }
533 }
534}
535
536implement {
537 name = "overloadbookmark",
538 actions = bookmarks.overload,
539 arguments = "2 strings",
540}
541 |