publ-sor.lua /size: 11 Kb    last modification: 2023-12-21 09:44
1if not modules then modules = { } end modules ['publ-sor'] = {
2    version   = 1.001,
3    comment   = "this module part of publication support",
4    author    = "Hans Hagen, PRAGMA-ADE, Hasselt NL",
5    copyright = "PRAGMA ADE / ConTeXt Development Team",
6    license   = "see context related readme files"
7}
8
9-- if needed we can optimize this one: chekc if it's detail or something else
10-- and use direct access, but in practice it's fast enough
11
12local type          = type
13local concat        = table.concat
14local formatters    = string.formatters
15local compare       = sorters.comparers.basic -- (a,b)
16local sort          = table.sort
17
18local toarray       = utilities.parsers.settings_to_array
19local utfchar       = utf.char
20
21local publications  = publications
22local writers       = publications.writers
23
24local variables     = interfaces.variables
25local v_short       = variables.short
26local v_default     = variables.default
27local v_reference   = variables.reference
28local v_dataset     = variables.dataset
29local v_list        = variables.list
30local v_index       = variables.index
31local v_cite        = variables.cite
32local v_used        = variables.used
33
34local report        = logs.reporter("publications","sorters")
35
36local trace_sorters = false  trackers.register("publications.sorters",function(v) trace_sorters = v end)
37
38-- authors(s) | year | journal | title | pages
39
40local template = [[
41local type, tostring = type, tostring
42
43local writers  = publications.writers
44local datasets = publications.datasets
45local getter   = publications.getfaster -- (current,data,details,field,categories,types)
46local strip    = sorters.strip
47local splitter = sorters.splitters.utf
48
49local function newsplitter(splitter)
50    return table.setmetatableindex({},function(t,k) -- could be done in the sorter but seldom that many shared
51        local v = splitter(k,true)                  -- in other cases
52        t[k] = v
53        return v
54    end)
55end
56
57return function(dataset,list,method) -- indexer
58    local current       = datasets[dataset]
59    local luadata       = current.luadata
60    local details       = current.details
61    local specification = publications.currentspecification
62    local categories    = specification.categories
63    local types         = specification.types
64    local splitted      = newsplitter(splitter) -- saves mem
65    local snippets      = { } -- saves mem
66    local result        = { }
67
68%helpers%
69
70    for i=1,#list do
71        -- either { tag, tag, ... } or { { tag, index }, { tag, index } }
72        local li    = list[i]
73        local tag   = type(li) == "string" and li or li[1]
74        local index = tostring(i)
75        local entry = luadata[tag]
76        if entry then
77            -- maybe optional: if entry.key then push the keygetter
78            -- in slot 1 and ignore (e.g. author)
79            local detail  = details[tag]
80            result[i] = {
81                index  = i,
82                split  = {
83
84%getters%
85
86                },
87            }
88        else
89            result[i] = {
90                index  = i,
91                split  = {
92
93%unknowns%
94
95                },
96            }
97        end
98    end
99    return result
100end
101]]
102
103local f_getter = formatters["splitted[strip(getter(current,entry,detail,%q,categories,types) or %q)], -- %s"]
104local f_writer = formatters["splitted[strip(writer_%s(getter(current,entry,detail,%q,categories,types) or %q,snippets))], -- %s"]
105local f_helper = formatters["local writer_%s = writers[%q] -- %s: %s"]
106local f_value  = formatters["splitted[%q], -- %s"]
107local s_index  = "splitted[index], -- the order in the list, always added"
108
109-- there is no need to cache this in specification
110
111local sharedmethods      = { }
112publications.sortmethods = sharedmethods
113
114local function sortsequence(dataset,list,sorttype)
115
116    if not list or #list == 0 then
117        return
118    end
119
120    local specification = publications.currentspecification
121    local types         = specification.types
122    local sortmethods   = specification.sortmethods
123    local method        = sortmethods and sortmethods[sorttype] or sharedmethods[sorttype]
124    local sequence      = method and method.sequence
125
126    local s_default     = "<before end>"
127    local s_unknown     = "<at the end>"
128
129    local c_default     = utfchar(0xFFFE)
130    local c_unknown     = utfchar(0xFFFF)
131
132    if not sequence and type(sorttype) == "string" then
133        local list = toarray(sorttype)
134        if #list > 0 then
135            local indexdone = false
136            sequence = { }
137            for i=1,#list do
138                local entry   = toarray(list[i])
139                local field   = entry[1]
140                local default = entry[2]
141                local unknown = entry[3] or default
142                sequence[i] = {
143                    field   = field,
144                    default = default == s_default and c_default or default or c_default,
145                    unknown = unknown == s_unknown and c_unknown or unknown or c_unknown,
146                }
147                if field == "index" then
148                    indexdone = true
149                end
150            end
151            if not indexdone then
152                sequence[#sequence+1] = {
153                    field   = "index",
154                    default = 0,
155                    unknown = 0,
156                }
157            end
158        end
159        if trace_sorters then
160            report("creating sequence from method %a",sorttype)
161        end
162    end
163
164    if sequence then
165
166        local getters  = { }
167        local unknowns = { }
168        local helpers  = { }
169
170        if trace_sorters then
171            report("initializing method %a",sorttype)
172        end
173
174        for i=1,#sequence do
175            local step      = sequence[i]
176            local field     = step.field or "?"
177            local default   = step.default or c_default
178            local unknown   = step.unknown or c_unknown
179            local fldtype   = types[field]
180            local fldwriter = step.writer or fldtype
181            local writer    = fldwriter and writers[fldwriter]
182
183            if trace_sorters then
184                report("% 3i : field %a, type %a, default %a, unknown %a",i,field,fldtype,
185                    default == c_default and s_default or default,
186                    unknown == c_unknown and s_unknown or unknown
187                )
188            end
189
190            if writer then
191                local h = #helpers + 1
192                getters[i] = f_writer(h,field,default,field)
193                helpers[h] = f_helper(h,fldwriter,field,fldtype)
194            else
195                getters[i] = f_getter(field,default,field)
196            end
197            unknowns[i] = f_value(unknown,field)
198        end
199
200        unknowns[#unknowns+1] = s_index
201        getters [#getters +1] = s_index
202
203        local code = utilities.templates.replace(template, {
204            helpers  = concat(helpers, "\n"),
205            getters  = concat(getters, "\n"),
206            unknowns = concat(unknowns,"\n"),
207        })
208
209     -- print(code)
210
211        local action, error = loadstring(code)
212        if type(action) == "function" then
213            action = action()
214        else
215            report("error when compiling sort method %a: %s",sorttype,error or "unknown")
216        end
217        if type(action) == "function" then
218            local valid = action(dataset,list,method)
219            if valid and #valid > 0 then
220-- sorters.setlanguage(options.language,options.method)
221                sorters.sort(valid,compare)
222                return valid
223            else
224                report("error when applying sort method %a",sorttype)
225            end
226        else
227            report("error in sort method %a",sorttype)
228        end
229    else
230        report("invalid sort method %a",sorttype)
231    end
232
233end
234
235-- tag | listindex | reference | userdata | dataindex
236
237-- short      : short + tag index
238-- dataset    : index + tag
239-- list       : list + index
240-- reference  : tag + index
241-- used       : reference + dataset
242-- authoryear : complex sort
243
244local sorters = { }
245
246sorters[v_short] = function(dataset,rendering,list) -- should we store it
247    local shorts = rendering.shorts
248    local function compare(a,b)
249        if a and b then
250            local taga = a[1]
251            local tagb = b[1]
252            if taga and tagb then
253                local shorta = shorts[taga]
254                local shortb = shorts[tagb]
255                if shorta and shortb then
256                    -- assumes ascii shorts ... no utf yet
257                    return shorta < shortb
258                end
259                -- fall back on tag order
260                return taga < tagb
261            end
262            -- fall back on dataset order
263            local indexa = a[5]
264            local indexb = b[5]
265            if indexa and indexb then
266                return indexa < indexb
267            end
268        end
269        return false
270    end
271    sort(list,compare)
272end
273
274sorters[v_dataset] = function(dataset,rendering,list) -- dataset index
275    local function compare(a,b)
276        if a and b then
277            local indexa = a[5]
278            local indexb = b[5]
279            if indexa and indexb then
280                return indexa < indexb
281            end
282            local taga = a[1]
283            local tagb = b[1]
284            if taga and tagb then
285                return taga < tagb
286            end
287        end
288        return false
289    end
290    sort(list,compare)
291end
292
293sorters[v_list] = function(dataset,rendering,list) -- list index (normally redundant)
294    local function compare(a,b)
295        if a and b then
296            local lista = a[2]
297            local listb = b[2]
298            if lista and listb then
299                return lista < listb
300            end
301            local indexa = a[5]
302            local indexb = b[5]
303            if indexa and indexb then
304                return indexa < indexb
305            end
306        end
307        return false
308    end
309    sort(list,compare)
310end
311
312sorters[v_reference] = function(dataset,rendering,list) -- tag
313    local function compare(a,b)
314        if a and b then
315            local taga = a[1]
316            local tagb = b[1]
317            if taga and tagb then
318                return taga < tagb
319            end
320            local indexa = a[5]
321            local indexb = b[5]
322            if indexa and indexb then
323                return indexa < indexb
324            end
325        end
326        return false
327    end
328    sort(list,compare)
329end
330
331sorters[v_used] = function(dataset,rendering,list) -- tag
332    local function compare(a,b)
333        if a and b then
334            local referencea = a[2]
335            local referenceb = b[2]
336            if referencea and referenceb then
337                return referencea < referenceb
338            end
339            local indexa = a[5]
340            local indexb = b[5]
341            if indexa and indexb then
342                return indexa < indexb
343            end
344        end
345        return false
346    end
347    sort(list,compare)
348end
349
350sorters[v_default] = sorters[v_list]
351sorters[""]        = sorters[v_list]
352sorters[v_cite]    = sorters[v_list]
353sorters[v_index]   = sorters[v_dataset]
354
355local function anything(dataset,rendering,list,sorttype)
356    local valid = sortsequence(dataset,list,sorttype) -- field order
357    if valid and #valid > 0 then
358        -- hm, we have a complication here because a sortsequence doesn't know if there's a field
359        -- so there is no real catch possible here .., anyway, we add a index as last entry when no
360        -- one is set so that should be good enough (needs testing)
361        for i=1,#valid do
362            local v = valid[i]
363            valid[i] = list[v.index]
364        end
365        return valid
366    end
367end
368
369table.setmetatableindex(sorters,function(t,k) return anything end)
370
371publications.lists.sorters = sorters
372
373publications.sortmethods.key = {
374    sequence = {
375        { field = "key",   default = "", unknown = "" },
376        { field = "index", default = 0, unknown = 0 },
377    },
378}
379
380publications.sortmethods.index = {
381    sequence = {
382        { field = "index", default = 0, unknown = 0 },
383    },
384}
385
386publications.sortmethods.dataset = publications.sortmethods.index
387