summaryrefslogtreecommitdiff
path: root/biblio/citation-style-language/citeproc-node-style.lua
diff options
context:
space:
mode:
Diffstat (limited to 'biblio/citation-style-language/citeproc-node-style.lua')
-rw-r--r--biblio/citation-style-language/citeproc-node-style.lua573
1 files changed, 449 insertions, 124 deletions
diff --git a/biblio/citation-style-language/citeproc-node-style.lua b/biblio/citation-style-language/citeproc-node-style.lua
index 2fb10cc79c..5c387bd6c8 100644
--- a/biblio/citation-style-language/citeproc-node-style.lua
+++ b/biblio/citation-style-language/citeproc-node-style.lua
@@ -4,15 +4,126 @@
-- Repository: https://github.com/zepinglee/citeproc-lua
--
-local style = {}
+local style_module = {}
-local element = require("citeproc-element")
+local dom = require("luaxml-domobject")
+
+local Element = require("citeproc-element").Element
+local IrNode = require("citeproc-ir-node").IrNode
+local Rendered = require("citeproc-ir-node").Rendered
+local SeqIr = require("citeproc-ir-node").SeqIr
+local PlainText = require("citeproc-output").PlainText
+local DisamStringFormat = require("citeproc-output").DisamStringFormat
local util = require("citeproc-util")
-local Style = element.Element:new()
+local Style = Element:derive("style")
+
+function Style:new()
+ local o = {
+ children = {},
+ macros = {},
+ locales = {},
+ initialize_with_hyphen = true,
+ demote_non_dropping_particle = "display-and-sort",
+ }
+ setmetatable(o, self)
+ self.__index = self
+ return o
+end
+
+function Style:parse(xml_str)
+ local csl_xml = dom.parse(xml_str)
+ if not csl_xml then
+ error("Failed to parse CSL style.")
+ end
+ local style_node = csl_xml:get_path("style")[1]
+ if not csl_xml then
+ error('Element "style" not found.')
+ end
+ return Style:from_node(style_node)
+end
+
+local function make_name_inheritance(name, node)
+ name:set_attribute(node, "and")
+ name:set_attribute(node, "delimiter-precedes-et-al")
+ name:set_attribute(node, "delimiter-precedes-last")
+ name:set_number_attribute(node, "et-al-min")
+ name:set_number_attribute(node, "et-al-use-first")
+ name:set_number_attribute(node, "et-al-subsequent-min")
+ name:set_number_attribute(node, "et-al-subsequent-use-first")
+ name:set_bool_attribute(node, "et-al-use-last")
+ name:set_bool_attribute(node, "initialize")
+ name:set_attribute(node, "initialize-with")
+ name:set_attribute(node, "name-as-sort-order")
+ name:set_attribute(node, "sort-separator")
+ local delimiter = node:get_attribute("name-delimiter")
+ if delimiter then
+ name.delimiter = delimiter
+ end
+ local form = node:get_attribute("name-form")
+ if form then
+ name.form = form
+ end
+ local names_delimiter = node:get_attribute("names-delimiter")
+ if names_delimiter then
+ name.names_delimiter = names_delimiter
+ end
+end
+
+function Style:from_node(node)
+ local o = Style:new()
+
+ o:set_attribute(node, "class")
+ o:set_attribute(node, "default-locale")
+ o:set_attribute(node, "version")
+
+ -- Global Options
+ o.initialize_with_hyphen = true
+ o:set_bool_attribute(node, "initialize-with-hyphen")
+ o:set_attribute(node, "page-range-format")
+ o:set_attribute(node, "demote-non-dropping-particle")
+
+ -- Inheritable Name Options
+ -- https://docs.citationstyles.org/en/stable/specification.html#inheritable-name-options
+ o.name_inheritance = require("citeproc-node-names").Name:new()
+ make_name_inheritance(o.name_inheritance, node)
+
+ if o.page_range_format == "chicago" then
+ if o.version < "1.1" then
+ o.page_range_format = "chicago-15"
+ else
+ o.page_range_format = "chicago-16"
+ end
+ end
+
+ o.macros = {}
+ o.locales = {}
+
+ o.children = {}
+ o:process_children_nodes(node)
+
+ for _, child in ipairs(o.children) do
+ local element_name = child.element_name
+ if element_name == "info" then
+ o.info = child
+ elseif element_name == "citation" then
+ o.citation = child
+ elseif element_name == "bibliography" then
+ o.bibliography = child
+ elseif element_name == "macro" then
+ o.macros[child.name] = child
+ elseif element_name == "locale" then
+ local xml_lang = child.xml_lang or "@generic"
+ o.locales[xml_lang] = child
+ end
+ end
+
+ return o
+end
+
-Style.default_options = {
+Style._default_options = {
["initialize-with-hyphen"] = true,
["page-range-format"] = nil,
["demote-non-dropping-particle"] = "display-and-sort",
@@ -29,171 +140,385 @@ function Style:set_lang(lang, force_lang)
end
end
-function Style:render_citation (items, context)
- self:debug_info(context)
- context = self:process_context(context)
- context.style = self
- local citation = self:get_child("citation")
- return citation:render(items, context)
-end
-function Style:render_biblography (items, context)
- self:debug_info(context)
- context = self:process_context(context)
- context.style = self
- local bibliography = self:get_child("bibliography")
- return bibliography:render(items, context)
-end
+local Info = Element:derive("info")
+
+
+function Info:from_node(node)
+ local o = Info:new()
+
+ -- o.authors = nil
+ -- o.contributors = nil
+ o.categories = {}
+ o.id = nil
+ -- o.issn = nil
+ -- o.eissn = nil
+ -- o.issnl = nil
+ o.links = {
+ independent_parent = nil,
+ }
+ -- o.published = nil
+ -- o.rights = nil
+ -- o.summary = nil
+ o.title = nil
+ -- o.title_short = nil
+ o.updated = nil
+
+ for _, child in ipairs(node:get_children()) do
+ if child:is_element() then
+ local element_name = child:get_element_name()
+ if element_name == "category" then
+ local citation_format = child:get_attribute("citation-format")
+ if citation_format then
+ o.categories.citation_format = citation_format
+ end
+
+ elseif element_name == "id" then
+ o.id = child:get_text()
+
+ elseif element_name == "link" then
+ local href = child:get_attribute("href")
+ local rel = child:get_attribute("rel")
+ if href and rel == "independent-parent" then
+ o.links.independent_parent = href
+ end
+
+ elseif element_name == "title" then
+ o.title = child:get_text()
+
+ elseif element_name == "updated" then
+ o.updated = child:get_text()
+
+ end
+ end
+ end
-function Style:get_version ()
- return self:get_attribute("version")
+ return o
end
-function Style:get_locales()
- if not self.locale_dict then
- self.locale_dict = {}
- end
- local locales = self.locale_dict[self.lang]
- if not locales then
- locales = self:get_locale_list(self.lang)
- self.locale_dict[self.lang] = locales
- end
- return locales
-end
-function Style:get_locale_list (lang)
- assert(lang ~= nil)
- local language = string.sub(lang, 1, 2)
- local primary_dialect = util.primary_dialects[language]
- if not primary_dialect then
- -- util.warning(string.format("Failed to find primary dialect of \"%s\"", language))
- end
- local locale_list = {}
+local Citation = Element:derive("citation", {
+ givenname_disambiguation_rule = "by-cite",
+ -- https://github.com/citation-style-language/schema/issues/338
+ -- The cite_group_delimiter may be changed to inherit the delimiter in citaion > layout.
+ cite_group_delimiter = ", ",
+ near_note_distance = 5,
+})
- -- 1. In-style cs:locale elements
- -- i. `xml:lang` set to chosen dialect, “de-AT”
- if lang == language then
- lang = primary_dialect
- end
- table.insert(locale_list, self:get_in_style_locale(lang))
+function Citation:from_node(node, style)
- -- ii. `xml:lang` set to matching language, “de” (German)
- if language and language ~= lang then
- table.insert(locale_list, self:get_in_style_locale(language))
- end
+ local o = Citation:new()
+ o.children = {}
- -- iii. `xml:lang` not set
- table.insert(locale_list, self:get_in_style_locale(nil))
+ o:process_children_nodes(node)
- -- 2. Locale files
- -- iv. `xml:lang` set to chosen dialect, “de-AT”
- if lang then
- table.insert(locale_list, self:get_engine():get_system_locale(lang))
+ -- o.layouts = nil -- CSL-M extension
+
+ for _, child in ipairs(o.children) do
+ local element_name = child.element_name
+ if element_name == "layout" then
+ o.layout = child
+ elseif element_name == "sort" then
+ o.sort = child
+ end
end
- -- v. `xml:lang` set to matching primary dialect, “de-DE” (Standard German)
- -- (only applicable when the chosen locale is a secondary dialect)
- if primary_dialect and primary_dialect ~= lang then
- table.insert(locale_list, self:get_engine():get_system_locale(primary_dialect))
+ -- Disambiguation
+ o:set_bool_attribute(node, "disambiguate-add-givenname")
+ o:set_attribute(node, "givenname-disambiguation-rule")
+ o:set_bool_attribute(node, "disambiguate-add-names")
+ o:set_bool_attribute(node, "disambiguate-add-year-suffix")
+
+ -- Cite Grouping
+ o:set_attribute(node, "cite-group-delimiter")
+ -- In the current citeproc-js implementation and test suite,
+ -- cite grouping is activated by setting the cite-group-delimiter
+ -- attribute or the collapse attributes on cs:citation.
+ -- It may be changed to an independent procedure.
+ -- https://github.com/citation-style-language/schema/issues/338
+ if node:get_attribute("cite-group-delimiter") then
+ o.cite_grouping = true
+ else
+ o.cite_grouping = false
end
- -- vi. `xml:lang` set to “en-US” (American English)
- if lang ~= "en-US" and primary_dialect ~= "en-US" then
- table.insert(locale_list, self:get_engine():get_system_locale("en-US"))
+
+ -- Cite Collapsing
+ o:set_attribute(node, "collapse")
+ o:set_attribute(node, "year-suffix-delimiter")
+ if not o.year_suffix_delimiter then
+ o.year_suffix_delimiter = o.layout.delimiter
+ end
+ o:set_attribute(node, "after-collapse-delimiter")
+ if not o.after_collapse_delimiter then
+ o.after_collapse_delimiter = o.layout.delimiter
end
- return locale_list
-end
+ -- Note Distance
+ o:set_number_attribute(node, "near-note-distance")
-function Style:get_in_style_locale (lang)
- for _, locale in ipairs(self:query_selector("locale")) do
- if locale:get_attribute("xml:lang") == lang then
- return locale
+ local name_inheritance = require("citeproc-node-names").Name:new()
+ for key, value in pairs(style.name_inheritance) do
+ if value ~= nil then
+ name_inheritance[key] = value
end
end
- return nil
+ make_name_inheritance(name_inheritance, node)
+ o.name_inheritance = name_inheritance
+
+ -- update_mode = "plain" or "numeric" or "position" (or "both"?)
+
+ return o
end
-function Style:get_term (...)
- for _, locale in ipairs(self:get_locales()) do
- local res = locale:get_term(...)
- if res then
- return res
- end
+function Citation:build_ir(engine, state, context)
+ if not self.layout then
+ util.error("Missing citation layout.")
end
- return nil
+ return self.layout:build_ir(engine, state, context)
end
-local Citation = element.Element:new()
+local Bibliography = Element:derive("bibliography", {
+ hanging_indent = false,
+ line_spacing = 1,
+ entry_spacing = 1,
+ subsequent_author_substitute_rule = "complete-all"
+})
+
+function Bibliography:from_node(node, style)
+ local o = Bibliography:new()
+ o.children = {}
-function Citation:render (items, context)
- self:debug_info(context)
- context = self:process_context(context)
+ o:process_children_nodes(node)
- context.mode = "citation"
- context.citation = self
+ -- o.layouts = nil -- CSL-M extension
- local sort = self:get_child("sort")
- if sort then
- sort:sort(items, context)
+ for _, child in ipairs(o.children) do
+ local element_name = child.element_name
+ if element_name == "layout" then
+ o.layout = child
+ elseif element_name == "sort" then
+ o.sort = child
+ end
end
- local layout = self:get_child("layout")
- return layout:render(items, context)
+ -- Whitespace
+ o:set_bool_attribute(node, "hanging-indent")
+ o:set_attribute(node, "second-field-align")
+ o:set_number_attribute(node, "line-spacing")
+ o:set_number_attribute(node, "entry-spacing")
+
+ -- Reference Grouping
+ o:set_attribute(node, "subsequent-author-substitute")
+ o:set_attribute(node, "subsequent-author-substitute-rule")
+
+ local name_inheritance = require("citeproc-node-names").Name:new()
+ for key, value in pairs(style.name_inheritance) do
+ if value ~= nil then
+ name_inheritance[key] = value
+ end
+ end
+ make_name_inheritance(name_inheritance, node)
+ o.name_inheritance = name_inheritance
+
+ return o
end
+function Bibliography:build_ir(engine, state, context)
+ if not self.layout then
+ util.error("Missing bibliography layout.")
+ end
+ local ir = self.layout:build_ir(engine, state, context)
+ -- util.debug(ir)
+ if self.second_field_align == "flush" and #ir.children >= 2 then
+ ir.children[1].display = "left-margin"
+ local right_inline_ir = SeqIr:new(util.slice(ir.children, 2), self)
+ right_inline_ir.display = "right-inline"
+ if ir.affixes then
+ right_inline_ir.affixes = ir.affixes
+ right_inline_ir.formatting = ir.formatting
+ ir.affixes = nil
+ ir.formatting = nil
+ end
+ ir.children = {ir.children[1], right_inline_ir}
+ end
-local Bibliography = element.Element:new()
+ if self.subsequent_author_substitute then
+ self:substitute_subsequent_authors(engine, ir)
+ end
-Bibliography.default_options = {
- ["hanging-indent"] = false,
- ["second-field-align"] = nil,
- ["line-spacing"] = 1,
- ["entry-spacing"] = 1,
- ["subsequent-author-substitute"] = nil,
- ["subsequent-author-substitute-rule"] = "complete-all",
-}
+ if not ir then
+ ir = Rendered:new(PlainText:new("[CSL STYLE ERROR: reference with no printed form.]"), self)
+ end
+ return ir
+end
-function Bibliography:render (items, context)
- self:debug_info(context)
- context = self:process_context(context)
- -- util.debug(context)
+function Bibliography:substitute_subsequent_authors(engine, ir)
+ ir.first_name_ir = self:find_first_name_ir(ir) -- should be a SeqIr wiht _element = "names"
+ if not ir.first_name_ir then
+ engine.previous_bib_names_ir = nil
+ return
+ end
+ if self.subsequent_author_substitute_rule == "complete-all" then
+ self:substitute_subsequent_authors_complete_all(engine, ir)
+ elseif self.subsequent_author_substitute_rule == "complete-each" then
+ self:substitute_subsequent_authors_complete_each(engine, ir)
+ elseif self.subsequent_author_substitute_rule == "partial-each" then
+ self:substitute_subsequent_authors_partial_each(engine, ir)
+ elseif self.subsequent_author_substitute_rule == "partial-first" then
+ self:substitute_subsequent_authors_partial_first(engine, ir)
+ end
+ engine.previous_bib_names_ir = ir.first_name_ir
+end
- context.mode = "bibliography"
- context.bibliography = self
+function Bibliography:find_first_name_ir(ir)
+ if ir._type == "NameIr" then
+ return ir
+ elseif ir.children then
+ for _, child_ir in ipairs(ir.children) do
+ local first_name_ir = self:find_first_name_ir(child_ir)
+ if first_name_ir then
+ return first_name_ir
+ end
+ end
+ end
+ return nil
+end
- -- Already sorted in CiteProc:sort_bibliography()
+function Bibliography:substitute_subsequent_authors_complete_all(engine, ir)
+ local bib_names_str = ""
- local layout = self:get_child("layout")
- local res = layout:render(items, context)
+ if #ir.first_name_ir.person_name_irs > 0 then
+ for _, person_name_ir in ipairs(ir.first_name_ir.person_name_irs) do
+ if bib_names_str ~= "" then
+ bib_names_str = bib_names_str .. " "
+ end
+ local name_variants = person_name_ir.disam_variants
+ bib_names_str = bib_names_str .. name_variants[#name_variants]
+ end
+ else
+ -- In case of a <text variable="title"/> in <substitute>
+ local disam_format = DisamStringFormat:new()
+ local inlines = ir.first_name_ir:flatten(disam_format)
+ bib_names_str = disam_format:output(inlines)
+ end
+ ir.first_name_ir.bib_names_str = bib_names_str
+
+ if engine.previous_bib_names_ir and
+ engine.previous_bib_names_ir.bib_names_str == bib_names_str then
+ local text = self.subsequent_author_substitute
+ if text == "" then
+ ir.first_name_ir.children = {}
+ ir.first_name_ir.group_var = "missing"
+ else
+ -- the output of label is not substituted
+ -- util.debug(ir.first_name_ir)
+ ir.first_name_ir.children = {Rendered:new({PlainText:new(text)}, self)}
+ end
+ end
+end
- local params = res[1]
+function Bibliography:substitute_subsequent_authors_complete_each(engine, ir)
+ local bib_names_str = ""
- params.entryspacing = context.options["entry-spacing"]
- params.linespacing = context.options["line-spacing"]
- params.hangingindent = context.options["hanging-indent"]
- params["second-field-align"] = context.options["second-field-align"]
- for _, key in ipairs({"bibstart", "bibend"}) do
- local value = context.engine.formatter[key]
- if type(value) == "function" then
- value = value(context)
+ if #ir.first_name_ir.person_name_irs > 0 then
+ for _, person_name_ir in ipairs(ir.first_name_ir.person_name_irs) do
+ if bib_names_str ~= "" then
+ bib_names_str = bib_names_str .. " "
+ end
+ local name_variants = person_name_ir.disam_variants
+ bib_names_str = bib_names_str .. name_variants[#name_variants]
end
- params[key] = value
+ else
+ -- In case of a <text variable="title"/> in <substitute>
+ local disam_format = DisamStringFormat:new()
+ local inlines = ir.first_name_ir:flatten(disam_format)
+ bib_names_str = disam_format:output(inlines)
end
+ ir.first_name_ir.bib_names_str = bib_names_str
+
+ if engine.previous_bib_names_ir and
+ engine.previous_bib_names_ir.bib_names_str == bib_names_str then
+ local text = self.subsequent_author_substitute
+ if #ir.first_name_ir.person_name_irs > 0 then
+ for _, person_name_ir in ipairs(ir.first_name_ir.person_name_irs) do
+ person_name_ir.inlines = {PlainText:new(text)}
+ end
+ else
+ -- In case of a <text variable="title"/> in <substitute>
+ if text == "" then
+ ir.first_name_ir.children = {}
+ ir.first_name_ir.group_var = "missing"
+ else
+ ir.first_name_ir.children = {Rendered:new({PlainText:new(text)}, self)}
+ end
+ end
+ end
+end
- params.bibliography_errors = {}
- params.entry_ids = {}
- for _, item in ipairs(items) do
- table.insert(params.entry_ids, item.id)
+function Bibliography:substitute_subsequent_authors_partial_each(engine, ir)
+ local bib_names_str = ""
+
+ if #ir.first_name_ir.person_name_irs > 0 then
+ if engine.previous_bib_names_ir then
+ for i, person_name_ir in ipairs(ir.first_name_ir.person_name_irs) do
+ local prev_name_ir = engine.previous_bib_names_ir.person_names[i]
+ if prev_name_ir then
+ local prev_name_variants = prev_name_ir.disam_variants
+ local prev_full_name_str = prev_name_variants[#prev_name_variants]
+ local name_variants = person_name_ir.disam_variants
+ local full_name_str = name_variants[#name_variants]
+ if prev_full_name_str == full_name_str then
+ local text = self.subsequent_author_substitute
+ person_name_ir.inlines = {PlainText:new(text)}
+ else
+ break
+ end
+ end
+ end
+ end
+ else
+ -- In case of a <text variable="title"/> in <substitute>
+ local disam_format = DisamStringFormat:new()
+ local inlines = ir.first_name_ir:flatten(disam_format)
+ bib_names_str = disam_format:output(inlines)
+ ir.first_name_ir.bib_names_str = bib_names_str
+ if engine.previous_bib_names_ir and
+ engine.previous_bib_names_ir.bib_names_str == bib_names_str then
+ local text = self.subsequent_author_substitute
+ if text == "" then
+ ir.first_name_ir.children = {}
+ ir.first_name_ir.group_var = "missing"
+ else
+ ir.first_name_ir.children = {Rendered:new({PlainText:new(text)}, self)}
+ end
+ end
end
+end
+
+function Bibliography:substitute_subsequent_authors_partial_first(engine, ir)
+end
+
+local Macro = Element:derive("macro")
- return res
+function Macro:from_node(node)
+ local o = Macro:new()
+ o.children = {}
+ o:set_attribute(node, "name")
+ o:process_children_nodes(node)
+ return o
end
-style.Style = Style
-style.Citation = Citation
-style.Bibliography = Bibliography
+function Macro:build_ir(engine, state, context)
+ local ir = self:build_group_ir(engine, state, context)
+ return ir
+end
+
+
+style_module.Style = Style
+style_module.Citation = Citation
+style_module.Bibliography = Bibliography
-return style
+return style_module