summaryrefslogtreecommitdiff
path: root/biblio/citation-style-language/citeproc-util.lua
diff options
context:
space:
mode:
Diffstat (limited to 'biblio/citation-style-language/citeproc-util.lua')
-rw-r--r--biblio/citation-style-language/citeproc-util.lua309
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