summaryrefslogtreecommitdiff
path: root/tex/context/base/strc-lst.lua
diff options
context:
space:
mode:
Diffstat (limited to 'tex/context/base/strc-lst.lua')
-rw-r--r--tex/context/base/strc-lst.lua486
1 files changed, 486 insertions, 0 deletions
diff --git a/tex/context/base/strc-lst.lua b/tex/context/base/strc-lst.lua
new file mode 100644
index 000000000..fefbe52ce
--- /dev/null
+++ b/tex/context/base/strc-lst.lua
@@ -0,0 +1,486 @@
+if not modules then modules = { } end modules ['strc-lst'] = {
+ version = 1.001,
+ comment = "companion to strc-lst.mkiv",
+ author = "Hans Hagen, PRAGMA-ADE, Hasselt NL",
+ copyright = "PRAGMA ADE / ConTeXt Development Team",
+ license = "see context related readme files"
+}
+
+-- when all datastructures are stable a packer will be added which will
+-- bring down memory consumption a bit; we can use for instance a pagenumber,
+-- section, metadata cache (internal then has to move up one level) or a
+-- shared cache [we can use a fast and stupid serializer]
+
+local format, gmatch, gsub = string.format, string.gmatch, string.gsub
+local tonumber = tonumber
+local texsprint, texprint, texwrite, texcount = tex.sprint, tex.print, tex.write, tex.count
+local concat, insert, remove = table.concat, table.insert, table.remove
+local lpegmatch = lpeg.match
+
+local trace_lists = false trackers.register("structure.lists", function(v) trace_lists = v end)
+
+local ctxcatcodes = tex.ctxcatcodes
+
+structure.lists = structure.lists or { }
+structure.sections = structure.sections or { }
+structure.helpers = structure.helpers or { }
+structure.documents = structure.documents or { }
+structure.pages = structure.pages or { }
+
+local lists = structure.lists
+local sections = structure.sections
+local helpers = structure.helpers
+local documents = structure.documents
+local pages = structure.pages
+
+lists.collected = lists.collected or { }
+lists.tobesaved = lists.tobesaved or { }
+lists.enhancers = lists.enhancers or { }
+lists.internals = lists.internals or { }
+lists.ordered = lists.ordered or { }
+
+local variables = interfaces.variables
+local matching_till_depth, number_at_depth = sections.matching_till_depth, sections.number_at_depth
+
+local function initializer()
+ -- create a cross reference between internal references
+ -- and list entries
+ local collected = lists.collected
+ local internals = jobreferences.internals
+ local ordered = lists.ordered
+ for i=1,#collected do
+ local c = collected[i]
+ local m = c.metadata
+ local r = c.references
+ if m then
+ -- access by internal reference
+ local internal = r and r.internal
+ if internal then
+ internals[internal] = c
+ end
+ -- access by order in list
+ local kind, name = m.kind, m.name
+ if kind and name then
+ local ok = ordered[kind]
+ if ok then
+ local on = ok[name]
+ if on then
+ on[#on+1] = c
+ else
+ ok[name] = { c }
+ end
+ else
+ ordered[kind] = { [name] = { c } }
+ end
+ end
+ end
+ end
+end
+
+if job then
+ job.register('structure.lists.collected', structure.lists.tobesaved, initializer)
+end
+
+local cached, pushed = { }, { }
+
+function lists.push(t)
+ local r = t.references
+ local i = (r and r.internal) or 0 -- brrr
+ local p = pushed[i]
+ if not p then
+ p = #cached + 1
+ cached[p] = helpers.simplify(t)
+ pushed[i] = p
+ end
+ texwrite(p)
+end
+
+function lists.doifstoredelse(n)
+ commands.doifelse(cached[tonumber(n)])
+end
+
+-- this is the main pagenumber enhancer
+
+function lists.enhance(n)
+ -- todo: symbolic names for counters
+ local l = cached[n]
+ if l then
+ --
+ l.directives = nil -- might change
+ -- save in the right order (happens at shipout)
+ lists.tobesaved[#lists.tobesaved+1] = l
+ -- default enhancer (cross referencing)
+ l.references.realpage = texcount.realpageno
+ -- specific enhancer (kind of obsolete)
+ local kind = l.metadata.kind
+ local enhancer = kind and lists.enhancers[kind]
+ if enhancer then
+ enhancer(l)
+ end
+ end
+end
+
+-- we can use level instead but we can also decide to remove level from the metadata
+
+local nesting = { }
+
+function lists.pushnesting(i)
+ local r = lists.result[i]
+ local name = r.metadata.name
+ local numberdata = r and r.numberdata
+ local n = (numberdata and numberdata.numbers[sections.getlevel(name)]) or 0
+ insert(nesting, { number = n, name = name, result = lists.result, parent = r })
+end
+
+function lists.popnesting()
+ local old = remove(nesting)
+ lists.result = old.result
+end
+
+-- will be split
+
+local function filter_collected(names, criterium, number, collected, nested)
+ local numbers, depth = documents.data.numbers, documents.data.depth
+ local hash, result, all, detail = { }, { }, not names or names == "" or names == variables.all, nil
+ names, criterium = gsub(names," ",""), gsub(criterium," ","")
+ if trace_lists then
+ logs.report("lists","filtering names: %s, criterium: %s, number: %s",names,criterium,number or "-")
+ end
+ if not all then
+ for s in gmatch(names,"[^, ]+") do -- sort of settings to hash
+ hash[s] = true
+ end
+ end
+ if criterium == variables.intro then
+ -- special case, no structure yet
+ for i=1,#collected do
+ local v = collected[i]
+ local r = v.references
+ if r and r.section == 0 then
+ result[#result+1] = v
+ end
+ end
+ elseif criterium == variables.all or criterium == variables.text then
+ for i=1,#collected do
+ local v = collected[i]
+ local r = v.references
+ if r then
+ local sectionnumber = (r.section == 0) or jobsections.collected[r.section]
+ if sectionnumber then -- and not sectionnumber.hidenumber then
+ local metadata = v.metadata
+ if metadata and not metadata.nolist and (all or hash[metadata.name or false]) then
+ result[#result+1] = v
+ end
+ end
+ end
+ end
+ elseif criterium == variables.current then
+ if depth == 0 then
+ return filter_collected(names,variables.intro,number,collected)
+ else
+ for i=1,#collected do
+ local v = collected[i]
+ local r = v.references
+ if r then
+ local sectionnumber = jobsections.collected[r.section]
+ if sectionnumber then -- and not sectionnumber.hidenumber then
+ local cnumbers = sectionnumber.numbers
+ local metadata = v.metadata
+ if cnumbers then
+ if metadata and not metadata.nolist and (all or hash[metadata.name or false]) and #cnumbers > depth then
+ local ok = true
+ for d=1,depth do
+ local cnd = cnumbers[d]
+ if not (cnd == 0 or cnd == numbers[d]) then
+ ok = false
+ break
+ end
+ end
+ if ok then
+ result[#result+1] = v
+ end
+ end
+ end
+ end
+ end
+ end
+ end
+ elseif criterium == variables.here then
+ -- this is quite dirty ... as cnumbers is not sparse we can misuse #cnumbers
+ if depth == 0 then
+ return filter_collected(names,variables.intro,number,collected)
+ else
+ for i=1,#collected do
+ local v = collected[i]
+ local r = v.references
+ if r then
+ local sectionnumber = jobsections.collected[r.section]
+ if sectionnumber then -- and not sectionnumber.hidenumber then
+ local cnumbers = sectionnumber.numbers
+ local metadata = v.metadata
+ if cnumbers then
+--~ print(#cnumbers, depth, table.concat(cnumbers))
+ if metadata and not metadata.nolist and (all or hash[metadata.name or false]) and #cnumbers >= depth then
+ local ok = true
+ for d=1,depth do
+ local cnd = cnumbers[d]
+ if not (cnd == 0 or cnd == numbers[d]) then
+ ok = false
+ break
+ end
+ end
+ if ok then
+ result[#result+1] = v
+ end
+ end
+ end
+ end
+ end
+ end
+ end
+ elseif criterium == variables.previous then
+ if depth == 0 then
+ return filter_collected(names,variables.intro,number,collected)
+ else
+ for i=1,#collected do
+ local v = collected[i]
+ local r = v.references
+ if r then
+ local sectionnumber = jobsections.collected[r.section]
+ if sectionnumber then -- and not sectionnumber.hidenumber then
+ local cnumbers = sectionnumber.numbers
+ local metadata = v.metadata
+ if cnumbers then
+ if metadata and not metadata.nolist and (all or hash[metadata.name or false]) and #cnumbers >= depth then
+ local ok = true
+ for d=1,depth-1 do
+ local cnd = cnumbers[d]
+ if not (cnd == 0 or cnd == numbers[d]) then
+ ok = false
+ break
+ end
+ end
+ if ok then
+ result[#result+1] = v
+ end
+ end
+ end
+ end
+ end
+ end
+ end
+ elseif criterium == variables["local"] then -- not yet ok
+ local nested = nesting[#nesting]
+ if nested then
+ return filter_collected(names,nested.name,nested.number,collected,nested)
+ elseif sections.autodepth(documents.data.numbers) == 0 then
+ return filter_collected(names,variables.all,number,collected)
+ else
+ return filter_collected(names,variables.current,number,collected)
+ end
+ else -- sectionname, number
+ -- not the same as register
+ local depth = sections.getlevel(criterium)
+ local number = tonumber(number) or number_at_depth(depth) or 0
+ if trace_lists then
+ local t = sections.numbers()
+ detail = format("depth: %s, number: %s, numbers: %s, startset: %s",depth,number,(#t>0 and concat(t,".",1,depth)) or "?",#collected)
+ end
+ if number > 0 then
+ local parent = nested and nested.parent and nested.parent.numberdata.numbers -- so local as well as nested
+ for i=1,#collected do
+ local v = collected[i]
+ local r = v.references
+ if r then
+ local sectionnumber = jobsections.collected[r.section]
+ if sectionnumber then
+ local metadata = v.metadata
+ local cnumbers = sectionnumber.numbers
+ if cnumbers then
+ if (all or hash[metadata.name or false]) and #cnumbers >= depth and matching_till_depth(depth,cnumbers,parent) then
+ result[#result+1] = v
+ end
+ end
+ end
+ end
+ end
+ end
+ end
+ if trace_lists then
+ if detail then
+ logs.report("lists","criterium: %s, %s, found: %s",criterium,detail,#result)
+ else
+ logs.report("lists","criterium: %s, found: %s",criterium,#result)
+ end
+ end
+ return result
+end
+
+lists.filter_collected = filter_collected
+
+function lists.filter(names, criterium, number)
+ return filter_collected(names, criterium, number, lists.collected)
+end
+
+lists.result = { }
+
+function lists.process(...)
+ lists.result = lists.filter(...)
+ for i=1,#lists.result do
+ local r = lists.result[i]
+ local m = r.metadata
+ texsprint(ctxcatcodes,format("\\processlistofstructure{%s}{%s}{%i}",m.name,m.kind,i))
+--~ context.processlistofstructure(m.name,m.kind,i)
+ end
+end
+
+function lists.analyze(...)
+ lists.result = lists.filter(...)
+end
+
+function lists.userdata(name,r,tag) -- to tex (todo: xml)
+ local result = lists.result[r]
+ if result then
+ local userdata, metadata = result.userdata, result.metadata
+ local str = userdata and userdata[tag]
+ if str then
+ texsprint(metadata and metadata.catcodes or ctxcatcodes,str)
+ end
+ end
+end
+
+function lists.uservalue(name,r,tag,default) -- to lua
+ local str = lists.result[r]
+ str = str and str.userdata
+ str = str and str[tag]
+ return str or default
+end
+
+function lists.size()
+ texprint(#lists.result)
+end
+
+function lists.location(n)
+ local l = lists.result[n]
+ texsprint(l.references.internal or n)
+end
+
+function lists.sectionnumber(name,n,spec)
+ local data = lists.result[n]
+ local sectiondata = jobsections.collected[data.references.section]
+ -- hm, prefixnumber?
+ sections.typesetnumber(sectiondata,"prefix",spec,sectiondata) -- data happens to contain the spec too
+end
+
+-- some basics (todo: helpers for pages)
+
+function lists.title(name,n,tag) -- tag becomes obsolete
+ local data = lists.result[n]
+ if data then
+ local titledata = data.titledata
+ if titledata then
+ helpers.title(titledata[tag] or titledata.list or titledata.title or "",data.metadata)
+ end
+ end
+end
+
+function lists.savedtitle(name,n,tag)
+ local data = cached[tonumber(n)]
+ if data then
+ local titledata = data.titledata
+ if titledata then
+ helpers.title(titledata[tag] or titledata.title or "",data.metadata)
+--~ texsprint(ctxcatcodes,titledata[tag] or titledata.title or "")
+ end
+ end
+end
+
+function lists.savednumber(name,n)
+ local data = cached[tonumber(n)]
+ if data then
+ local numberdata = data.numberdata
+ if numberdata then
+ sections.typesetnumber(numberdata,"number",numberdata or false)
+ end
+ end
+end
+
+function lists.savedprefixednumber(name,n)
+ local data = cached[tonumber(n)]
+ if data then
+ helpers.prefix(data,data.prefixdata)
+ local numberdata = data.numberdata
+ if numberdata then
+--~ print(name,n,table.serialize(numberdata))
+ sections.typesetnumber(numberdata,"number",numberdata or false)
+ end
+ end
+end
+
+function lists.prefix(name,n,spec)
+ helpers.prefix(lists.result[n],spec)
+end
+
+function lists.page(name,n,pagespec)
+ helpers.page(lists.result[n],pagespec)
+end
+
+function lists.prefixedpage(name,n,prefixspec,pagespec)
+ helpers.prefixpage(lists.result[n],prefixspec,pagespec)
+end
+
+function lists.realpage(name,n)
+ local data = lists.result[n]
+ if data then
+ local references = data.references
+ texsprint(references and references.realpage or 0)
+ else
+ texsprint(0)
+ end
+end
+
+-- numbers stored in entry.numberdata + entry.numberprefix
+
+function lists.number(name,n,spec)
+ local data = lists.result[n]
+ if data then
+ local numberdata = data.numberdata
+ if numberdata then
+ sections.typesetnumber(numberdata,"number",spec or false,numberdata or false)
+ end
+ end
+end
+
+function lists.prefixednumber(name,n,prefixspec,numberspec)
+ local data = lists.result[n]
+ if data then
+ helpers.prefix(data,prefixspec)
+ local numberdata = data.numberdata
+ if numberdata then
+--~ print(table.serialize(numberspec))
+ sections.typesetnumber(numberdata,"number",numberspec or false,numberdata or false)
+ end
+ end
+end
+
+-- todo, do this in references namespace ordered instead (this is an experiment)
+--
+-- also see lpdf-ano (maybe move this there)
+
+local splitter = lpeg.splitat(":")
+
+function jobreferences.specials.order(var,actions) -- jobreferences.specials !
+ local operation = var.operation
+ if operation then
+ local kind, name, n = lpegmatch(splitter,operation)
+ local order = lists.ordered[kind]
+ order = order and order[name]
+ local v = order[tonumber(n)]
+ local r = v and v.references.realpage
+ if r then
+ actions.realpage = r
+ var.operation = r -- brrr, but test anyway
+ return jobreferences.specials.page(var,actions)
+ end
+ end
+end