-- -- Copyright (c) 2021-2024 Zeping Lee -- Released under the MIT license. -- Repository: https://github.com/zepinglee/citeproc-lua -- local locale = {} local element local util if kpse then element = require("citeproc-element") util = require("citeproc-util") else element = require("citeproc.element") util = require("citeproc.util") end local Element = element.Element ---@class Locale: Element ---@field xml_lang string? ---@field terms Terms ---@field dates table ---@field style_options { limit_day_ordinals_to_day_1: boolean?, punctuation_in_quote: boolean } local Locale = Element:derive("locale") function Locale:new() local o = { xml_lang = nil, terms = {}, ordinal_terms = nil, dates = {}, style_options = { -- limit_day_ordinals_to_day_1 = false, -- punctuation_in_quote = false, }, } setmetatable(o, self) self.__index = self return o end function Locale:from_node(node) local o = Locale:new() o.xml_lang = node:get_attribute("xml:lang") o:process_children_nodes(node) for _, child in ipairs(o.children) do local element_name = child.element_name if element_name == "terms" then o.terms = child.terms_map o.ordinal_terms = child.ordinal_terms elseif element_name == "date" then o.dates[child.form] = child end end for _, child in ipairs(node:get_children()) do if child:is_element() and child:get_element_name() == "style-options" then local style_options = child o.style_options.limit_day_ordinals_to_day_1 = util.to_boolean( style_options:get_attribute("limit-day-ordinals-to-day-1") ) o.style_options.punctuation_in_quote = util.to_boolean( style_options:get_attribute("punctuation-in-quote") ) end end return o end function Locale:merge(other) for key, value in pairs(other.terms) do self.terms[key] = value end -- https://docs.citationstyles.org/en/stable/specification.html#ordinal-suffixes -- The “ordinal” term, and “ordinal-00” through “ordinal-99” terms, behave -- differently from other terms when it comes to Locale Fallback. -- Whereas other terms can be (re)defined individually, (re)defining any of -- the ordinal terms through cs:locale replaces all previously defined -- ordinal terms. if other.ordinal_terms then self.ordinal_terms = other.ordinal_terms end for key, value in pairs(other.dates) do self.dates[key] = value end for key, value in pairs(other.style_options) do if value ~= nil then self.style_options[key] = value end end return self end Locale.form_fallbacks = { ["verb-short"] = {"verb-short", "verb", "long"}, ["verb"] = {"verb", "long"}, ["symbol"] = {"symbol", "short", "long"}, ["short"] = {"short", "long"}, ["long"] = {"long"}, } ---Remove leading and trailing spaces ---See label_EditorTranslator1.txt, the `\n ` ---gives nothing. ---@param str string? ---@return string? local function strip_term_content(str) if not str then return nil end str = string.gsub(str, "^[\r\n]*", "") str = string.gsub(str, "[\r\n]*$", "") if string.match(str, "^%s*$") then str = "" end return str end -- Keep in sync with Terms:from_node function Locale:get_simple_term(name, form, plural) form = form or "long" for _, fallback_form in ipairs(self.form_fallbacks[form]) do local key = name if form ~= "long" then -- if not key then -- print(debug.traceback()) -- end key = key .. "/form-" .. fallback_form end local term = self.terms[key] if term then if plural then return strip_term_content(term.multiple) or strip_term_content(term.text) else return strip_term_content(term.single) or strip_term_content(term.text) end end end return nil end function Locale:get_ordinal_term(number, gender) -- TODO: match and gender local keys = {} if gender then if number < 100 then table.insert(keys, string.format("ordinal-%02d/gender-form-%s/match-whole-number", number, gender)) end table.insert(keys, string.format("ordinal-%02d/gender-form-%s/match-last-two-digits", number % 100, gender)) table.insert(keys, string.format("ordinal-%02d/gender-form-%s/match-last-digit", number % 10, gender)) end if number < 100 then table.insert(keys, string.format("ordinal-%02d/match-whole-number", number)) end table.insert(keys, string.format("ordinal-%02d/match-last-two-digits", number % 100)) table.insert(keys, string.format("ordinal-%02d/match-last-digit", number % 10)) table.insert(keys, "ordinal") for _, key in ipairs(keys) do local term = self.ordinal_terms[key] if term then return term.text end end return nil end function Locale:get_number_gender(name) local term = self.terms[name] if term and term.gender then return term.gender else return nil end end ---@class Terms: Element local Terms = Element:derive("terms") function Terms:new() local o = Element.new(self) o.element_name = "terms" o.children = {} o.terms_map = {} o.ordinal_terms = nil return o end function Terms:from_node(node) local o = Terms:new() o:process_children_nodes(node) for _, term in ipairs(o.children) do local form = term.form local gender = term.gender local gender_form = term.gender_form local match if util.startswith(term.name, "ordinal-0") then match = term.match or "last-digit" elseif util.startswith(term.name, "ordinal-") then match = term.match or "last-two-digits" end local key = term.name if form then key = key .. '/form-' .. form end -- if gender then -- key = key .. '/gender-' .. gender -- end if gender_form then key = key .. '/gender-form-' .. gender_form end if match then key = key .. '/match-' .. match end if term.name == "ordinal" or util.startswith(term.name, "ordinal-") then if not o.ordinal_terms then o.ordinal_terms = {} end o.ordinal_terms[key] = term else o.terms_map[key] = term end end return o end ---@class Term: Element local Term = Element:derive("term") function Term:from_node(node) local o = Term:new() o.name = node:get_attribute("name") o.form = node:get_attribute("form") o.match = node:get_attribute("match") o.gender = node:get_attribute("gender") o.gender_form = node:get_attribute("gender-form") o.text = node:get_text() for _, child in ipairs(node:get_children()) do if child:is_element() then local element_name = child:get_element_name() if element_name == "single" then o.single = child:get_text() o.text = o.single elseif element_name == "multiple" then o.multiple = child:get_text() end end end return o end locale.Locale = Locale locale.Term = Term return locale