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