diff options
Diffstat (limited to 'Master/texmf-dist/scripts/citation-style-language/citeproc-engine.lua')
-rw-r--r-- | Master/texmf-dist/scripts/citation-style-language/citeproc-engine.lua | 419 |
1 files changed, 419 insertions, 0 deletions
diff --git a/Master/texmf-dist/scripts/citation-style-language/citeproc-engine.lua b/Master/texmf-dist/scripts/citation-style-language/citeproc-engine.lua new file mode 100644 index 00000000000..ac8cc9c44e3 --- /dev/null +++ b/Master/texmf-dist/scripts/citation-style-language/citeproc-engine.lua @@ -0,0 +1,419 @@ +--[[ + Copyright (C) 2021 Zeping Lee +--]] + +local engine = {} + +local dom = require("luaxml-domobject") + +local richtext = require("citeproc-richtext") +local element = require("citeproc-element") +local nodes = require("citeproc-nodes") +local formats = require("citeproc-formats") +local util = require("citeproc-util") + + +local CiteProc = {} + +function CiteProc.new (sys, style, lang, force_lang) + if sys == nil then + error("\"citeprocSys\" required") + end + if sys.retrieveLocale == nil then + error("\"citeprocSys.retrieveLocale\" required") + end + if sys.retrieveItem == nil then + error("\"citeprocSys.retrieveItem\" required") + end + local o = {} + o.registry = { + citations = {}, -- A map + citation_strings = {}, -- A list + registry = {}, -- A map + reflist = {}, -- A list + previous_citation = nil, + requires_sorting = false, + } + + o.sys = sys + o.system_locales = {} + + if type(style) == "string" then + o.csl = dom.parse(style) + else + o.csl = style + end + o.csl:traverse_elements(CiteProc.set_base_class) + o.csl:root_node().engine = o + o.style = o.csl:get_path("style")[1] + o.style.lang = lang + o.csl:root_node().style = o.style + + o.style:set_lang(lang, force_lang) + + o.formatter = formats.latex + o.linking_enabled = false + + setmetatable(o, { __index = CiteProc }) + return o +end + +function CiteProc:updateItems (ids) + self.registry.reflist = {} + self.registry.registry = {} + for _, id in ipairs(ids) do + self:get_item(id) + end +end + +function CiteProc:updateUncitedItems(ids) + for _, id in ipairs(ids) do + if not self.registry.registry[id] then + self:get_item(id) + end + end + -- TODO: disambiguation +end + +function CiteProc:processCitationCluster(citation, citationsPre, citationsPost) + -- citation = { + -- citationID = "CITATION-3", + -- citationItems = { + -- { id = "ITEM-1" }, + -- { id = "ITEM-2" }, + -- }, + -- properties = { + -- noteIndex = 3, + -- }, + -- } + -- citationsPre = { + -- {"CITATION-1", 1}, + -- {"CITATION-2", 2}, + -- } + -- citationsPost = { + -- {"CITATION-4", 4}, + -- } + -- returns = { + -- { + -- bibchange = true, + -- citation_errors = {}, + -- }, + -- { + -- { 2, "[1,2]", "CITATION-3" } + -- } + -- } + self.registry.citations[citation.citationID] = citation + + local items = {} + + for _, cite_item in ipairs(citation.citationItems) do + cite_item.id = tostring(cite_item.id) + local position_first = (self.registry.registry[cite_item.id] == nil) + local item_data = self:get_item(cite_item.id) + + if item_data then + -- Create a wrapper of the orignal item from registry so that + -- it may hold different `locator` or `position` values for cites. + local item = setmetatable({}, {__index = function (_, key) + if cite_item[key] then + return cite_item[key] + else + return item_data[key] + end + end}) + + if not item.position and position_first then + item.position = util.position_map["first"] + end + + local first_reference_note_number = nil + for _, pre_citation in ipairs(citationsPre) do + pre_citation = self.registry.citations[pre_citation[1]] + for _, pre_cite_item in ipairs(pre_citation.citationItems) do + if pre_cite_item.id == cite_item.id then + first_reference_note_number = pre_citation.properties.noteIndex + end + break + end + if first_reference_note_number then + break + end + end + item["first-reference-note-number"] = first_reference_note_number + + table.insert(items, item) + end + end + + if #citationsPre > 0 then + local previous_citation_id = citationsPre[#citationsPre][1] + local previous_citation = self.registry.citations[previous_citation_id] + self.registry.previous_citation = previous_citation + end + + if self.registry.requires_sorting then + self:sort_bibliography() + end + + local params = { + bibchange = false, + citation_errors = {}, + } + + local citation_id_note_list = {} + for _, citation_id_note in ipairs(citationsPre) do + table.insert(citation_id_note_list, citation_id_note) + end + local note_index = 0 + if citation.properties and citation.properties.noteIndex then + note_index = citation.properties.noteIndex + end + table.insert(citation_id_note_list, {citation.citationID, note_index}) + for _, citation_id_note in ipairs(citationsPost) do + table.insert(citation_id_note_list, citation_id_note) + end + + local citation_id_cited = {} + for _, citation_id_note in ipairs(citation_id_note_list) do + citation_id_cited[citation_id_note[1]] = true + end + for citation_id, _ in pairs(self.registry.citations) do + if not citation_id_cited[citation_id] then + self.registry.citations[citation_id] = nil + self.registry.citation_strings[citation_id] = nil + end + end + + local output = {} + + for i, citation_id_note in ipairs(citation_id_note_list) do + local citation_id = citation_id_note[1] + -- local note_index = citation_id_note[2] + if citation_id == citation.citationID then + local context = { + build = {}, + engine = self, + } + local citation_str = self.style:render_citation(items, context) + + self.registry.citation_strings[citation_id] = citation_str + table.insert(output, {i - 1, citation_str, citation_id}) + else + -- TODO: correct note_index + -- TODO: update other citations after disambiguation + local citation_str = self.registry.citation_strings[citation_id] + if self.registry.citation_strings[citation_id] ~= citation_str then + params.bibchange = true + self.registry.citation_strings[citation_id] = citation_str + table.insert(output, {i - 1, citation_str, citation_id}) + end + end + end + + return {params, output} +end + +function CiteProc:makeCitationCluster (citation_items) + local items = {} + for _, cite_item in ipairs(citation_items) do + cite_item.id = tostring(cite_item.id) + local position_first = (self.registry.registry[cite_item.id] == nil) + local item_data = self:get_item(cite_item.id) + + -- Create a wrapper of the orignal item from registry so that + -- it may hold different `locator` or `position` values for cites. + local item = setmetatable({}, {__index = function (_, key) + if cite_item[key] then + return cite_item[key] + else + return item_data[key] + end + end}) + + if not item.position and position_first then + item.position = util.position_map["first"] + end + table.insert(items, item) + end + + if self.registry.requires_sorting then + self:sort_bibliography() + end + + local context = { + build = {}, + engine=self, + } + local res = self.style:render_citation(items, context) + self.registry.previous_citation = { + citationID = "pseudo-citation", + citationItems = items, + properties = { + noteIndex = 1, + } + } + return res +end + +function CiteProc:makeBibliography() + local items = {} + + if self.registry.requires_sorting then + self:sort_bibliography() + end + + for _, id in ipairs(self.registry.reflist) do + local item = self.registry.registry[id] + table.insert(items, item) + end + + local context = { + build = {}, + engine=self, + } + local res = self.style:render_biblography(items, context) + return res +end + +function CiteProc:set_formatter(format) + self.formatter = formats[format] +end + +function CiteProc:enable_linking() + self.linking_enabled = true +end + +function CiteProc:disable_linking() + self.linking_enabled = false +end + +function CiteProc.set_base_class (node) + if node:is_element() then + local name = node:get_element_name() + local element_class = nodes[name] + if element_class then + element_class:set_base_class(node) + else + element.Element:set_base_class(node) + end + end +end + +function CiteProc:get_style_class() + return self.style:get_attribute("class") or "in-text" +end + +function CiteProc:get_item (id) + local item = self.registry.registry[id] + if not item then + item = self:_retrieve_item(id) + if not item then + return nil + end + table.insert(self.registry.reflist, id) + item["citation-number"] = #self.registry.reflist + self.registry.registry[id] = item + self.registry.requires_sorting = true + end + local res = {} + setmetatable(res, {__index = item}) + return res +end + +function CiteProc:_retrieve_item (id) + -- Retrieve, copy, and normalize + local res = {} + local item = self.sys.retrieveItem(id) + if not item then + util.warning(string.format('Failed to retrieve item "%s"', id)) + return nil + end + + item.id = tostring(item.id) + + for key, value in pairs(item) do + if key == "title" then + value = self.normalize_string(value) + end + res[key] = value + end + + if res["page"] and not res["page-first"] then + local page_first = util.split(res["page"], "%s*[&,-]%s*")[1] + page_first = util.split(page_first, util.unicode["en dash"])[1] + res["page-first"] = page_first + end + + return res +end + +function CiteProc.normalize_string (str) + if not str or str == "" then + return str + end + -- French punctuation spacing + if type(str) == "string" then + str = string.gsub(str, " ;", util.unicode["narrow no-break space"] .. ";") + str = string.gsub(str, " %?", util.unicode["narrow no-break space"] .. "?") + str = string.gsub(str, " !", util.unicode["narrow no-break space"] .. "!") + str = string.gsub(str, " »", util.unicode["narrow no-break space"] .. "»") + str = string.gsub(str, "« ", "«" .. util.unicode["narrow no-break space"]) + end + -- local text = str + local text = richtext.new(str) + return text +end + +function CiteProc:sort_bibliography() + -- Sort the items in registry according to the `sort` in `bibliography.` + -- This will update the `citation-number` of each item. + local bibliography_sort = self.style:get_path("style bibliography sort")[1] + if not bibliography_sort then + return + end + local items = {} + for _, id in ipairs(self.registry.reflist) do + table.insert(items, self.registry.registry[id]) + end + + local context = { + engine = self, + style = self.style, + mode = "bibliography", + } + context = self.style:process_context(context) + context = self.style:get_path("style bibliography")[1]:process_context(context) + + bibliography_sort:sort(items, context) + self.registry.reflist = {} + for i, item in ipairs(items) do + item["citation-number"] = i + table.insert(self.registry.reflist, item.id) + end + self.registry.requires_sorting = false +end + +function CiteProc:get_system_locale (lang) + local locale = self.system_locales[lang] + if not locale then + locale = self.sys.retrieveLocale(lang) + if not locale then + util.warning(string.format("Failed to retrieve locale \"%s\"", lang)) + return nil + end + if type(locale) == "string" then + locale = dom.parse(locale) + end + locale:traverse_elements(self.set_base_class) + locale = locale:get_path("locale")[1] + locale:root_node().engine = self + locale:root_node().style = self.style + self.system_locales[lang] = locale + end + return locale +end + + +engine.CiteProc = CiteProc + +return engine |