diff options
author | Hans Hagen <pragma@wxs.nl> | 2013-05-20 02:00:00 +0200 |
---|---|---|
committer | Hans Hagen <pragma@wxs.nl> | 2013-05-20 02:00:00 +0200 |
commit | bd95a21d2b31a5fab1b4cc7c2b0334823fb3a3e9 (patch) | |
tree | 831128c411476f077eb7910d8c08f524d3ee43ec /tex/context/base/strc-lst.lua | |
parent | 9a10021cd4cb23995ad3ffa915fc5b7f6890aaf8 (diff) | |
download | context-bd95a21d2b31a5fab1b4cc7c2b0334823fb3a3e9.tar.gz |
beta 2013.05.20 02:00
Diffstat (limited to 'tex/context/base/strc-lst.lua')
-rw-r--r-- | tex/context/base/strc-lst.lua | 1690 |
1 files changed, 845 insertions, 845 deletions
diff --git a/tex/context/base/strc-lst.lua b/tex/context/base/strc-lst.lua index 2395abb62..ad7dc0f54 100644 --- a/tex/context/base/strc-lst.lua +++ b/tex/context/base/strc-lst.lua @@ -1,845 +1,845 @@ -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]
-
--- todo: tag entry in list is crap
---
--- move more to commands
-
-local format, gmatch, gsub = string.format, string.gmatch, string.gsub
-local tonumber = tonumber
-local texcount = tex.count
-local concat, insert, remove = table.concat, table.insert, table.remove
-local lpegmatch = lpeg.match
-local simple_hash_to_string, settings_to_hash = utilities.parsers.simple_hash_to_string, utilities.parsers.settings_to_hash
-local allocate, checked = utilities.storage.allocate, utilities.storage.checked
-
-local trace_lists = false trackers.register("structures.lists", function(v) trace_lists = v end)
-
-local report_lists = logs.reporter("structure","lists")
-
-local structures = structures
-local lists = structures.lists
-local sections = structures.sections
-local helpers = structures.helpers
-local documents = structures.documents
-local pages = structures.pages
-local tags = structures.tags
-local references = structures.references
-
-local collected = allocate()
-local tobesaved = allocate()
-local cached = allocate()
-local pushed = allocate()
-
-lists.collected = collected
-lists.tobesaved = tobesaved
-
-lists.enhancers = lists.enhancers or { }
-lists.internals = allocate(lists.internals or { }) -- to be checked
-lists.ordered = allocate(lists.ordered or { }) -- to be checked
-lists.cached = cached
-lists.pushed = pushed
-
-references.specials = references.specials or { }
-
-local variables = interfaces.variables
-local matchingtilldepth = sections.matchingtilldepth
-local numberatdepth = sections.numberatdepth
-
--- -- -- -- -- --
-
-local function zerostrippedconcat(t,separator) -- for the moment not public
- local f, l = 1, #t
- for i=f,l do
- if t[i] == 0 then
- f = f + 1
- end
- end
- for i=l,f,-1 do
- if t[i] == 0 then
- l = l - 1
- end
- end
- return concat(t,separator,f,l)
-end
-
--- -- -- -- -- --
-
-local function initializer()
- -- create a cross reference between internal references
- -- and list entries
- local collected = lists.collected
- local internals = checked(references.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
- if r then
- r.listindex = i -- handy to have
- end
- end
-end
-
-job.register('structures.lists.collected', tobesaved, initializer)
-
-local groupindices = table.setmetatableindex("table")
-
-function lists.groupindex(name,group)
- local groupindex = groupindices[name]
- return groupindex and groupindex[group] or 0
-end
-
-function lists.addto(t)
- local m = t.metadata
- local u = t.userdata
- if u and type(u) == "string" then
- t.userdata = helpers.touserdata(u) -- nicer at the tex end
- end
- local numberdata = t.numberdata
- local group = numberdata and numberdata.group
- if not group then
- -- forget about it
- elseif group == "" then
- group, numberdata.group = nil, nil
- else
- local groupindex = groupindices[m.name][group]
- if groupindex then
- numberdata.numbers = cached[groupindex].numberdata.numbers
- end
- end
- 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
- r.listindex = p
- end
- local setcomponent = references.setcomponent
- if setcomponent then
- setcomponent(t) -- might move to the tex end
- end
- if group then
- groupindices[m.name][group] = p
- end
- return p
-end
-
-function lists.discard(n)
- n = tonumber(n)
- if not n then
- -- maybe an error message
- elseif n == #cached then
- cached[n] = nil
- n = n -1
- while n > 0 and cached[n] == false do
- cached[n] = nil -- collect garbage
- n = n - 1
- end
- else
- cached[n] = false
- end
-end
-
-function lists.iscached(n)
- return 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
- local metadata = l.metadata
- local references = l.references
- --
- l.directives = nil -- might change
- -- save in the right order (happens at shipout)
- lists.tobesaved[#lists.tobesaved+1] = l
- -- default enhancer (cross referencing)
- references.realpage = texcount.realpageno
- -- tags
- local kind = metadata.kind
- local name = metadata.name
- if references then
- -- is this used ?
- local tag = tags.getid(kind,name)
- if tag and tag ~= "?" then
- references.tag = tag
- end
- --~ references.listindex = n
- end
- -- specific enhancer (kind of obsolete)
- local enhancer = kind and lists.enhancers[kind]
- if enhancer then
- enhancer(l)
- end
- return l
- 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 parent = lists.result[i]
- local name = parent.metadata.name
- local numberdata = parent and parent.numberdata
- local numbers = numberdata and numberdata.numbers
- local number = numbers and numbers[sections.getlevel(name)] or 0
- insert(nesting, { number = number, name = name, result = lists.result, parent = parent })
-end
-
-function lists.popnesting()
- local old = remove(nesting)
- lists.result = old.result
-end
-
--- will be split
-
--- Historically we had blocks but in the mkiv approach that could as well be a level
--- which would simplify things a bit.
-
-local splitter = lpeg.splitat(":")
-
--- this will become filtercollected(specification) and then we'll also have sectionblock as key
-
-local sorters = {
- [variables.command] = function(a,b)
- if a.metadata.kind == "command" or b.metadata.kind == "command" then
- return a.references.internal < b.references.internal
- else
- return a.references.order < b.references.order
- end
- end,
- [variables.all] = function(a,b)
- return a.references.internal < b.references.internal
- end,
-}
-
--- some day soon we will pass a table .. also split the function
-
-local function filtercollected(names, criterium, number, collected, forced, nested, sortorder) -- names is hash or string
- local numbers, depth = documents.data.numbers, documents.data.depth
- local result, nofresult, detail = { }, 0, nil
- local block = false -- all
- criterium = gsub(criterium or ""," ","") -- not needed
- -- new, will be applied stepwise
- local wantedblock, wantedcriterium = lpegmatch(splitter,criterium) -- block:criterium
- if wantedblock == "" or wantedblock == variables.all or wantedblock == variables.text then
- criterium = wantedcriterium ~= "" and wantedcriterium or criterium
- elseif not wantedcriterium then
- block = documents.data.block
- else
- block, criterium = wantedblock, wantedcriterium
- end
- if block == "" then
- block = false
- end
--- print(">>",block,criterium)
- --
- forced = forced or { } -- todo: also on other branched, for the moment only needed for bookmarks
- if type(names) == "string" then
- names = settings_to_hash(names)
- end
- local all = not next(names) or names[variables.all] or false
- if trace_lists then
- report_lists("filtering names %a, criterium %a, block %a, number %a",names,criterium,block or "*",number)
- 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
- nofresult = nofresult + 1
- result[nofresult] = v
- end
- end
- elseif all or criterium == variables.all or criterium == variables.text then
- for i=1,#collected do
- local v = collected[i]
- local r = v.references
- if r and (not block or not r.block or block == r.block) then
- local metadata = v.metadata
- if metadata then
- local name = metadata.name or false
- local sectionnumber = (r.section == 0) or sections.collected[r.section]
- if forced[name] or (sectionnumber and not metadata.nolist and (all or names[name])) then -- and not sectionnumber.hidenumber then
- nofresult = nofresult + 1
- result[nofresult] = v
- end
- end
- end
- end
- elseif criterium == variables.current then
- if depth == 0 then
- return filtercollected(names,variables.intro,number,collected,forced,false,sortorder)
- else
- for i=1,#collected do
- local v = collected[i]
- local r = v.references
- if r and (not block or not r.block or block == r.block) then
- local sectionnumber = sections.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 names[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
- nofresult = nofresult + 1
- result[nofresult] = 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 filtercollected(names,variables.intro,number,collected,forced,false,sortorder)
- else
- for i=1,#collected do
- local v = collected[i]
- local r = v.references
- if r then -- and (not block or not r.block or block == r.block) then
- local sectionnumber = sections.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 names[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
- nofresult = nofresult + 1
- result[nofresult] = v
- end
- end
- end
- end
- end
- end
- end
- elseif criterium == variables.previous then
- if depth == 0 then
- return filtercollected(names,variables.intro,number,collected,forced,false,sortorder)
- else
- for i=1,#collected do
- local v = collected[i]
- local r = v.references
- if r and (not block or not r.block or block == r.block) then
- local sectionnumber = sections.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 names[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
- nofresult = nofresult + 1
- result[nofresult] = v
- end
- end
- end
- end
- end
- end
- end
- elseif criterium == variables["local"] then -- not yet ok
- local nested = nesting[#nesting]
- if nested then
- return filtercollected(names,nested.name,nested.number,collected,forced,nested,sortorder)
- elseif sections.autodepth(documents.data.numbers) == 0 then
- return filtercollected(names,variables.all,number,collected,forced,false,sortorder)
- else
- return filtercollected(names,variables.current,number,collected,forced,false,sortorder)
- end
- elseif criterium == variables.component then
- -- special case, no structure yet
- local component = resolvers.jobs.currentcomponent() or ""
- if component ~= "" then
- for i=1,#collected do
- local v = collected[i]
- local r = v.references
- local m = v.metadata
- if r and r.component == component and (m and names[m.name] or all) then
- nofresult = nofresult + 1
- result[nofresult] = v
- end
- end
- end
- else -- sectionname, number
- -- not the same as register
- local depth = sections.getlevel(criterium)
- local number = tonumber(number) or numberatdepth(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 pnumbers = nil
- local pblock = block
- local parent = nested and nested.parent
- if parent then
- pnumbers = parent.numberdata.numbers or pnumbers -- so local as well as nested
- pblock = parent.references.block or pblock
- end
- for i=1,#collected do
- local v = collected[i]
- local r = v.references
- if r and (not block or not r.block or pblock == r.block) then
- local sectionnumber = sections.collected[r.section]
- if sectionnumber then
- local metadata = v.metadata
- local cnumbers = sectionnumber.numbers
- if cnumbers then
- if (all or names[metadata.name or false]) and #cnumbers >= depth and matchingtilldepth(depth,cnumbers,pnumbers) then
- nofresult = nofresult + 1
- result[nofresult] = v
- end
- end
- end
- end
- end
- end
- end
- if trace_lists then
- report_lists("criterium %a, block %a, found %a, detail %a",criterium,block or "*",#result,detail)
- end
-
- if sortorder then -- experiment
- local sorter = sorters[sortorder]
- if sorter then
- if trace_lists then
- report_lists("sorting list using method %a",sortorder)
- end
- for i=1,#result do
- result[i].references.order = i
- end
- table.sort(result,sorter)
- end
- end
-
- return result
-end
-
-lists.filtercollected = filtercollected
-
-function lists.filter(specification)
- return filtercollected(
- specification.names,
- specification.criterium,
- specification.number,
- lists.collected,
- specification.forced,
- false,
- specification.order
- )
-end
-
-lists.result = { }
-
-function lists.process(specification)
- lists.result = lists.filter(specification)
- local specials = utilities.parsers.settings_to_hash(specification.extras or "")
- specials = next(specials) and specials or nil
- for i=1,#lists.result do
- local r = lists.result[i]
- local m = r.metadata
- local s = specials and r.numberdata and specials[zerostrippedconcat(r.numberdata.numbers,".")] or ""
- context.strclistsentryprocess(m.name,m.kind,i,s)
- end
-end
-
-function lists.analyze(specification)
- lists.result = lists.filter(specification)
-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
- return str, metadata
- 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()
- return #lists.result
-end
-
-function lists.location(n)
- local l = lists.result[n]
- return l and l.references.internal or n
-end
-
-function lists.label(n,default)
- local l = lists.result[n]
- local t = l.titledata
- return t and t.label or default or ""
-end
-
-function lists.sectionnumber(name,n,spec)
- local data = lists.result[n]
- local sectiondata = sections.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.hastitledata(name,n,tag)
- local data = cached[tonumber(n)]
- if data then
- local titledata = data.titledata
- if titledata then
- return (titledata[tag] or titledata.title or "") == ""
- end
- end
- return false
-end
-
-function lists.haspagedata(name,n)
- local data = lists.result[n]
- if data then
- local references = data.references
- if references and references.realpage then -- or references.pagedata
- return true
- end
- end
- return false
-end
-
-function lists.hasnumberdata(name,n)
- local data = lists.result[n]
- if data then
- local numberdata = data.numberdata
- if numberdata and not numberdata.hidenumber then -- th ehide number is true
- return true
- end
- end
- return false
-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
- return references and references.realpage or 0
- else
- return 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
- 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 references.specials.order(var,actions) -- references.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 references.specials.page(var,actions)
- end
- end
-end
-
--- interface (maybe strclistpush etc)
-
-commands.pushlist = lists.pushnesting
-commands.poplist = lists.popnesting
-commands.enhancelist = lists.enhance
-commands.processlist = lists.process
-commands.analyzelist = lists.analyze
-commands.listtitle = lists.title
-commands.listprefixednumber = lists.prefixednumber
-commands.listprefixedpage = lists.prefixedpage
-
-
-function commands.addtolist (...) context(lists.addto (...)) end -- we could use variables instead of print
-function commands.listsize (...) context(lists.size (...)) end
-function commands.listlocation (...) context(lists.location (...)) end
-function commands.listlabel (...) context(lists.label (...)) end
-function commands.listrealpage (...) context(lists.realpage (...)) end
-function commands.listgroupindex(...) context(lists.groupindex(...)) end
-
-function commands.listuserdata(...)
- local str, metadata = lists.userdata(...)
- if str then
- -- local catcodes = metadata and metadata.catcodes
- -- if catcodes then
- -- context.sprint(catcodes,str)
- -- else
- -- context(str)
- -- end
- helpers.title(str,metadata)
- end
-end
-
--- we could also set variables .. names will change (when this module is done)
--- maybe strc_lists_savedtitle etc
-
-function commands.doiflisthastitleelse (...) commands.doifelse(lists.hastitledata (...)) end
-function commands.doiflisthaspageelse (...) commands.doifelse(lists.haspagedata (...)) end
-function commands.doiflisthasnumberelse(...) commands.doifelse(lists.hasnumberdata(...)) end
-function commands.doiflisthasentry (n) commands.doifelse(lists.iscached (n )) end
-
-function commands.savedlistnumber(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 commands.savedlisttitle(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)
- end
- end
-end
-
--- function commands.savedlistprefixednumber(name,n)
--- local data = cached[tonumber(n)]
--- if data then
--- local numberdata = data.numberdata
--- if numberdata then
--- helpers.prefix(data,data.prefixdata)
--- sections.typesetnumber(numberdata,"number",numberdata or false)
--- end
--- end
--- end
-
-if not lists.reordered then
- function lists.reordered(data)
- return data.numberdata
- end
-end
-
-function commands.savedlistprefixednumber(name,n)
- local data = cached[tonumber(n)]
- if data then
- local numberdata = lists.reordered(data)
- if numberdata then
- helpers.prefix(data,data.prefixdata)
- sections.typesetnumber(numberdata,"number",numberdata or false)
- end
- end
-end
-
-commands.discardfromlist = lists.discard
-
--- new and experimental and therefore off by default
-
-local sort, setmetatableindex = table.sort, table.setmetatableindex
-
-lists.autoreorder = false -- true
-
-local function addlevel(t,k)
- local v = { }
- setmetatableindex(v,function(t,k)
- local v = { }
- t[k] = v
- return v
- end)
- t[k] = v
- return v
-end
-
-local internals = setmetatableindex({ }, function(t,k)
-
- local sublists = setmetatableindex({ },addlevel)
-
- local collected = lists.collected or { }
-
- for i=1,#collected do
- local entry = collected[i]
- local numberdata = entry.numberdata
- if numberdata then
- local metadata = entry.metadata
- if metadata then
- local references = entry.references
- if references then
- local kind = metadata.kind
- local name = numberdata.counter or metadata.name
- local internal = references.internal
- if kind and name and internal then
- local sublist = sublists[kind][name]
- sublist[#sublist + 1] = { internal, numberdata }
- end
- end
- end
- end
- end
-
- for k, v in next, sublists do
- for k, v in next, v do
- local tmp = { }
- for i=1,#v do
- tmp[i] = v[i]
- end
- sort(v,function(a,b) return a[1] < b[1] end)
- for i=1,#v do
- t[v[i][1]] = tmp[i][2]
- end
- end
- end
-
- setmetatableindex(t,nil)
-
- return t[k]
-
-end)
-
-function lists.reordered(entry)
- local numberdata = entry.numberdata
- if lists.autoreorder then
- if numberdata then
- local metadata = entry.metadata
- if metadata then
- local references = entry.references
- if references then
- local kind = metadata.kind
- local name = numberdata.counter or metadata.name
- local internal = references.internal
- if kind and name and internal then
- return internals[internal] or numberdata
- end
- end
- end
- end
- else
- function lists.reordered(entry)
- return entry.numberdata
- end
- end
- return numberdata
-end
+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] + +-- todo: tag entry in list is crap +-- +-- move more to commands + +local format, gmatch, gsub = string.format, string.gmatch, string.gsub +local tonumber = tonumber +local texcount = tex.count +local concat, insert, remove = table.concat, table.insert, table.remove +local lpegmatch = lpeg.match +local simple_hash_to_string, settings_to_hash = utilities.parsers.simple_hash_to_string, utilities.parsers.settings_to_hash +local allocate, checked = utilities.storage.allocate, utilities.storage.checked + +local trace_lists = false trackers.register("structures.lists", function(v) trace_lists = v end) + +local report_lists = logs.reporter("structure","lists") + +local structures = structures +local lists = structures.lists +local sections = structures.sections +local helpers = structures.helpers +local documents = structures.documents +local pages = structures.pages +local tags = structures.tags +local references = structures.references + +local collected = allocate() +local tobesaved = allocate() +local cached = allocate() +local pushed = allocate() + +lists.collected = collected +lists.tobesaved = tobesaved + +lists.enhancers = lists.enhancers or { } +lists.internals = allocate(lists.internals or { }) -- to be checked +lists.ordered = allocate(lists.ordered or { }) -- to be checked +lists.cached = cached +lists.pushed = pushed + +references.specials = references.specials or { } + +local variables = interfaces.variables +local matchingtilldepth = sections.matchingtilldepth +local numberatdepth = sections.numberatdepth + +-- -- -- -- -- -- + +local function zerostrippedconcat(t,separator) -- for the moment not public + local f, l = 1, #t + for i=f,l do + if t[i] == 0 then + f = f + 1 + end + end + for i=l,f,-1 do + if t[i] == 0 then + l = l - 1 + end + end + return concat(t,separator,f,l) +end + +-- -- -- -- -- -- + +local function initializer() + -- create a cross reference between internal references + -- and list entries + local collected = lists.collected + local internals = checked(references.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 + if r then + r.listindex = i -- handy to have + end + end +end + +job.register('structures.lists.collected', tobesaved, initializer) + +local groupindices = table.setmetatableindex("table") + +function lists.groupindex(name,group) + local groupindex = groupindices[name] + return groupindex and groupindex[group] or 0 +end + +function lists.addto(t) + local m = t.metadata + local u = t.userdata + if u and type(u) == "string" then + t.userdata = helpers.touserdata(u) -- nicer at the tex end + end + local numberdata = t.numberdata + local group = numberdata and numberdata.group + if not group then + -- forget about it + elseif group == "" then + group, numberdata.group = nil, nil + else + local groupindex = groupindices[m.name][group] + if groupindex then + numberdata.numbers = cached[groupindex].numberdata.numbers + end + end + 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 + r.listindex = p + end + local setcomponent = references.setcomponent + if setcomponent then + setcomponent(t) -- might move to the tex end + end + if group then + groupindices[m.name][group] = p + end + return p +end + +function lists.discard(n) + n = tonumber(n) + if not n then + -- maybe an error message + elseif n == #cached then + cached[n] = nil + n = n -1 + while n > 0 and cached[n] == false do + cached[n] = nil -- collect garbage + n = n - 1 + end + else + cached[n] = false + end +end + +function lists.iscached(n) + return 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 + local metadata = l.metadata + local references = l.references + -- + l.directives = nil -- might change + -- save in the right order (happens at shipout) + lists.tobesaved[#lists.tobesaved+1] = l + -- default enhancer (cross referencing) + references.realpage = texcount.realpageno + -- tags + local kind = metadata.kind + local name = metadata.name + if references then + -- is this used ? + local tag = tags.getid(kind,name) + if tag and tag ~= "?" then + references.tag = tag + end + --~ references.listindex = n + end + -- specific enhancer (kind of obsolete) + local enhancer = kind and lists.enhancers[kind] + if enhancer then + enhancer(l) + end + return l + 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 parent = lists.result[i] + local name = parent.metadata.name + local numberdata = parent and parent.numberdata + local numbers = numberdata and numberdata.numbers + local number = numbers and numbers[sections.getlevel(name)] or 0 + insert(nesting, { number = number, name = name, result = lists.result, parent = parent }) +end + +function lists.popnesting() + local old = remove(nesting) + lists.result = old.result +end + +-- will be split + +-- Historically we had blocks but in the mkiv approach that could as well be a level +-- which would simplify things a bit. + +local splitter = lpeg.splitat(":") + +-- this will become filtercollected(specification) and then we'll also have sectionblock as key + +local sorters = { + [variables.command] = function(a,b) + if a.metadata.kind == "command" or b.metadata.kind == "command" then + return a.references.internal < b.references.internal + else + return a.references.order < b.references.order + end + end, + [variables.all] = function(a,b) + return a.references.internal < b.references.internal + end, +} + +-- some day soon we will pass a table .. also split the function + +local function filtercollected(names, criterium, number, collected, forced, nested, sortorder) -- names is hash or string + local numbers, depth = documents.data.numbers, documents.data.depth + local result, nofresult, detail = { }, 0, nil + local block = false -- all + criterium = gsub(criterium or ""," ","") -- not needed + -- new, will be applied stepwise + local wantedblock, wantedcriterium = lpegmatch(splitter,criterium) -- block:criterium + if wantedblock == "" or wantedblock == variables.all or wantedblock == variables.text then + criterium = wantedcriterium ~= "" and wantedcriterium or criterium + elseif not wantedcriterium then + block = documents.data.block + else + block, criterium = wantedblock, wantedcriterium + end + if block == "" then + block = false + end +-- print(">>",block,criterium) + -- + forced = forced or { } -- todo: also on other branched, for the moment only needed for bookmarks + if type(names) == "string" then + names = settings_to_hash(names) + end + local all = not next(names) or names[variables.all] or false + if trace_lists then + report_lists("filtering names %a, criterium %a, block %a, number %a",names,criterium,block or "*",number) + 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 + nofresult = nofresult + 1 + result[nofresult] = v + end + end + elseif all or criterium == variables.all or criterium == variables.text then + for i=1,#collected do + local v = collected[i] + local r = v.references + if r and (not block or not r.block or block == r.block) then + local metadata = v.metadata + if metadata then + local name = metadata.name or false + local sectionnumber = (r.section == 0) or sections.collected[r.section] + if forced[name] or (sectionnumber and not metadata.nolist and (all or names[name])) then -- and not sectionnumber.hidenumber then + nofresult = nofresult + 1 + result[nofresult] = v + end + end + end + end + elseif criterium == variables.current then + if depth == 0 then + return filtercollected(names,variables.intro,number,collected,forced,false,sortorder) + else + for i=1,#collected do + local v = collected[i] + local r = v.references + if r and (not block or not r.block or block == r.block) then + local sectionnumber = sections.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 names[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 + nofresult = nofresult + 1 + result[nofresult] = 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 filtercollected(names,variables.intro,number,collected,forced,false,sortorder) + else + for i=1,#collected do + local v = collected[i] + local r = v.references + if r then -- and (not block or not r.block or block == r.block) then + local sectionnumber = sections.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 names[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 + nofresult = nofresult + 1 + result[nofresult] = v + end + end + end + end + end + end + end + elseif criterium == variables.previous then + if depth == 0 then + return filtercollected(names,variables.intro,number,collected,forced,false,sortorder) + else + for i=1,#collected do + local v = collected[i] + local r = v.references + if r and (not block or not r.block or block == r.block) then + local sectionnumber = sections.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 names[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 + nofresult = nofresult + 1 + result[nofresult] = v + end + end + end + end + end + end + end + elseif criterium == variables["local"] then -- not yet ok + local nested = nesting[#nesting] + if nested then + return filtercollected(names,nested.name,nested.number,collected,forced,nested,sortorder) + elseif sections.autodepth(documents.data.numbers) == 0 then + return filtercollected(names,variables.all,number,collected,forced,false,sortorder) + else + return filtercollected(names,variables.current,number,collected,forced,false,sortorder) + end + elseif criterium == variables.component then + -- special case, no structure yet + local component = resolvers.jobs.currentcomponent() or "" + if component ~= "" then + for i=1,#collected do + local v = collected[i] + local r = v.references + local m = v.metadata + if r and r.component == component and (m and names[m.name] or all) then + nofresult = nofresult + 1 + result[nofresult] = v + end + end + end + else -- sectionname, number + -- not the same as register + local depth = sections.getlevel(criterium) + local number = tonumber(number) or numberatdepth(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 pnumbers = nil + local pblock = block + local parent = nested and nested.parent + if parent then + pnumbers = parent.numberdata.numbers or pnumbers -- so local as well as nested + pblock = parent.references.block or pblock + end + for i=1,#collected do + local v = collected[i] + local r = v.references + if r and (not block or not r.block or pblock == r.block) then + local sectionnumber = sections.collected[r.section] + if sectionnumber then + local metadata = v.metadata + local cnumbers = sectionnumber.numbers + if cnumbers then + if (all or names[metadata.name or false]) and #cnumbers >= depth and matchingtilldepth(depth,cnumbers,pnumbers) then + nofresult = nofresult + 1 + result[nofresult] = v + end + end + end + end + end + end + end + if trace_lists then + report_lists("criterium %a, block %a, found %a, detail %a",criterium,block or "*",#result,detail) + end + + if sortorder then -- experiment + local sorter = sorters[sortorder] + if sorter then + if trace_lists then + report_lists("sorting list using method %a",sortorder) + end + for i=1,#result do + result[i].references.order = i + end + table.sort(result,sorter) + end + end + + return result +end + +lists.filtercollected = filtercollected + +function lists.filter(specification) + return filtercollected( + specification.names, + specification.criterium, + specification.number, + lists.collected, + specification.forced, + false, + specification.order + ) +end + +lists.result = { } + +function lists.process(specification) + lists.result = lists.filter(specification) + local specials = utilities.parsers.settings_to_hash(specification.extras or "") + specials = next(specials) and specials or nil + for i=1,#lists.result do + local r = lists.result[i] + local m = r.metadata + local s = specials and r.numberdata and specials[zerostrippedconcat(r.numberdata.numbers,".")] or "" + context.strclistsentryprocess(m.name,m.kind,i,s) + end +end + +function lists.analyze(specification) + lists.result = lists.filter(specification) +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 + return str, metadata + 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() + return #lists.result +end + +function lists.location(n) + local l = lists.result[n] + return l and l.references.internal or n +end + +function lists.label(n,default) + local l = lists.result[n] + local t = l.titledata + return t and t.label or default or "" +end + +function lists.sectionnumber(name,n,spec) + local data = lists.result[n] + local sectiondata = sections.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.hastitledata(name,n,tag) + local data = cached[tonumber(n)] + if data then + local titledata = data.titledata + if titledata then + return (titledata[tag] or titledata.title or "") == "" + end + end + return false +end + +function lists.haspagedata(name,n) + local data = lists.result[n] + if data then + local references = data.references + if references and references.realpage then -- or references.pagedata + return true + end + end + return false +end + +function lists.hasnumberdata(name,n) + local data = lists.result[n] + if data then + local numberdata = data.numberdata + if numberdata and not numberdata.hidenumber then -- th ehide number is true + return true + end + end + return false +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 + return references and references.realpage or 0 + else + return 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 + 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 references.specials.order(var,actions) -- references.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 references.specials.page(var,actions) + end + end +end + +-- interface (maybe strclistpush etc) + +commands.pushlist = lists.pushnesting +commands.poplist = lists.popnesting +commands.enhancelist = lists.enhance +commands.processlist = lists.process +commands.analyzelist = lists.analyze +commands.listtitle = lists.title +commands.listprefixednumber = lists.prefixednumber +commands.listprefixedpage = lists.prefixedpage + + +function commands.addtolist (...) context(lists.addto (...)) end -- we could use variables instead of print +function commands.listsize (...) context(lists.size (...)) end +function commands.listlocation (...) context(lists.location (...)) end +function commands.listlabel (...) context(lists.label (...)) end +function commands.listrealpage (...) context(lists.realpage (...)) end +function commands.listgroupindex(...) context(lists.groupindex(...)) end + +function commands.listuserdata(...) + local str, metadata = lists.userdata(...) + if str then + -- local catcodes = metadata and metadata.catcodes + -- if catcodes then + -- context.sprint(catcodes,str) + -- else + -- context(str) + -- end + helpers.title(str,metadata) + end +end + +-- we could also set variables .. names will change (when this module is done) +-- maybe strc_lists_savedtitle etc + +function commands.doiflisthastitleelse (...) commands.doifelse(lists.hastitledata (...)) end +function commands.doiflisthaspageelse (...) commands.doifelse(lists.haspagedata (...)) end +function commands.doiflisthasnumberelse(...) commands.doifelse(lists.hasnumberdata(...)) end +function commands.doiflisthasentry (n) commands.doifelse(lists.iscached (n )) end + +function commands.savedlistnumber(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 commands.savedlisttitle(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) + end + end +end + +-- function commands.savedlistprefixednumber(name,n) +-- local data = cached[tonumber(n)] +-- if data then +-- local numberdata = data.numberdata +-- if numberdata then +-- helpers.prefix(data,data.prefixdata) +-- sections.typesetnumber(numberdata,"number",numberdata or false) +-- end +-- end +-- end + +if not lists.reordered then + function lists.reordered(data) + return data.numberdata + end +end + +function commands.savedlistprefixednumber(name,n) + local data = cached[tonumber(n)] + if data then + local numberdata = lists.reordered(data) + if numberdata then + helpers.prefix(data,data.prefixdata) + sections.typesetnumber(numberdata,"number",numberdata or false) + end + end +end + +commands.discardfromlist = lists.discard + +-- new and experimental and therefore off by default + +local sort, setmetatableindex = table.sort, table.setmetatableindex + +lists.autoreorder = false -- true + +local function addlevel(t,k) + local v = { } + setmetatableindex(v,function(t,k) + local v = { } + t[k] = v + return v + end) + t[k] = v + return v +end + +local internals = setmetatableindex({ }, function(t,k) + + local sublists = setmetatableindex({ },addlevel) + + local collected = lists.collected or { } + + for i=1,#collected do + local entry = collected[i] + local numberdata = entry.numberdata + if numberdata then + local metadata = entry.metadata + if metadata then + local references = entry.references + if references then + local kind = metadata.kind + local name = numberdata.counter or metadata.name + local internal = references.internal + if kind and name and internal then + local sublist = sublists[kind][name] + sublist[#sublist + 1] = { internal, numberdata } + end + end + end + end + end + + for k, v in next, sublists do + for k, v in next, v do + local tmp = { } + for i=1,#v do + tmp[i] = v[i] + end + sort(v,function(a,b) return a[1] < b[1] end) + for i=1,#v do + t[v[i][1]] = tmp[i][2] + end + end + end + + setmetatableindex(t,nil) + + return t[k] + +end) + +function lists.reordered(entry) + local numberdata = entry.numberdata + if lists.autoreorder then + if numberdata then + local metadata = entry.metadata + if metadata then + local references = entry.references + if references then + local kind = metadata.kind + local name = numberdata.counter or metadata.name + local internal = references.internal + if kind and name and internal then + return internals[internal] or numberdata + end + end + end + end + else + function lists.reordered(entry) + return entry.numberdata + end + end + return numberdata +end |