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