1if not modules then modules = { } end modules ['strc-mar'] = {
2 version = 1.001,
3 comment = "companion to strc-mar.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
12local insert, concat = table.insert, table.concat
13local tostring, next, rawget, type = tostring, next, rawget, type
14local lpegmatch = lpeg.match
15
16local context = context
17local commands = commands
18
19local implement = interfaces.implement
20
21local allocate = utilities.storage.allocate
22local setmetatableindex = table.setmetatableindex
23
24local nuts = nodes.nuts
25local tonut = nuts.tonut
26
27local getid = nuts.getid
28local getlist = nuts.getlist
29local getattr = nuts.getattr
30local getbox = nuts.getbox
31
32local nextnode = nuts.traversers.node
33
34local nodecodes = nodes.nodecodes
35local whatsitcodes = nodes.whatsitcodes
36
37local glyph_code = nodecodes.glyph
38local hlist_code = nodecodes.hlist
39local vlist_code = nodecodes.vlist
40local whatsit_code = nodecodes.whatsit
41
42local lateluawhatsit_code = whatsitcodes.latelua
43
44local texsetattribute = tex.setattribute
45
46local a_marks = attributes.private("marks")
47
48local trace_set = false trackers.register("marks.set", function(v) trace_set = v end)
49local trace_get = false trackers.register("marks.get", function(v) trace_get = v end)
50local trace_details = false trackers.register("marks.details", function(v) trace_details = v end)
51
52local report_marks = logs.reporter("structure","marks")
53
54local variables = interfaces.variables
55
56local v_first = variables.first
57local v_last = variables.last
58local v_previous = variables.previous
59local v_next = variables.next
60local v_top = variables.top
61local v_bottom = variables.bottom
62local v_current = variables.current
63local v_default = variables.default
64local v_page = variables.page
65local v_all = variables.all
66local v_keep = variables.keep
67
68local v_nocheck_suffix = ":" .. variables.nocheck
69
70local v_first_nocheck = variables.first .. v_nocheck_suffix
71local v_last_nocheck = variables.last .. v_nocheck_suffix
72local v_previous_nocheck = variables.previous .. v_nocheck_suffix
73local v_next_nocheck = variables.next .. v_nocheck_suffix
74local v_top_nocheck = variables.top .. v_nocheck_suffix
75local v_bottom_nocheck = variables.bottom .. v_nocheck_suffix
76
77local structures = structures
78local marks = structures.marks
79
80local settings_to_array = utilities.parsers.settings_to_array
81
82local boxes_too = false
83
84directives.register("marks.boxestoo", function(v) boxes_too = v end)
85
86local data = marks.data or allocate()
87marks.data = data
88
89storage.register("structures/marks/data", marks.data, "structures.marks.data")
90
91local stack, topofstack = { }, 0
92
93local ranges = {
94 [v_page] = {
95 first = 0,
96 last = 0,
97 },
98}
99
100local function resolve(t,k)
101 if k then
102 if trace_set or trace_get then
103 report_marks("undefined mark, name %a",k)
104 end
105 local crap = { autodefined = true }
106 t[k] = crap
107 return crap
108 else
109
110 end
111end
112
113setmetatableindex(data, resolve)
114
115function marks.exists(name)
116 return rawget(data,name) ~= nil
117end
118
119
120
121local function sweep(head,first,last)
122 for n, id, subtype in nextnode, head do
123
124 if id == glyph_code or (id == whatsit_code and subtype == lateluawhatsit_code) then
125 local a = getattr(n,a_marks)
126 if not a then
127
128 elseif first == 0 then
129 first, last = a, a
130 elseif a > last then
131 last = a
132 end
133 elseif id == hlist_code or id == vlist_code then
134 if boxes_too then
135 local a = getattr(n,a_marks)
136 if not a then
137
138 elseif first == 0 then
139 first, last = a, a
140 elseif a > last then
141 last = a
142 end
143 end
144 local list = getlist(n)
145 if list then
146 first, last = sweep(list,first,last)
147 end
148 end
149 end
150 return first, last
151end
152
153local classes = { }
154
155setmetatableindex(classes, function(t,k) local s = settings_to_array(k) t[k] = s return s end)
156
157local lasts = { }
158
159function marks.synchronize(class,n,option)
160 local box = getbox(n)
161 if box then
162 local first, last = sweep(getlist(box),0,0)
163 if option == v_keep and first == 0 and last == 0 then
164 if trace_get or trace_set then
165 report_marks("action %a, class %a, box %a","retain at synchronize",class,n)
166 end
167
168 first = lasts[class] or 0
169 last = first
170 else
171 lasts[class] = last
172 local classlist = classes[class]
173 for i=1,#classlist do
174 local class = classlist[i]
175 local range = ranges[class]
176 if range then
177 range.first = first
178 range.last = last
179 else
180 range = {
181 first = first,
182 last = last,
183 }
184 ranges[class] = range
185 end
186 if trace_get or trace_set then
187 report_marks("action %a, class %a, first %a, last %a","synchronize",class,range.first,range.last)
188 end
189 end
190 end
191 elseif trace_get or trace_set then
192 report_marks("action %s, class %a, box %a","synchronize without content",class,n)
193 end
194end
195
196
197
198local function resolve(t,k)
199 if k == "fullchain" then
200 local fullchain = { }
201 local chain = t.chain
202 while chain and chain ~= "" do
203 insert(fullchain,1,chain)
204 chain = data[chain].chain
205 end
206 t[k] = fullchain
207 return fullchain
208 elseif k == "chain" then
209 t[k] = ""
210 return ""
211 elseif k == "reset" or k == "set" then
212 t[k] = 0
213 return 0
214 elseif k == "parent" then
215 t[k] = false
216 return false
217 end
218end
219
220function marks.define(name,settings)
221 if not settings then
222 settings = { }
223 elseif type(settings) == "string" then
224 settings = { parent = settings }
225 end
226 data[name] = settings
227 local parent = settings.parent
228 if parent == nil or parent == "" or parent == name then
229 settings.parent = false
230 else
231 local dp = data[parent]
232 if not dp then
233 settings.parent = false
234 elseif dp.parent then
235 settings.parent = dp.parent
236 end
237 end
238 setmetatableindex(settings, resolve)
239end
240
241for k, v in next, data do
242 setmetatableindex(v,resolve)
243end
244
245local function parentname(name)
246 local dn = data[name]
247 return dn and dn.parent or name
248end
249
250function marks.relate(name,chain)
251 local dn = data[name]
252 if dn and not dn.parent then
253 if chain and chain ~= "" then
254 dn.chain = chain
255 local dc = data[chain]
256 if dc then
257 local children = dc.children
258 if not children then
259 children = { }
260 dc.children = children
261 end
262 children[#children+1] = name
263 end
264 elseif trace_set then
265 report_marks("error: invalid relation, name %a, chain %a",name,chain)
266 end
267 end
268end
269
270local function resetchildren(new,name)
271 local dn = data[name]
272 if dn and not dn.parent then
273 local children = dn.children
274 if children then
275 for i=1,#children do
276 local ci = children[i]
277 new[ci] = false
278 if trace_set then
279 report_marks("action %a, parent %a, child %a","reset",name,ci)
280 end
281 resetchildren(new,ci)
282 end
283 end
284 end
285end
286
287function marks.set(name,value)
288 local dn = data[name]
289 if dn then
290 local child = name
291 local parent = dn.parent
292 if parent then
293 name = parent
294 dn = data[name]
295 end
296 dn.set = topofstack
297 if not dn.reset then
298 dn.reset = 0
299 end
300 local top = stack[topofstack]
301 local new = { }
302 if top then
303 for k, v in next, top do
304 local d = data[k]
305 local r = d.reset or 0
306 local s = d.set or 0
307 if r <= topofstack and s < r then
308 new[k] = false
309 else
310 new[k] = v
311 end
312 end
313 end
314 resetchildren(new,name)
315 new[name] = value
316 topofstack = topofstack + 1
317 stack[topofstack] = new
318 if trace_set then
319 if name == child then
320 report_marks("action %a, name %a, index %a, value %a","set",name,topofstack,value)
321 else
322 report_marks("action %a, parent %a, child %a, index %a, value %a","set",parent,child,topofstack,value)
323 end
324 end
325 texsetattribute("global",a_marks,topofstack)
326 end
327end
328
329local function reset(name)
330 if v_all then
331 if trace_set then
332 report_marks("action %a","reset all")
333 end
334 stack = { }
335 for name, dn in next, data do
336 local parent = dn.parent
337 if parent then
338 dn.reset = 0
339 dn.set = 0
340 end
341 end
342 else
343 local dn = data[name]
344 if dn then
345 local parent = dn.parent
346 if parent then
347 name = parent
348 dn = data[name]
349 end
350 if trace_set then
351 report_marks("action %a, name %a, index %a","reset",name,topofstack)
352 end
353 dn.reset = topofstack
354 local children = dn.children
355 if children then
356 for i=1,#children do
357 local ci = children[i]
358 reset(ci)
359 end
360 end
361 end
362 end
363end
364
365marks.reset = reset
366
367function marks.get(n,name,value)
368 local dn = data[name]
369 if dn then
370 name = dn.parent or name
371 local top = stack[n]
372 if top then
373 context(top[name])
374 end
375 end
376end
377
378function marks.show(first,last)
379 if first and last then
380 for k=first,last do
381 local v = stack[k]
382 if v then
383 report_marks("% 4i: %s",k,table.sequenced(v))
384 end
385 end
386 else
387 for k, v in table.sortedpairs(stack) do
388 report_marks("% 4i: %s",k,table.sequenced(v))
389 end
390 end
391end
392
393local function resolve(name,first,last,strict,quitonfalse,notrace)
394 local dn = data[name]
395 if dn then
396 local child = name
397 local parent = dn.parent
398 name = parent or child
399 dn = data[name]
400 local step, method
401 if first > last then
402 step, method = -1, "bottom-up"
403 else
404 step, method = 1, "top-down"
405 end
406 if trace_get and not notrace then
407 report_marks("action %a, strategy %a, name %a, parent %a, strict %a","request",method,child,parent,strict or false)
408 end
409 if trace_details and not notrace then
410 marks.show(first,last)
411 end
412 local r = dn.reset
413 local s = dn.set
414 if first <= last and first <= r then
415 if trace_get and not notrace then
416 report_marks("action %a, name %a, first %a, last %a, reset %a, index %a","reset first",name,first,last,r,first)
417 end
418 elseif first >= last and last <= r then
419 if trace_get and not notrace then
420 report_marks("action %a, name %a, first %a, last %a, reset %a, index %a","reset last",name,first,last,r,last)
421 end
422 elseif not stack[first] or not stack[last] then
423 if trace_get and not notrace then
424
425 report_marks("error: out of range, name %a, reset %a, index %a",name,r,first)
426 end
427 elseif strict then
428 local top = stack[first]
429 local fullchain = dn.fullchain
430 if not fullchain or #fullchain == 0 then
431 if trace_get and not notrace then
432 report_marks("warning: no full chain, trying again, name %a, first %a, last %a",name,first,last)
433 end
434 return resolve(name,first,last)
435 else
436 if trace_get and not notrace then
437 report_marks("found chain [ % => T ]",fullchain)
438 end
439 local chaindata = { }
440 local chainlength = #fullchain
441 for i=1,chainlength do
442 local cname = fullchain[i]
443 if data[cname].set > 0 then
444 local value = resolve(cname,first,last,false,false,true)
445 if value == "" then
446 if trace_get and not notrace then
447 report_marks("quitting chain, name %a, reset %a, start %a",name,r,first)
448 end
449 return ""
450 else
451 chaindata[i] = value
452 end
453 end
454 end
455 if trace_get and not notrace then
456 report_marks("using chain [ % => T ]",chaindata)
457 end
458 local value, index, found = resolve(name,first,last,false,false,true)
459 if value ~= "" then
460 if trace_get and not notrace then
461 report_marks("following chain [ % => T ]",chaindata)
462 end
463 for i=1,chainlength do
464 local cname = fullchain[i]
465 if data[cname].set > 0 and chaindata[i] ~= found[cname] then
466 if trace_get and not notrace then
467 report_marks("quiting chain, name %a, reset %a, index %a",name,r,first)
468 end
469 return ""
470 end
471 end
472 if trace_get and not notrace then
473 report_marks("found in chain, name %a, reset %a, start %a, index %a, value %a",name,r,first,index,value)
474 end
475 return value, index, found
476 elseif trace_get and not notrace then
477 report_marks("not found, name %a, reset %a",name,r)
478 end
479 end
480 else
481 for i=first,last,step do
482 local current = stack[i]
483 local value = current and current[name]
484 if value == nil then
485
486 elseif value == false then
487 if quitonfalse then
488 return ""
489 end
490 elseif value == true then
491 if trace_get and not notrace then
492 report_marks("quitting steps, name %a, reset %a, start %a, index %a",name,r,first,i)
493 end
494 return ""
495 elseif value ~= "" then
496 if trace_get and not notrace then
497 report_marks("found in steps, name %a, reset %a, start %a, index %a, value %a",name,r,first,i,value)
498 end
499 return value, i, current
500 end
501 end
502 if trace_get and not notrace then
503 report_marks("not found in steps, name %a, reset %a",name,r)
504 end
505 end
506 end
507 return ""
508end
509
510
511
512local methods = { }
513
514local function doresolve(name,rangename,swap,df,dl,strict)
515 local range = ranges[rangename] or ranges[v_page]
516 local first = range.first
517 local last = range.last
518 if trace_get then
519 report_marks("action %a, name %a, range %a, swap %a, first %a, last %a, df %a, dl %a, strict %a",
520 "resolving",name,rangename,swap or false,first,last,df,dl,strict or false)
521 end
522 if swap then
523 first, last = last + df, first + dl
524 else
525 first, last = first + df, last + dl
526 end
527 local value, index, found = resolve(name,first,last,strict)
528
529 return value, index, found
530end
531
532
533
534
535
536
537
538
539
540
541methods[v_previous] = function(name,range) return doresolve(name,range,false,-1,0,true ) end
542methods[v_top] = function(name,range) return doresolve(name,range,false, 0,0,true ) end
543methods[v_bottom] = function(name,range) return doresolve(name,range,true , 0,0,true ) end
544methods[v_next] = function(name,range) return doresolve(name,range,true , 0,1,true ) end
545
546methods[v_previous_nocheck] = function(name,range) return doresolve(name,range,false,-1,0,false) end
547methods[v_top_nocheck] = function(name,range) return doresolve(name,range,false, 0,0,false) end
548methods[v_bottom_nocheck] = function(name,range) return doresolve(name,range,true , 0,0,false) end
549methods[v_next_nocheck] = function(name,range) return doresolve(name,range,true , 0,1,false) end
550
551local function do_first(name,range,check)
552 if trace_get then
553 report_marks("action %a, name %a, range %a","resolving first",name,range)
554 end
555 local f_value, f_index, f_found = doresolve(name,range,false,0,0,check)
556 if f_found then
557 if trace_get then
558 report_marks("action %a, name %a, range %a","resolving last",name,range)
559 end
560 local l_value, l_index, l_found = doresolve(name,range,true ,0,0,check)
561 if l_found and l_index > f_index then
562 local name = parentname(name)
563 for i=f_index,l_index,1 do
564 local si = stack[i]
565 local sn = si[name]
566 if sn and sn ~= false and sn ~= true and sn ~= "" and sn ~= f_value then
567 if trace_get then
568 report_marks("action %a, name %a, range %a, index %a, value %a","resolving",name,range,i,sn)
569 end
570 return sn, i, si
571 end
572 end
573 end
574 end
575 if trace_get then
576 report_marks("resolved, name %a, range %a, using first",name,range)
577 end
578 return f_value, f_index, f_found
579end
580
581local function do_last(name,range,check)
582 if trace_get then
583 report_marks("action %a, name %a, range %a","resolving last",name,range)
584 end
585 local l_value, l_index, l_found = doresolve(name,range,true ,0,0,check)
586 if l_found then
587 if trace_get then
588 report_marks("action %a, name %a, range %a","resolving first",name,range)
589 end
590 local f_value, f_index, f_found = doresolve(name,range,false,0,0,check)
591 if f_found and l_index > f_index then
592 local name = parentname(name)
593 for i=l_index,f_index,-1 do
594 local si = stack[i]
595 local sn = si[name]
596 if sn and sn ~= false and sn ~= true and sn ~= "" and sn ~= l_value then
597 if trace_get then
598 report_marks("action %a, name %a, range %a, index %a, value %a","resolving",name,range,i,sn)
599 end
600 return sn, i, si
601 end
602 end
603 end
604 end
605 if trace_get then
606 report_marks("resolved, name %a, range %a, using first",name,range)
607 end
608 return l_value, l_index, l_found
609end
610
611methods[v_first ] = function(name,range) return do_first(name,range,true ) end
612methods[v_last ] = function(name,range) return do_last (name,range,true ) end
613methods[v_first_nocheck] = function(name,range) return do_first(name,range,false) end
614methods[v_last_nocheck ] = function(name,range) return do_last (name,range,false) end
615
616methods[v_current] = function(name,range)
617 local top = stack[topofstack]
618 return top and top[parentname(name)] or ""
619end
620
621local function fetched(name,range,method)
622 local value = (methods[method] or methods[v_first])(name,range) or ""
623 if not trace_get then
624
625 elseif value == "" then
626 report_marks("nothing fetched, name %a, range %a, method %a",name,range,method)
627 else
628 report_marks("marking fetched, name %a, range %a, method %a, value %a",name,range,method,value)
629 end
630 return value or ""
631end
632
633
634
635marks.fetched = fetched
636
637
638
639marks.tracers = marks.tracers or { }
640
641function marks.tracers.showtable()
642 context.starttabulate { "|l|l|l|lp|lp|" }
643 context.tabulaterowbold("name","parent","chain","children","fullchain")
644 context.ML()
645 for k, v in table.sortedpairs(data) do
646 local parent = v.parent or ""
647 local chain = v.chain or ""
648 local children = v.children or { }
649 local fullchain = v.fullchain or { }
650 table.sort(children)
651 context.tabulaterowtyp(k,parent,chain,concat(children," "),concat(fullchain," "))
652 end
653 context.stoptabulate()
654end
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681 local ctx_separator = context.markingseparator
682 local ctx_command = context.markingcommand
683
684 local function fetchonemark(name,range,method)
685 ctx_command(name,fetched(name,range,method))
686 end
687
688 local function fetchtwomarks(name,range)
689 ctx_command(name,fetched(name,range,v_first))
690 ctx_separator(name)
691 ctx_command(name,fetched(name,range,v_last))
692 end
693
694 local function fetchallmarks(name,range)
695 ctx_command(name,fetched(name,range,v_previous))
696 ctx_separator(name)
697 ctx_command(name,fetched(name,range,v_first))
698 ctx_separator(name)
699 ctx_command(name,fetched(name,range,v_last))
700 end
701
702function marks.fetch(name,range,method)
703 if trace_get then
704 report_marks("marking requested, name %a, range %a, method %a",name,range,method)
705 end
706 if method == "" or method == v_default then
707 fetchonemark(name,range,v_first)
708 elseif method == v_both then
709 fetchtwomarks(name,range)
710 elseif method == v_all then
711 fetchallmarks(name,range)
712 else
713 fetchonemark(name,range,method)
714 end
715end
716
717function marks.fetchonemark (name,range,method) fetchonemark (name,range,method) end
718function marks.fetchtwomarks(name,range) fetchtwomarks(name,range ) end
719function marks.fetchallmarks(name,range) fetchallmarks(name,range ) end
720
721
722
723local pattern = lpeg.afterprefix("li::")
724
725function marks.title(tag,n)
726 local listindex = lpegmatch(pattern,n)
727 if listindex then
728 commands.savedlisttitle(tag,listindex,"marking")
729 else
730 context(n)
731 end
732end
733
734function marks.number(tag,n)
735 local listindex = lpegmatch(pattern,n)
736 if listindex then
737 commands.savedlistnumber(tag,listindex)
738 else
739
740 context(n)
741 end
742end
743
744
745
746implement { name = "markingtitle", actions = marks.title, arguments = "2 strings" }
747implement { name = "markingnumber", actions = marks.number, arguments = "2 strings" }
748
749implement { name = "definemarking", actions = marks.define, arguments = "2 strings" }
750implement { name = "relatemarking", actions = marks.relate, arguments = "2 strings" }
751implement { name = "setmarking", actions = marks.set, arguments = "2 strings" }
752implement { name = "resetmarking", actions = marks.reset, arguments = "string" }
753implement { name = "synchronizemarking", actions = marks.synchronize, arguments = { "string", "integer", "string" } }
754implement { name = "getmarking", actions = marks.fetch, arguments = "3 strings" }
755implement { name = "fetchonemark", actions = marks.fetchonemark, arguments = "3 strings" }
756implement { name = "fetchtwomarks", actions = marks.fetchtwomarks, arguments = "2 strings" }
757implement { name = "fetchallmarks", actions = marks.fetchallmarks, arguments = "2 strings" }
758
759implement { name = "doifelsemarking", actions = { marks.exists, commands.doifelse }, arguments = "string" }
760 |