1if not modules then modules = { } end modules ['strc-reg'] = {
2 version = 1.001,
3 comment = "companion to strc-reg.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
9local next, type, tonumber, rawget = next, type, tonumber, rawget
10local char, format, gmatch = string.char, string.format, string.gmatch
11local equal, concat, remove = table.are_equal, table.concat, table.remove
12local lpegmatch, lpegpatterns, P, C, Ct, Cs = lpeg.match, lpeg.patterns, lpeg.P, lpeg.C, lpeg.Ct, lpeg.Cs
13local allocate = utilities.storage.allocate
14
15local trace_registers = false trackers.register("structures.registers", function(v) trace_registers = v end)
16
17local report_registers = logs.reporter("structure","registers")
18
19local structures = structures
20local registers = structures.registers
21local helpers = structures.helpers
22local sections = structures.sections
23local documents = structures.documents
24local pages = structures.pages
25local references = structures.references
26
27local usedinternals = references.usedinternals
28
29local mappings = sorters.mappings
30local entries = sorters.entries
31local replacements = sorters.replacements
32
33local processors = typesetters.processors
34local splitprocessor = processors.split
35
36local texgetcount = tex.getcount
37local texiscount = tex.iscount
38
39local variables = interfaces.variables
40local v_forward = variables.forward
41local v_all = variables.all
42local v_no = variables.no
43local v_yes = variables.yes
44local v_packed = variables.packed
45local v_current = variables.current
46local v_previous = variables.previous
47local v_first = variables.first
48local v_last = variables.last
49local v_text = variables.text
50local v_section = variables.section
51
52local context = context
53local ctx_latelua = context.latelua
54
55local implement = interfaces.implement
56
57local matchingtilldepth = sections.matchingtilldepth
58local numberatdepth = sections.numberatdepth
59local currentlevel = sections.currentlevel
60local currentid = sections.currentid
61
62local touserdata = helpers.touserdata
63
64local internalreferences = references.internals
65local setinternalreference = references.setinternalreference
66
67local setmetatableindex = table.setmetatableindex
68
69local absmaxlevel = 5
70
71local c_realpageno = texiscount("realpageno")
72local c_locationcount = texiscount("locationcount")
73
74local h_prefixpage = helpers.prefixpage
75local h_prefixlastpage = helpers.prefixlastpage
76local h_title = helpers.title
77local h_prefix = helpers.prefix
78
79local ctx_startregisteroutput = context.startregisteroutput
80local ctx_stopregisteroutput = context.stopregisteroutput
81local ctx_startregistersection = context.startregistersection
82local ctx_stopregistersection = context.stopregistersection
83local ctx_startregisterentries = context.startregisterentries
84local ctx_stopregisterentries = context.stopregisterentries
85local ctx_startregisterentry = context.startregisterentry
86local ctx_stopregisterentry = context.stopregisterentry
87local ctx_startregisterpages = context.startregisterpages
88local ctx_stopregisterpages = context.stopregisterpages
89local ctx_startregisterseewords = context.startregisterseewords
90local ctx_stopregisterseewords = context.stopregisterseewords
91
92local ctx_registerentry = context.registerentry
93local ctx_registerseeword = context.registerseeword
94local ctx_registerpagerange = context.registerpagerange
95local ctx_registeronepage = context.registeronepage
96local ctx_registersection = context.registersection
97local ctx_registerpacked = context.registerpacked
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
199
200
201
202
203
204local function filtercollected(names,criterium,number,collected,prevmode)
205 if not criterium or criterium == "" then
206 criterium = v_all
207 end
208 local data = documents.data
209 local numbers = data.numbers
210 local depth = data.depth
211 local hash = { }
212 local result = { }
213 local nofresult = 0
214 local all = not names or names == "" or names == v_all
215 local detail = nil
216 if not all then
217 for s in gmatch(names,"[^, ]+") do
218 hash[s] = true
219 end
220 end
221 if criterium == v_all or criterium == v_text then
222 for i=1,#collected do
223 local v = collected[i]
224 if all then
225 nofresult = nofresult + 1
226 result[nofresult] = v
227 else
228 local vmn = v.metadata and v.metadata.name
229 if hash[vmn] then
230 nofresult = nofresult + 1
231 result[nofresult] = v
232 end
233 end
234 end
235 elseif criterium == v_current then
236 local collectedsections = sections.collected
237 for i=1,#collected do
238 local v = collected[i]
239 local sectionnumber = collectedsections[v.references.section]
240 if sectionnumber then
241 local cnumbers = sectionnumber.numbers
242 if prevmode then
243 if (all or hash[v.metadata.name]) and #cnumbers >= depth then
244 local ok = true
245 for d=1,depth do
246 if not (cnumbers[d] == numbers[d]) then
247 ok = false
248 break
249 end
250 end
251 if ok then
252 nofresult = nofresult + 1
253 result[nofresult] = v
254 end
255 end
256 else
257 if (all or hash[v.metadata.name]) and #cnumbers > depth then
258 local ok = true
259 for d=1,depth do
260 local cnd = cnumbers[d]
261 if not (cnd == 0 or cnd == numbers[d]) then
262 ok = false
263 break
264 end
265 end
266 if ok then
267 nofresult = nofresult + 1
268 result[nofresult] = v
269 end
270 end
271 end
272 end
273 end
274 elseif criterium == v_previous then
275 local collectedsections = sections.collected
276 for i=1,#collected do
277 local v = collected[i]
278 local sectionnumber = collectedsections[v.references.section]
279 if sectionnumber then
280 local cnumbers = sectionnumber.numbers
281 if (all or hash[v.metadata.name]) and #cnumbers >= depth then
282 local ok = true
283 if prevmode then
284 for d=1,depth do
285 if not (cnumbers[d] == numbers[d]) then
286 ok = false
287 break
288 end
289 end
290 else
291 for d=1,depth do
292 local cnd = cnumbers[d]
293 if not (cnd == 0 or cnd == numbers[d]) then
294 ok = false
295 break
296 end
297 end
298 end
299 if ok then
300 nofresult = nofresult + 1
301 result[nofresult] = v
302 end
303 end
304 end
305 end
306 elseif criterium == variables["local"] then
307 if sections.autodepth(data.numbers) == 0 then
308 return filtercollected(names,v_all,number,collected,prevmode)
309 else
310 return filtercollected(names,v_current,number,collected,prevmode)
311 end
312 else
313
314
315 local depth = sections.getlevel(criterium)
316 local number = tonumber(number) or numberatdepth(depth) or 0
317 if trace_registers then
318 detail = format("depth: %s, number: %s, numbers: %s, startset: %s",depth,number,concat(sections.numbers(),".",1,depth),#collected)
319 end
320 if number > 0 then
321 for i=1,#collected do
322 local v = collected[i]
323 local r = v.references
324 if r then
325 local sectionnumber = sections.collected[r.section]
326 if sectionnumber then
327 local metadata = v.metadata
328 local cnumbers = sectionnumber.numbers
329 if cnumbers then
330 if (all or hash[metadata.name or false]) and #cnumbers >= depth and matchingtilldepth(depth,cnumbers) then
331 nofresult = nofresult + 1
332 result[nofresult] = v
333 end
334 end
335 end
336 end
337 end
338 end
339 end
340 if trace_registers then
341 if detail then
342 report_registers("criterium %a, detail %a, found %a",criterium,detail,#result)
343 else
344 report_registers("criterium %a, detail %a, found %a",criterium,nil,#result)
345 end
346 end
347 return result
348end
349
350local tobesaved = allocate()
351local collected = allocate()
352
353registers.collected = collected
354registers.tobesaved = tobesaved
355registers.filtercollected = filtercollected
356
357
358
359
360
361
362
363
364local function checker(t,k)
365 local v = {
366 metadata = {
367 language = 'en',
368 sorted = false,
369 class = class,
370 },
371 entries = { },
372 }
373 t[k] = v
374 return v
375end
376
377local function initializer()
378 tobesaved = registers.tobesaved
379 collected = registers.collected
380 setmetatableindex(tobesaved,checker)
381 setmetatableindex(collected,checker)
382 local usedinternals = references.usedinternals
383 for name, list in next, collected do
384 local entries = list.entries
385 if not list.metadata.notsaved then
386 for e=1,#entries do
387 local entry = entries[e]
388 local r = entry.references
389 if r then
390 local internal = r and r.internal
391 if internal then
392 internalreferences[internal] = entry
393 usedinternals[internal] = r.used
394 end
395 end
396 end
397 end
398 end
399
400end
401
402local function finalizer()
403 local flaginternals = references.flaginternals
404 local usedviews = references.usedviews
405 for k, v in next, tobesaved do
406 local entries = v.entries
407 if entries then
408 for i=1,#entries do
409 local r = entries[i].references
410 if r then
411 local i = r.internal
412 local f = flaginternals[i]
413 if f then
414 r.used = usedviews[i] or true
415 end
416 end
417 end
418 end
419 end
420end
421
422job.register('structures.registers.collected', tobesaved, initializer, finalizer)
423
424setmetatableindex(tobesaved,checker)
425setmetatableindex(collected,checker)
426
427local function defineregister(class,method)
428 local d = tobesaved[class]
429 if method == v_forward then
430 d.metadata.notsaved = true
431 end
432end
433
434registers.define = defineregister
435registers.setmethod = defineregister
436
437implement {
438 name = "defineregister",
439 actions = defineregister,
440 arguments = "2 strings",
441}
442
443implement {
444 name = "setregistermethod",
445 actions = defineregister,
446 arguments = "2 strings",
447}
448
449local p_s = P("+")
450local p_e = P("&") * (1-P(";"))^0 * P(";")
451local p_r = C((p_e + (1-p_s))^0)
452
453local p_t = Cs ( (
454 lpegpatterns.nestedbraces
455
456 + lpegpatterns.nestedparents
457 + P("$") * (1-P("$"))^1 * P("$")
458 + (1-p_s)
459 )^1)
460
461local entrysplitter_xml = Ct(p_r * (p_s * p_r)^0)
462
463local entrysplitter_tex = Ct((p_t * p_s^-1)^0)
464
465local tagged = { }
466
467
468
469local function preprocessentries(rawdata)
470 local entries = rawdata.entries
471 if entries then
472 local processors = rawdata.processors
473 local et = entries.entries
474 local kt = entries.keys
475 local pt = entries.processors
476 local entryproc = processors and processors.entry
477 local pageproc = processors and processors.page
478 local coding = rawdata.metadata.coding
479 if entryproc == "" then
480 entryproc = nil
481 end
482 if pageproc == "" then
483 pageproc = nil
484 end
485 if not et then
486 local p, e = splitprocessor(entries.entry or "")
487 if p then
488 entryproc = p
489 end
490 et = lpegmatch(coding == "xml" and entrysplitter_xml or entrysplitter_tex,e)
491 end
492 if not kt then
493 local p, k = splitprocessor(entries.key or "")
494 if p then
495 pageproc = p
496 end
497 kt = lpegmatch(coding == "xml" and entrysplitter_xml or entrysplitter_tex,k)
498 end
499 if not pt then
500 pt = { }
501 end
502
503 entries = { }
504 local ok = false
505 for k=#et,1,-1 do
506 local etk = et[k]
507 local ktk = kt[k]
508 local ptk = pt[k]
509 if not ok and etk == "" then
510 entries[k] = nil
511 else
512 if not etk then
513 etk = ""
514 end
515
516 if ptk == "" then
517 ptk = nil
518 end
519
520 if ktk == "" then
521 if ptk then
522 ktk = false
523 else
524 ktk = nil
525 end
526 end
527 entries[k] = { etk, ktk, ptk }
528 ok = true
529 end
530 end
531 rawdata.list = entries
532 if pageproc or entryproc then
533 rawdata.processors = { entryproc, pageproc }
534 end
535 rawdata.entries = nil
536 end
537 local seeword = rawdata.seeword
538 if seeword then
539 local text = seeword.text or ""
540 local sp, st = splitprocessor(text)
541 seeword.text = text
542
543 if sp then
544 seeword.processor = sp
545 end
546 end
547end
548
549local function storeregister(rawdata)
550 local references = rawdata.references
551 local metadata = rawdata.metadata
552
553 if not metadata then
554 metadata = { }
555 rawdata.metadata = metadata
556 end
557
558 if not metadata.kind then
559 metadata.kind = "entry"
560 end
561
562
563 if not metadata.catcodes then
564 metadata.catcodes = tex.catcodetable
565 end
566
567 local name = metadata.name
568 local notsaved = tobesaved[name].metadata.notsaved
569
570 if not references then
571 references = { }
572 rawdata.references = references
573 end
574
575 local internal = references.internal
576 if not internal then
577 internal = texgetcount(c_locationcount)
578 references.internal = internal
579 end
580
581 if notsaved then
582 usedinternals[internal] = references.used
583 end
584
585 if not references.realpage then
586 references.realpage = 0
587 end
588
589 local userdata = rawdata.userdata
590 if userdata then
591 rawdata.userdata = touserdata(userdata)
592 end
593
594 references.block = structures.sections.currentblock()
595 references.structure = resolvers.jobs.currentstructure()
596 references.section = currentid()
597 metadata.level = currentlevel()
598
599 local data = notsaved and collected[name] or tobesaved[name]
600 local entries = data.entries
601
602 internalreferences[internal] = rawdata
603 preprocessentries(rawdata)
604 entries[#entries+1] = rawdata
605 local label = references.label
606 if label and label ~= "" then
607 tagged[label] = #entries
608 else
609 references.label = nil
610 end
611 return #entries
612end
613
614local function enhanceregister(specification)
615 local name = specification.name
616 local n = specification.n
617 local saved = tobesaved[name]
618 local data = saved.metadata.notsaved and collected[name] or saved
619 local entry = data.entries[n]
620 if entry then
621 entry.references.realpage = texgetcount(c_realpageno)
622 end
623end
624
625local function extendregister(rawdata)
626 local references = rawdata.references
627 local metadata = rawdata.metadata
628 if references and metadata then
629 local name = rawdata.metadata.name
630 local tag = references.label
631 if type(tag) == "string" then
632 tag = tagged[tag]
633 end
634 if tag then
635 local data = tobesaved[name].metadata.notsaved and collected[name] or tobesaved[name]
636 local entry = data.entries[tag]
637 if entry then
638 references = entry.references
639 references.lastrealpage = texgetcount(c_realpageno)
640 references.lastsection = currentid()
641 if rawdata then
642 local userdata = rawdata.userdata
643 if userdata and userdata ~= "" then
644 entry.userdata = table.merged(entry.userdata or { },touserdata(userdata))
645 end
646 if rawdata.entries then
647 preprocessentries(rawdata)
648 if next(rawdata.list) then
649 entry.list = rawdata.list
650 end
651 end
652 if metadata then
653 entry.metadata = table.merged(entry.metadata or { },metadata)
654 end
655 end
656 end
657 end
658 end
659end
660
661registers.store = storeregister
662registers.enhance = enhanceregister
663registers.extend = extendregister
664
665function registers.get(tag,n)
666 local list = tobesaved[tag]
667 return list and list.entries[n]
668end
669
670implement {
671 name = "enhanceregister",
672 arguments = { "string", "integer" },
673 actions = function(name,n)
674 enhanceregister { name = name, n = n }
675 end,
676}
677
678implement {
679 name = "deferredenhanceregister",
680 arguments = { "string", "integer" },
681 protected = true,
682 actions = function(name,n)
683 ctx_latelua { action = enhanceregister, name = name, n = n }
684 end,
685}
686
687implement {
688 name = "extendregister",
689 actions = extendregister,
690 arguments = {
691 {
692 { "metadata", {
693 { "name" },
694 { "coding" },
695 { "catcodes", "integer" },
696 { "own" },
697 }
698 },
699 { "entries", {
700 { "entries", "list" },
701 { "keys", "list" },
702 { "processors", "list" },
703 { "entry" },
704 { "key" },
705 { "processor" },
706 }
707 },
708 { "references", {
709 { "label" },
710 }
711 },
712 { "userdata" },
713 }
714 }
715}
716
717implement {
718 name = "extendregisterlate",
719 arguments = "2 strings",
720 actions = function(name,label)
721 context.latelua(function()
722 extendregister {
723 metadata = { name = name },
724 references = { abel = label },
725 }
726 end)
727 end,
728}
729
730
731
732implement {
733 name = "storeregister",
734
735
736
737
738
739 actions = { storeregister, context },
740 arguments = {
741 {
742 { "metadata", {
743 { "kind" },
744 { "name" },
745 { "coding" },
746 { "level", "integer" },
747 { "catcodes", "integer" },
748 { "own" },
749 { "xmlroot" },
750 { "xmlsetup" },
751 }
752 },
753 { "entries", {
754 { "entries", "list" },
755 { "keys", "list" },
756 { "processors", "list" },
757 { "entry" },
758 { "key" },
759 { "processor" },
760 }
761 },
762 { "references", {
763 { "internal", "integer" },
764 { "section", "integer" },
765 { "view" },
766 { "label" },
767 }
768 },
769 { "seeword", {
770 { "text" },
771 }
772 },
773 { "processors", {
774 { "entry" },
775 { "key" },
776 { "page" },
777 }
778 },
779 { "userdata" },
780 }
781 }
782}
783
784
785
786local compare = sorters.comparers.basic
787
788function registers.compare(a,b)
789 local result = compare(a,b)
790 if result ~= 0 then
791 return result
792 else
793 local ka = a.metadata.kind
794 local kb = b.metadata.kind
795 if ka == kb then
796 local ra = a.references
797 local rb = b.references
798 local pa = ra.realpage
799 local pb = rb.realpage
800 if not pa or not pb then
801 return 0
802 elseif pa < pb then
803 return -1
804 elseif pa > pb then
805 return 1
806 else
807
808
809 local ia = ra.internal
810 local ib = rb.internal
811 if not ia or not ib then
812 return 0
813 elseif ia < ib then
814 return -1
815 elseif ia > ib then
816 return 1
817 else
818 return 0
819 end
820 end
821 elseif ka == "see" then
822 return 1
823 elseif kb == "see" then
824 return -1
825 end
826 end
827 return 0
828end
829
830function registers.filter(data,options)
831 data.result = registers.filtercollected(nil,options.criterium,options.number,data.entries,true)
832end
833
834local seeindex = 0
835
836
837
838
839
840local function crosslinkseewords(result,check)
841
842 local seewords = { }
843 for i=1,#result do
844 local data = result[i]
845 local seeword = data.seeword
846 if seeword then
847 local seetext = seeword.text
848 if seetext and not seewords[seetext] then
849 seeindex = seeindex + 1
850 seewords[seetext] = seeindex
851 if trace_registers then
852 report_registers("see word %03i: %s",seeindex,seetext)
853 end
854 end
855 end
856 end
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877 local entries = { }
878 local keywords = { }
879 local seeparents = { }
880 for i=1,#result do
881 local data = result[i]
882 local word = data.list
883 local size = #word
884 if data.seeword then
885
886 size = size - 1
887 end
888 for i=1,size do
889 local w = word[i]
890 local e = w[1]
891 local k = w[2] or e
892 entries [i] = e
893 keywords[i] = k
894 end
895
896 local okay, seeindex
897 for n=size,1,-1 do
898 okay = concat(keywords,"+",1,n)
899 seeindex = seewords[okay]
900
901 if seeindex then
902 break
903 end
904 okay = concat(entries,"+",1,n)
905 seeindex = seewords[okay]
906 if seeindex then
907 break
908 end
909 end
910 if seeindex then
911 seeparents[okay] = data
912 data.references.seeparent = seeindex
913 if trace_registers then
914 report_registers("see parent %03i: %s",seeindex,okay)
915 end
916 end
917 end
918
919
920 for i=1,#result do
921 local data = result[i]
922 local seeword = data.seeword
923 if seeword then
924 local text = seeword.text
925 if text then
926 local seeparent = seeparents[text]
927 if seeparent then
928 local seeindex = seewords[text]
929 data.references.seeindex = seeindex
930 if trace_registers then
931 report_registers("see crosslink %03i: %s",seeindex,text)
932 end
933 seeword.valid = true
934 elseif check then
935 report_registers("invalid crosslink : %s, %s",text,"ignored")
936 seeword.valid = false
937 else
938 report_registers("invalid crosslink : %s, %s",text,"passed")
939 seeword.valid = true
940 end
941 end
942 end
943 end
944end
945
946local function removeemptyentries(result)
947 local i, n, m = 1, #result, 0
948 while i <= n do
949 local entry = result[i]
950 if #entry.list == 0 or #entry.split == 0 then
951 remove(result,i)
952 n = n - 1
953 m = m + 1
954 else
955 i = i + 1
956 end
957 end
958 if m > 0 then
959 report_registers("%s empty entries removed in register",m)
960 end
961end
962
963function registers.prepare(data,options)
964
965 local strip = sorters.strip
966 local splitter = sorters.splitters.utf
967 local result = data.result
968 if result then
969 local seeprefix = char(0)
970 for i=1, #result do
971 local entry = result[i]
972 local split = { }
973 local list = entry.list
974 if list then
975 if entry.seeword then
976
977 list[#list+1] = { seeprefix .. strip(entry.seeword.text) }
978 end
979 for l=1,#list do
980 local ll = list[l]
981 local word = ll[1]
982 local key = ll[2]
983 if not key or key == "" then
984 key = word
985 end
986 split[l] = splitter(strip(key))
987 end
988 end
989 entry.split = split
990 end
991 removeemptyentries(result)
992 crosslinkseewords(result,options.check ~= v_no)
993 end
994end
995
996function registers.sort(data,options)
997
998
999
1000 sorters.sort(data.result,registers.compare)
1001
1002end
1003
1004function registers.unique(data,options)
1005 local result = { }
1006 local nofresult = 0
1007 local prev = nil
1008 local dataresult = data.result
1009 local bysection = options.pagemethod == v_section
1010 for k=1,#dataresult do
1011 local v = dataresult[k]
1012 if prev then
1013 local vr = v.references
1014 local pr = prev.references
1015 if not equal(prev.list,v.list) then
1016
1017 elseif bysection and vr.section == pr.section then
1018 v = nil
1019
1020 elseif pr.realpage ~= vr.realpage then
1021
1022 else
1023 local pl = pr.lastrealpage
1024 local vl = vr.lastrealpage
1025 if pl or vl then
1026 if not vl then
1027
1028 elseif not pl then
1029
1030 elseif pl ~= vl then
1031
1032 else
1033 v = nil
1034 end
1035 else
1036 v = nil
1037 end
1038 end
1039 end
1040 if v then
1041 nofresult = nofresult + 1
1042 result[nofresult] = v
1043 prev = v
1044 end
1045 end
1046 data.result = result
1047end
1048
1049function registers.finalize(data,options)
1050 local result = data.result
1051 data.metadata.nofsorted = #result
1052 local split = { }
1053 local nofsplit = 0
1054 local lasttag = nil
1055 local done = nil
1056 local nofdone = 0
1057 local firstofsplit = sorters.firstofsplit
1058 for k=1,#result do
1059 local v = result[k]
1060 local entry, tag = firstofsplit(v)
1061 if tag ~= lasttag then
1062 if trace_registers then
1063 report_registers("splitting at %a",tag)
1064 end
1065 done = { }
1066 nofdone = 0
1067 nofsplit = nofsplit + 1
1068 lasttag = tag
1069 split[nofsplit] = { tag = tag, data = done }
1070 end
1071 nofdone = nofdone + 1
1072 done[nofdone] = v
1073 end
1074 data.result = split
1075end
1076
1077local used = { }
1078
1079function registers.use(tag,filename,class,prefix)
1080 used[tag] = {
1081 class = class,
1082 filename = filename,
1083 data = job.loadother(filename),
1084 prefix = prefix or class,
1085 label = prefix or class,
1086 }
1087end
1088
1089implement {
1090 name = "useregister",
1091 arguments = "4 strings",
1092 actions = registers.use,
1093}
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125implement {
1126 name = "registerlabel",
1127 arguments = "string",
1128 actions = function(tag)
1129 local u = used[tag]
1130 if u then
1131 context(u.label)
1132 end
1133 end
1134}
1135
1136function registers.tweak(data,options)
1137
1138end
1139
1140local function analyzeregister(class,options)
1141 local data = rawget(collected,class)
1142 if not data then
1143 local list = utilities.parsers.settings_to_array(class)
1144 local entries = { }
1145 local nofentries = 0
1146 local multiple = false
1147 for i=1,#list do
1148 if used[list[i]] then
1149 multiple = true
1150 break
1151 end
1152 end
1153 for i=1,#list do
1154 local l = list[i]
1155 local u = used[l]
1156 local d = collected[l]
1157 if u then
1158 local collected = u.data.structures.registers.collected
1159 local class = u.class
1160 if collected and class then
1161 d = collected[class]
1162 end
1163 end
1164 if d then
1165 local e = d.entries
1166
1167local u = multiple and { string.formatters["%03i"](i) } or nil
1168 for i=1,#e do
1169
1170 local ei = e[i]
1171 local external = ei.external
1172if multiple and ei.metadata.kind == "see" and external then
1173
1174else
1175
1176 nofentries = nofentries + 1
1177 entries[nofentries] = ei
1178 if u then
1179 local eil = ei.list
1180 eil[#eil+1] = u
1181if external then
1182 ei.external = l
1183end
1184 end
1185end
1186 end
1187 if not metadata then
1188 metadata = d.metadata
1189 end
1190 end
1191 end
1192 data = {
1193 metadata = metadata or { },
1194 multiple = multiple,
1195 entries = entries,
1196 }
1197 collected[class] = data
1198 options.multiple = multiple
1199 end
1200 if data and data.entries then
1201 options = options or { }
1202 sorters.setlanguage(options.language,options.method,options.numberorder)
1203 registers.filter(data,options)
1204 registers.prepare(data,options)
1205 registers.sort(data,options)
1206 registers.unique(data,options)
1207 registers.tweak(data,options)
1208 registers.finalize(data,options)
1209 data.metadata.sorted = true
1210 return data.metadata.nofsorted or 0
1211 else
1212 return 0
1213 end
1214end
1215
1216registers.analyze = analyzeregister
1217
1218implement {
1219 name = "analyzeregister",
1220 actions = { analyzeregister, context },
1221 arguments = {
1222 "string",
1223 {
1224 { "language" },
1225 { "method" },
1226 { "numberorder" },
1227 { "compress" },
1228 { "criterium" },
1229 { "pagenumber", "boolean" },
1230 }
1231 }
1232}
1233
1234
1235
1236function registers.userdata(index,name)
1237 local data = references.internals[tonumber(index)]
1238 return data and data.userdata and data.userdata[name] or nil
1239end
1240
1241implement {
1242 name = "registeruserdata",
1243 actions = { registers.userdata, context },
1244 arguments = { "integer", "string" }
1245}
1246
1247
1248
1249local function pagerange(f_entry,t_entry,is_last,prefixspec,pagespec)
1250 local fer = f_entry.references
1251 local ter = t_entry.references
1252 ctx_registerpagerange(
1253 f_entry.metadata.name or "",
1254 f_entry.processors and f_entry.processors[2] or "",
1255 f_entry.external or fer.internal or 0,
1256 fer.realpage or 0,
1257 function()
1258 h_prefixpage(f_entry,prefixspec,pagespec)
1259 end,
1260 t_entry.external or ter.internal or 0,
1261 ter.lastrealpage or ter.realpage or 0,
1262 function()
1263 if is_last then
1264 h_prefixlastpage(t_entry,prefixspec,pagespec)
1265 else
1266 h_prefixpage (t_entry,prefixspec,pagespec)
1267 end
1268 end
1269 )
1270end
1271
1272local function pagenumber(entry,prefixspec,pagespec)
1273 local er = entry.references
1274 ctx_registeronepage(
1275 entry.metadata.name or "",
1276 entry.processors and entry.processors[2] or "",
1277 entry.external or er.internal or 0,
1278 er.realpage or 0,
1279 function() h_prefixpage(entry,prefixspec,pagespec) end
1280 )
1281end
1282
1283local function packed(f_entry,t_entry)
1284 local fer = f_entry.references
1285 local ter = t_entry.references
1286
1287 ctx_registerpacked(
1288 f_entry.external or fer.internal or 0,
1289 t_entry.external or ter.internal or 0
1290 )
1291end
1292
1293local function collapsedpage(pages)
1294 for i=2,#pages do
1295 local first = pages[i-1]
1296 local second = pages[i]
1297 local first_first = first[1]
1298 local first_last = first[2]
1299 local second_first = second[1]
1300 local second_last = second[2]
1301 local first_last_pn = first_last .references.realpage
1302 local second_first_pn = second_first.references.realpage
1303 local second_last_pn = second_last .references.realpage
1304 local first_last_last = first_last .references.lastrealpage
1305 local second_first_last = second_first.references.lastrealpage
1306 if first_last_last then
1307 first_last_pn = first_last_last
1308 if first_last_pn == second_first_pn then
1309
1310 pages[i-1] = { first_first, second_last }
1311 remove(pages,i)
1312 return true
1313 elseif second_first == second_last and second_first_pn <= first_last_pn then
1314
1315 remove(pages,i)
1316 return true
1317 elseif second_first == second_last and second_first_pn > first_last_pn then
1318
1319 pages[i-1] = { first_first, second_last }
1320 remove(pages,i)
1321 return true
1322 elseif second_last_pn < first_last_pn then
1323
1324 remove(pages,i)
1325 return true
1326 elseif first_last_pn < second_last_pn then
1327
1328 pages[i-1] = { first_first, second_last }
1329 remove(pages,i)
1330 return true
1331 elseif first_last_pn + 1 == second_first_pn and second_last_pn > first_last_pn then
1332
1333 pages[i-1] = { first_first, second_last }
1334 remove(pages,i)
1335 return true
1336 elseif second_first.references.lastrealpage then
1337
1338 pages[i-1] = { first_first, second_last }
1339 remove(pages,i)
1340 return true
1341 end
1342 elseif second_first_last then
1343 second_first_pn = second_first_last
1344 if first_last_pn == second_first_pn then
1345
1346 pages[i-1] = { first_first, second_last }
1347 remove(pages,i)
1348 return true
1349 end
1350 elseif first_last_pn == second_first_pn then
1351
1352 pages[i-1] = { first_last, second_last }
1353 remove(pages,i)
1354 return true
1355 end
1356 end
1357 return false
1358end
1359
1360local function collapsepages(pages)
1361 while collapsedpage(pages) do end
1362 return #pages
1363end
1364
1365
1366
1367function registers.flush(data,options,prefixspec,pagespec)
1368 local compress = options.compress
1369 local collapse_singles = compress == v_yes
1370 local collapse_ranges = compress == v_all
1371 local collapse_packed = compress == v_packed
1372 local show_page_number = options.pagenumber ~= false
1373 local bysection = options.pagemethod == v_section
1374 local result = data.result
1375 local maxlevel = 0
1376
1377 for i=1,#result do
1378 local data = result[i].data
1379 for d=1,#data do
1380 local m = #data[d].list
1381 if m > maxlevel then
1382 maxlevel = m
1383 end
1384 end
1385 end
1386 if maxlevel > absmaxlevel then
1387 maxlevel = absmaxlevel
1388 report_registers("limiting level to %a",maxlevel)
1389 end
1390
1391 ctx_startregisteroutput()
1392 local done = { }
1393 local started = false
1394 for i=1,#result do
1395
1396 local sublist = result[i]
1397
1398 for i=1,maxlevel do
1399 done[i] = false
1400 end
1401 local data = sublist.data
1402 local d = 0
1403 local n = 0
1404 ctx_startregistersection(sublist.tag)
1405 for d=1,#data do
1406 local entry = data[d]
1407 if entry.metadata.kind == "see" then
1408 local list = entry.list
1409 if #list > 1 then
1410 list[#list] = nil
1411 else
1412
1413
1414 end
1415 end
1416
1417
1418 if options.multiple or entry.external then
1419 local list = entry.list
1420 list[#list] = nil
1421 end
1422 end
1423
1424
1425
1426 while d < #data do
1427 d = d + 1
1428 local entry = data[d]
1429
1430 local metadata = entry.metadata
1431 local kind = metadata.kind
1432 local list = entry.list
1433 local e = { false, false, false }
1434 for i=3,maxlevel do
1435 e[i] = false
1436 end
1437 for i=1,maxlevel do
1438 local li = list[i]
1439 if list[i] then
1440 e[i] = li[1]
1441 end
1442 if e[i] == done[i] then
1443
1444 elseif not e[i] then
1445
1446
1447 done[i] = false
1448 for j=i+1,maxlevel do
1449 done[j] = false
1450 end
1451 elseif e[i] == "" then
1452 done[i] = false
1453 for j=i+1,maxlevel do
1454 done[j] = false
1455 end
1456 else
1457 done[i] = e[i]
1458 for j=i+1,maxlevel do
1459 done[j] = false
1460 end
1461 if started then
1462 ctx_stopregisterentry()
1463 started = false
1464 end
1465 if n == i then
1466
1467
1468 else
1469 while n > i do
1470 n = n - 1
1471 ctx_stopregisterentries()
1472 end
1473 while n < i do
1474 n = n + 1
1475 ctx_startregisterentries(n)
1476 end
1477 end
1478 local references = entry.references
1479 local processors = entry.processors
1480 local internal = references.internal or 0
1481 local seeparent = references.seeparent or ""
1482 local processor = (li and li[3]) or (processors and processors[1]) or ""
1483
1484
1485 ctx_startregisterentry(0)
1486 started = true
1487 if metadata then
1488 ctx_registerentry(metadata.name or "",processor,internal,seeparent,function() h_title(e[i],metadata) end)
1489 else
1490
1491 ctx_registerentry("",processor,internal,seeindex,e[i])
1492 end
1493 end
1494 end
1495
1496 local function case_1()
1497
1498
1499 local first, last, prev, pages, dd, nofpages = entry, nil, entry, { }, d, 0
1500 while dd < #data do
1501 dd = dd + 1
1502 local next = data[dd]
1503 if next and next.metadata.kind == "see" then
1504 dd = dd - 1
1505 break
1506 else
1507 local el, nl = entry.list, next.list
1508 if not equal(el,nl) then
1509 dd = dd - 1
1510
1511 break
1512 elseif next.references.lastrealpage then
1513 nofpages = nofpages + 1
1514 pages[nofpages] = first and { first, last or first } or { entry, entry }
1515 nofpages = nofpages + 1
1516 pages[nofpages] = { next, next }
1517 first, last, prev = nil, nil, nil
1518 elseif not first then
1519 first, prev = next, next
1520 elseif next.references.realpage - prev.references.realpage == 1 then
1521 last, prev = next, next
1522 else
1523 nofpages = nofpages + 1
1524 pages[nofpages] = { first, last or first }
1525 first, last, prev = next, nil, next
1526 end
1527 end
1528 end
1529 if first then
1530 nofpages = nofpages + 1
1531 pages[nofpages] = { first, last or first }
1532 end
1533 if collapse_ranges and nofpages > 1 then
1534 nofpages = collapsepages(pages)
1535 end
1536 if nofpages > 0 then
1537 d = dd
1538 for p=1,nofpages do
1539 local page = pages[p]
1540 local first = page[1]
1541 local last = page[2]
1542 if first == last then
1543 if first.references.lastrealpage then
1544 pagerange(first,first,true,prefixspec,pagespec)
1545 else
1546 pagenumber(first,prefixspec,pagespec)
1547 end
1548 elseif last.references.lastrealpage then
1549 pagerange(first,last,true,prefixspec,pagespec)
1550 else
1551 pagerange(first,last,false,prefixspec,pagespec)
1552 end
1553 end
1554 elseif entry.references.lastrealpage then
1555 pagerange(entry,entry,true,prefixspec,pagespec)
1556 else
1557 pagenumber(entry,prefixspec,pagespec)
1558 end
1559 end
1560
1561 local function case_2()
1562 local first = nil
1563 local last = nil
1564 while true do
1565 if not first then
1566 first = entry
1567 end
1568 last = entry
1569 if d == #data then
1570 break
1571 else
1572 d = d + 1
1573 local next = data[d]
1574 if next.metadata.kind == "see" or not equal(entry.list,next.list) then
1575 d = d - 1
1576 break
1577 else
1578 entry = next
1579 end
1580 end
1581 end
1582 packed(first,last)
1583 end
1584
1585 local function case_3()
1586 while true do
1587 if entry.references.lastrealpage then
1588 pagerange(entry,entry,true,prefixspec,pagespec)
1589 else
1590 pagenumber(entry,prefixspec,pagespec)
1591 end
1592 if d == #data then
1593 break
1594 else
1595 d = d + 1
1596 local next = data[d]
1597 if next.metadata.kind == "see" or not equal(entry.list,next.list) then
1598 d = d - 1
1599 break
1600 else
1601 entry = next
1602 end
1603 end
1604 end
1605 end
1606
1607 local function case_4()
1608 local t = { }
1609 local nt = 0
1610 while true do
1611 if entry.seeword and entry.seeword.valid then
1612 nt = nt + 1
1613 t[nt] = entry
1614 end
1615 if d == #data then
1616 break
1617 else
1618 d = d + 1
1619 local next = data[d]
1620 if next.metadata.kind ~= "see" or not equal(entry.list,next.list) then
1621 d = d - 1
1622 break
1623 else
1624 entry = next
1625 end
1626 end
1627 end
1628 for i=1,nt do
1629 local entry = t[i]
1630 local seeword = entry.seeword
1631 local seetext = seeword.text or ""
1632 local processor = seeword.processor or (entry.processors and entry.processors[1]) or ""
1633 local seeindex = entry.external or entry.references.seeindex or ""
1634 local seelist = lpegmatch(coding == "xml" and entrysplitter_xml or entrysplitter_tex,seetext)
1635 if #seelist > 1 then
1636 seetext = concat(seelist,", ")
1637 end
1638 ctx_registerseeword(
1639 metadata.name or "",
1640 i,
1641 nt,
1642 processor,
1643 0,
1644 seeindex,
1645 function() h_title(seetext,metadata) end
1646 )
1647 end
1648 end
1649
1650 local function case_5()
1651 local first = d
1652 while true do
1653 if d == #data then
1654 break
1655 else
1656 d = d + 1
1657 local next = data[d]
1658 if next.metadata.kind == "see" or not equal(entry.list,next.list) then
1659 d = d - 1
1660 break
1661 else
1662 entry = next
1663 end
1664 end
1665 end
1666 local last = d
1667 local n = last - first + 1
1668 local i = 0
1669 local name = metadata.name or ""
1670 local processor = entry.processors and entry.processors[1] or ""
1671 for e=first,last do
1672 local d = data[e]
1673 local sectionindex = d.references.internal or 0
1674 i = i + 1
1675 ctx_registersection(
1676 name,
1677 i,
1678 n,
1679 processor,
1680 0,
1681 sectionindex,
1682 function() h_prefix(d,prefixspec,true) end
1683 )
1684 end
1685 end
1686
1687 if kind == "entry" then
1688 if show_page_number then
1689 ctx_startregisterpages()
1690 if bysection then
1691 case_5()
1692 elseif collapse_singles or collapse_ranges then
1693 case_1()
1694 elseif collapse_packed then
1695 case_2()
1696 else
1697 case_3()
1698 end
1699 ctx_stopregisterpages()
1700 end
1701 elseif kind == "see" then
1702 ctx_startregisterseewords()
1703 case_4()
1704 ctx_stopregisterseewords()
1705 end
1706
1707 end
1708
1709 if started then
1710 ctx_stopregisterentry()
1711 started = false
1712 end
1713 while n > 0 do
1714 ctx_stopregisterentries()
1715 n = n - 1
1716 end
1717 ctx_stopregistersection()
1718 end
1719 ctx_stopregisteroutput()
1720
1721 data.result = nil
1722 data.metadata.sorted = false
1723
1724 local entries = data.entries
1725 for i=1,#entries do
1726 entries[i].split = nil
1727 end
1728
1729end
1730
1731function registers.process(class,...)
1732 if analyzeregister(class,...) > 0 then
1733 local data = collected[class]
1734 registers.flush(data,...)
1735 end
1736end
1737
1738implement {
1739 name = "processregister",
1740 actions = registers.process,
1741 arguments = {
1742 "string",
1743 {
1744 { "language" },
1745 { "method" },
1746 { "numberorder" },
1747 { "compress" },
1748 { "criterium" },
1749 { "check" },
1750 { "pagemethod" },
1751 { "pagenumber", "boolean" },
1752 },
1753 {
1754 { "separatorset" },
1755 { "conversionset" },
1756 { "starter" },
1757 { "stopper" },
1758 { "set" },
1759 { "segments" },
1760 { "connector" },
1761 },
1762 {
1763 { "prefix" },
1764 { "separatorset" },
1765 { "conversionset" },
1766 { "starter" },
1767 { "stopper" },
1768 { "segments" },
1769 }
1770 }
1771}
1772
1773
1774
1775function registers.findinternal(tag,where,n)
1776
1777 local current = collected[tag]
1778 if not current then
1779 return 0
1780 end
1781 local entries = current.entries
1782 if not entries then
1783 return 0
1784 end
1785 local entry = entries[n]
1786 if not entry then
1787 return 0
1788 end
1789 local list = entry.list
1790 local size = #list
1791
1792 local start, stop, step
1793 if where == v_previous then
1794 start = n - 1
1795 stop = 1
1796 step = -1
1797 elseif where == v_first then
1798 start = 1
1799 stop = #entries
1800 step = 1
1801 elseif where == v_last then
1802 start = #entries
1803 stop = 1
1804 step = -1
1805 else
1806 start = n + 1
1807 stop = #entries
1808 step = 1
1809 end
1810
1811 for i=start,stop,step do
1812 local r = entries[i]
1813 local l = r.list
1814 local s = #l
1815 if s == size then
1816 local ok = true
1817 for i=1,size do
1818 if list[i][1] ~= l[i][1] then
1819 ok = false
1820 break
1821 end
1822 end
1823 if ok then
1824 return r.references.internal or 0
1825 end
1826 end
1827 end
1828 return 0
1829end
1830
1831interfaces.implement {
1832 name = "findregisterinternal",
1833 arguments = { "string", "string", "integer" },
1834 actions = { registers.findinternal, context },
1835}
1836
1837
1838
1839function registers.integrate(utilitydata)
1840 local filename = utilitydata.comment.file
1841 if filename and filename ~= environment.jobname then
1842 local structures = utilitydata.structures
1843 if structures then
1844 local registers = structures.registers.collected or { }
1845 if registers then
1846 local sections = structures.sections.collected or { }
1847 local pages = structures.pages .collected or { }
1848 for class, register in next, registers do
1849 local entries = register.entries
1850 if entries then
1851 for i=1,#entries do
1852 local entry = entries[i]
1853 local references = entry.references
1854 if references then
1855 local section = references.section
1856 local realpage = references.realpage
1857
1858 references.sectiondata = section and sections[section]
1859 references.pagedata = realpage and pages[realpage]
1860
1861
1862
1863
1864 if references.x then
1865 references.x = nil
1866 end
1867 if references.y then
1868 references.y = nil
1869 end
1870 references.external = filename
1871 end
1872 entry.external = true
1873 end
1874 end
1875 end
1876 end
1877 end
1878 end
1879end
1880 |