summaryrefslogtreecommitdiff
path: root/biblio/citation-style-language/citeproc-context.lua
diff options
context:
space:
mode:
Diffstat (limited to 'biblio/citation-style-language/citeproc-context.lua')
-rw-r--r--biblio/citation-style-language/citeproc-context.lua335
1 files changed, 335 insertions, 0 deletions
diff --git a/biblio/citation-style-language/citeproc-context.lua b/biblio/citation-style-language/citeproc-context.lua
new file mode 100644
index 0000000000..08be158bcf
--- /dev/null
+++ b/biblio/citation-style-language/citeproc-context.lua
@@ -0,0 +1,335 @@
+--
+-- Copyright (c) 2021-2022 Zeping Lee
+-- Released under the MIT license.
+-- Repository: https://github.com/zepinglee/citeproc-lua
+--
+
+local context = {}
+
+local LocalizedQuotes = require("citeproc-output").LocalizedQuotes
+
+local util = require("citeproc-util")
+
+
+local Context = {
+ reference = nil,
+ format = nil,
+ cite_id = nil,
+ style = nil,
+ locale = nil,
+ name_citation = nil,
+ names_delimiter = nil,
+
+ position = nil,
+
+ disamb_pass = nil,
+
+ cite = nil,
+ bib_number = nil,
+
+ in_bibliography = false,
+ sort_key = nil,
+
+ year_suffix = nil,
+}
+
+function Context:new()
+ local o = {
+ in_bibliography = false
+ }
+ setmetatable(o, self)
+ self.__index = self
+ return o
+end
+
+function Context:get_variable(name, form)
+ local variable_type = util.variable_types[name]
+ if variable_type == "number" then
+ return self:get_number(name)
+ -- elseif variable_type == "date" then
+ -- return self:get_date(name)
+ elseif variable_type == "name" then
+ return self:get_name(name)
+ else
+ return self:get_ordinary(name, form)
+ end
+end
+
+function Context:get_number(name)
+ if name == "locator" then
+ return self.cite.locator
+ elseif name == "citation-number" then
+ -- return self.bib_number
+ return self.reference["citation-number"]
+ elseif name == "first-reference-note-number" then
+ return self.cite["first-reference-note-number"]
+ elseif name == "page-first" then
+ return self.page_first(self.reference.page)
+ else
+ return self.reference[name]
+ end
+end
+
+function Context:get_ordinary(name, form)
+ local res = nil
+ local variable_name = name
+ if form and form ~= "long" then
+ variable_name = variable_name .. "-" .. form
+ end
+
+ if variable_name == "locator" or variable_name == "label" then
+ res = self.cite[variable_name]
+ else
+ res = self.reference[variable_name]
+ end
+ if res then
+ return res
+ end
+
+ if variable_name == "container-title-short" then
+ res = self.reference["journalAbbreviation"]
+ if res then
+ return res
+ end
+ end
+
+ if form then
+ res = self.reference[name]
+ if res then
+ return res
+ end
+ end
+
+ -- if name == "title-short" or name == "container-title-short" then
+ -- variable_name = string.gsub(name, "%-short$", "")
+ -- res = self.reference[variable_name]
+ -- end
+
+ return res
+end
+
+-- TODO: optimize: process only once
+-- TODO: organize the name parsing code
+function Context:get_name(variable_name)
+ local names = self.reference[variable_name]
+ if names then
+ for _, name in ipairs(names) do
+ if name.family == "" then
+ name.family = nil
+ end
+ if name.given == "" then
+ name.given = nil
+ end
+ if name.given and not name.family then
+ name.family = name.given
+ name.given = nil
+ end
+ self:parse_name_suffix(name)
+ self:split_ndp_family(name)
+ -- self:split_given_ndp(name)
+ self:split_given_dp(name)
+ end
+ end
+ return names
+end
+
+function Context:parse_name_suffix(name)
+ if not name.suffix and name.family and string.match(name.family, ",") then
+ local words = util.split(name.family, ",%s*")
+ name.suffix = words[#words]
+ name.family = table.concat(util.slice(words, 1, -2), ", ")
+ end
+ if not name.suffix and name.given and string.match(name.given, ",") then
+ -- Split name suffix: magic_NameSuffixNoComma.txt
+ -- "John, III" => given: "John", suffix: "III"
+ local words = util.split(name.given, ",%s*")
+ name.suffix = words[#words]
+ name.given = table.concat(util.slice(words, 1, -2), ", ")
+ end
+end
+
+function Context:split_ndp_family(name)
+ if name["non-dropping-particle"] or not name.family then
+ return
+ end
+ if util.startswith(name.family, '"') and util.endswith(name.family, '"') then
+ -- Stop parsing family name if surrounded by quotation marks
+ -- bugreports_parseName.txt
+ name.family = string.gsub(name.family, '"', "")
+ return
+ end
+ local ndp_parts = {}
+ local family_parts = {}
+ local parts = util.split(name.family)
+ for i, part in ipairs(parts) do
+ local ndp, family
+ -- d'Aubignac
+ ndp, family = string.match(part, "^(%l')(.+)$")
+ if ndp and family then
+ table.insert(ndp_parts, ndp)
+ parts[i] = family
+ else
+ ndp, family = string.match(part, "^(%l’)(.+)$")
+ if ndp and family then
+ table.insert(ndp_parts, ndp)
+ parts[i] = family
+ else
+ -- al-Aswānī
+ ndp, family = string.match(part, "^(%l+%-)(.+)$")
+ if ndp and family then
+ table.insert(ndp_parts, ndp)
+ parts[i] = family
+ elseif i < #parts and util.is_lower(part) then
+ table.insert(ndp_parts, part)
+ end
+ end
+ end
+ if ndp or i == #parts then
+ for j = i, #parts do
+ table.insert(family_parts, parts[j])
+ end
+ break
+ end
+ if not util.is_lower(part) then
+ for j = i, #parts do
+ table.insert(family_parts, parts[j])
+ end
+ break
+ end
+ end
+ if #ndp_parts > 0 then
+ name["non-dropping-particle"] = table.concat(ndp_parts, " ")
+ name.family = table.concat(family_parts, " ")
+ end
+end
+
+function Context:split_given_dp(name)
+ if name["dropping-particle"] or not name.given then
+ return
+ end
+ local dp_parts = {}
+ local given_parts = {}
+ local parts = util.split(name.given)
+ for i = #parts, 1, -1 do
+ local part = parts[i]
+ if i == 1 or not util.is_lower(part) then
+ for j = 1, i do
+ table.insert(given_parts, parts[j])
+ end
+ break
+ end
+ -- name_ParsedDroppingParticleWithApostrophe.txt
+ -- given: "François Hédelin d'" =>
+ -- given: "François Hédelin", dropping-particle: "d'"
+ if string.match(part, "^%l+'?$") or string.match(part, "^%l+’$") then
+ table.insert(dp_parts, 1, part)
+ end
+ end
+ if #dp_parts > 0 then
+ name["dropping-particle"] = table.concat(dp_parts, " ")
+ name.given = table.concat(given_parts, " ")
+ end
+end
+
+-- function Context:split_given_ndp(name)
+-- if name["non-dropping-particle"] or not name.given then
+-- return
+-- end
+
+-- if not (string.match(name.given, "%l'$") or string.match(name.given, "%l’$")) then
+-- return
+-- end
+
+-- local words = util.split(name.given)
+-- if #words < 2 then
+-- return
+-- end
+-- local last_word = words[#words]
+-- if util.endswith(last_word, "'") or util.endswith(last_word, util.unicode["apostrophe"]) then
+-- name["non-dropping-particle"] = last_word
+-- name.given = table.concat(util.slice(words, 1, -2), " ")
+-- end
+-- util.debug(name)
+-- end
+
+function Context:get_localized_date(form)
+ return self.locale.dates[form]
+end
+
+function Context:get_macro(name)
+ local res = self.style.macros[name]
+ if not res then
+ util.error(string.format('Undefined macro "%s"', name))
+ end
+ return res
+end
+
+function Context:get_simple_term(name, form, plural)
+ assert(self.locale)
+ return self.locale:get_simple_term(name, form, plural)
+end
+
+function Context:get_localized_quotes()
+ return LocalizedQuotes:new(
+ self:get_simple_term("open-quote"),
+ self:get_simple_term("close-quote"),
+ self:get_simple_term("open-inner-quote"),
+ self:get_simple_term("close-inner-quote"),
+ self.locale.style_options.punctuation_in_quote
+ )
+end
+
+function Context.page_first(page)
+ local page_first = util.split(page, "%s*[&,-]%s*")[1]
+ return util.split(page_first, util.unicode["en dash"])[1]
+end
+
+-- https://docs.citationstyles.org/en/stable/specification.html#non-english-items
+function Context:is_english()
+ local language = self:get_variable("language")
+ if util.startswith(self.engine.lang, "en") then
+ if not language or util.startswith(language, "en") then
+ return true
+ else
+ return false
+ end
+ else
+ if language and util.startswith(language, "en") then
+ return true
+ else
+ return false
+ end
+ end
+end
+
+
+local IrState = {}
+
+function IrState:new(style, cite_id, cite, reference)
+ local o = {
+ macro_stack = {},
+ suppressed = {},
+ }
+ setmetatable(o, self)
+ self.__index = self
+ return o
+end
+
+function IrState:push_macro(macro_name)
+ for _, name in ipairs(macro_name) do
+ if name == macro_name then
+ util.error(string.format('Recursive macro "%s".', macro_name))
+ end
+ table.insert(self.macro_stack, macro_name)
+ end
+end
+
+function IrState:pop_macro(macro_name)
+ table.remove(self.macro_stack)
+end
+
+
+context.Context = Context
+context.IrState = IrState
+
+return context