diff options
Diffstat (limited to 'biblio/citation-style-language/citeproc-util.lua')
-rw-r--r-- | biblio/citation-style-language/citeproc-util.lua | 309 |
1 files changed, 263 insertions, 46 deletions
diff --git a/biblio/citation-style-language/citeproc-util.lua b/biblio/citation-style-language/citeproc-util.lua index c9adb6558f..38de3b5751 100644 --- a/biblio/citation-style-language/citeproc-util.lua +++ b/biblio/citation-style-language/citeproc-util.lua @@ -6,11 +6,55 @@ -- load `slnunicode` from LuaTeX local unicode = require("unicode") -local inspect = require("inspect") +local inspect -- only load it when debugging local util = {} +function util.clone(obj) + if type(obj) == "table" then + local res = {} + for key, value in pairs(obj) do + res[key] = value + end + return setmetatable(res, getmetatable(obj)) + else + return obj + end +end + +function util.join(list, delimiter) + local res = {} + for i, item in ipairs(list) do + if i > 1 then + table.insert(res, delimiter) + end + table.insert(res, item) + end + return res +end + +function util.to_boolean(str) + if not str then + return false + end + if str == "true" then + return true + elseif str == "false" then + return false + else + util.warning(string.format('Invalid boolean string "%s"', str)) + return false + end +end + +function util.to_list(str) + if not str then + return nil + end + return util.split(str) +end + function util.to_ordinal (n) assert(type(n) == "number") local last_digit = n % 10 @@ -44,21 +88,60 @@ function util.warning(message) end end -local function remove_all_metatables(item, path) - if path[#path] ~= inspect.METATABLE then return item end -end +local remove_all_metatables = nil -function util.debug(...) - -- io.stderr:write(inspect(..., {process = remove_all_metatables})) - io.stderr:write(inspect(...)) +function util.debug(obj) + if not inspect then + inspect = require("inspect") + remove_all_metatables = function (item, path) + if path[#path] ~= inspect.METATABLE then + return item + end + end + end + io.stderr:write("[") + io.stderr:write(debug.getinfo(2, "S").source:sub(2)) + io.stderr:write(":") + io.stderr:write(debug.getinfo(2, "l").currentline) + io.stderr:write("] ") + io.stderr:write(inspect(obj, {process = remove_all_metatables})) io.stderr:write("\n") end -- Similar to re.split() in Python -function util.split(str, seps, maxsplit, include_sep) +function util.split(str, sep, maxsplit) if not str then - error("Invalid string.") + util.error("Invalid string.") + end + sep = sep or "%s+" + if sep == "" then + util.error("Empty separator") end + if string.find(str, sep) == nil then + return { str } + end + + if maxsplit == nil or maxsplit < 0 then + maxsplit = -1 -- No limit + end + local result = {} + local pattern = "(.-)" .. sep .. "()" + local num_splits = 0 + local lastPos = 1 + for part, pos in string.gmatch(str, pattern) do + if num_splits == maxsplit then + break + end + num_splits = num_splits + 1 + result[num_splits] = part + lastPos = pos + end + -- Handle the last field + result[num_splits + 1] = string.sub(str, lastPos) + return result +end + +function util.split_multiple(str, seps, include_sep) seps = seps or "%s+" if seps == "" then error("Empty separator") @@ -97,6 +180,15 @@ function util.split(str, seps, maxsplit, include_sep) return res end +-- TODO: [Unicode word boundaries](https://www.unicode.org/reports/tr29/#Word_Boundaries) +-- Returns: { +-- {word, boundary}, +-- {word, boundary}, +-- } +function util.segment_words(str) + return util.split_multiple(str, util.word_boundaries, true) +end + function util.slice (t, start, stop) start = start or 1 stop = stop or #t @@ -132,6 +224,31 @@ function util.concat (list, sep) return res end +-- Python list.extend() +function util.extend(first, second) + if not second then + print(debug.traceback()) + end + local l = #first + for i, element in ipairs(second) do + first[l + i] = element + end + return first +end + +-- Concat two lists in place +function util.concat_list(first, second) + local res + for i, element in ipairs(first) do + res[i] = element + end + local i = #res + for j, element in ipairs(second) do + res[i + j] = element + end + return res +end + function util.lstrip (str) if not str then return nil @@ -153,22 +270,34 @@ function util.strip (str) end function util.startswith (str, prefix) + if not str then + return false + end return string.sub(str, 1, #prefix) == prefix end function util.endswith (str, suffix) + if not str then + return false + end return string.sub(str, -#suffix) == suffix end +function util.is_punct(str) + return string.match(str, "^%p$") +end + function util.is_numeric (str) if str == nil or str == "" then return false end local res = true for w in string.gmatch(str, "%w+") do - if string.match(w, "^[a-zA-Z]*%d+[a-zA-Z]*$") == nil then - res = false - break + if not string.match(w, "^%a*%d+%a*$") and + not string.match(w, "^[MDCLXVI]+$") and + not string.match(w, "^[mdclxvi]+$") then + -- Roman number withou validation + return false end end for w in string.gmatch(str, "%W+") do @@ -180,19 +309,84 @@ function util.is_numeric (str) return res end -function util.is_uncertain_date (variable) - if variable == nil then - return false - end - local value = variable["circa"] - return value ~= nil and value ~= "" -end - util.variable_types = {} -- schema/schemas/styles/csl-variables.rnc util.variables = {} +-- -- Standard variables +-- util.variables.standard = { +-- "abstract", +-- "annote", +-- "archive", +-- "archive_collection", +-- "archive_location", +-- "archive-place", +-- "authority", +-- "call-number", +-- "citation-key", +-- "citation-label", +-- "collection-title", +-- "container-title", +-- "container-title-short", +-- "dimensions", +-- "division", +-- "DOI", +-- "event", +-- "event-title", +-- "event-place", +-- "genre", +-- "ISBN", +-- "ISSN", +-- "jurisdiction", +-- "keyword", +-- "language", +-- "license", +-- "medium", +-- "note", +-- "original-publisher", +-- "original-publisher-place", +-- "original-title", +-- "part-title", +-- "PMCID", +-- "PMID", +-- "publisher", +-- "publisher-place", +-- "references", +-- "reviewed-genre", +-- "reviewed-title", +-- "scale", +-- "source", +-- "status", +-- "title", +-- "title-short", +-- "URL", +-- "volume-title", +-- "year-suffix", +-- } + +-- Number variables +util.variables.number = { + "chapter-number", + "citation-number", + "collection-number", + "edition", + "first-reference-note-number", + "issue", + "locator", + "number", + "number-of-pages", + "number-of-volumes", + "page", + "page-first", + "part-number", + "printing-number", + "section", + "supplement-number", + "version", + "volume", +} + -- Date variables util.variables.date = { "accessed", @@ -234,28 +428,6 @@ util.variables.name = { "translator", } --- Number variables -util.variables.number = { - "chapter-number", - "citation-number", - "collection-number", - "edition", - "first-reference-note-number", - "issue", - "locator", - "number", - "number-of-pages", - "number-of-volumes", - "page", - "page-first", - "part-number", - "printing-number", - "section", - "supplement-number", - "version", - "volume", -} - util.variable_types = {} for type, variables in pairs(util.variables) do @@ -327,10 +499,22 @@ util.unicode = { ["apostrophe"] = "\u{2019}", ["left double quotation mark"] = "\u{201C}", ["right double quotation mark"] = "\u{201D}", + ["left-pointing double angle quotation mark"] = "\u{00AB}", + ["right-pointing double angle quotation mark"] = "\u{00BB}", ["horizontal ellipsis"] = "\u{2026}", ["narrow no-break space"] = "\u{202F}", } +util.word_boundaries = { + ":", + " ", + "%-", + "/", + util.unicode["no-break space"], + util.unicode["en dash"], + util.unicode["em dash"], +} + -- Text-case @@ -342,9 +526,11 @@ function util.is_upper (str) return unicode.utf8.upper(str) == str end -function util.capitalize (str) - str = unicode.utf8.lower(str) - local res = string.gsub(str, "%w", unicode.utf8.upper, 1) +function util.capitalize(str) + -- if not str then + -- print(debug.traceback()) + -- end + local res = string.gsub(str, utf8.charpattern, unicode.utf8.upper, 1) return res end @@ -635,7 +821,7 @@ function util.convert_roman (number) local output = {} for _, tuple in ipairs(util.roman_numerals) do local letter, value = table.unpack(tuple) - table.insert(output, string.rep(letter, number // value)) + table.insert(output, string.rep(letter, math.floor(number / value))) number = number % value end return table.concat(output, "") @@ -793,4 +979,35 @@ function util.read_file(path) end +function util.parse_iso_date(str) + local date + local date_parts = util.split(str, "/") + if #date_parts <= 2 then + date = {["date-parts"] = {}} + for _, date_part in ipairs(date_parts) do + table.insert(date["date-parts"], util.split(date_part, "%-")) + end + end + if not date then + date = {literal = str} + end + return date +end + + +function util.parse_extra_name(str) + local name + local name_parts = util.split(str, "%s*||%s*") + if #name_parts == 2 then + name = { + family = name_parts[1], + given = name_parts[2], + } + else + name = {literal = str} + end + return name +end + + return util |