///|
pub fn Processor::render_citation(
self : Processor,
items : Array[Item],
cites : Array[Cite],
format~ : String = "text",
) -> Result[String, CslError] {
if format != "text" && format != "html" {
return Err(BadItem("unsupported output format: " + format))
}
let style = context_style(self.style, true)
let prepared = match prepare_items(items, context_style(self.style, false)) {
Ok(xs) => xs
Err(e) => return Err(e)
}
let layout = match style.citation {
Some(layout) => layout
None => return Err(MissingStyle)
}
let entries : Array[CitationEntry] = []
for i = 0; i < cites.length(); i = i + 1 {
let cite = cites[i]
let item = match item_by_id(prepared, cite.id) {
Some(item) => copy_render_item(item)
None => return Err(BadItem("unknown citation id: " + cite.id))
}
item.strings["locator"] = cite.locator
item.strings["locator-label"] = cite.label
if cite.suppress_author {
item.names.remove("author") |> ignore
}
entries.push({ item, cite, order: i, })
}
if !style.citation_sort.is_empty() {
entries.sort_by(fn(a, b) {
let c = compare_items(a.item, b.item, style.citation_sort, style)
if c == 0 {
cmp_int(a.order, b.order)
} else {
c
}
})
}
let can_collapse = style.citation_collapse == "citation-number" &&
numeric_layout(layout.children, style)
let buffer = StringBuilder()
let mut i = 0
let mut previous_collapsed = false
let mut emitted = false
while i < entries.length() {
let entry = entries[i]
let mut end = i
if can_collapse && collapsible_cite(entry.cite) {
let mut number = parse_int(item_value(entry.item, "citation-number")).unwrap_or(
0,
)
while end + 1 < entries.length() &&
collapsible_cite(entries[end + 1].cite) {
let next = parse_int(
item_value(entries[end + 1].item, "citation-number"),
).unwrap_or(0)
if next <= 0 || next - 1 != number {
break
}
end = end + 1
number = next
}
}
let collapsed = end - i >= 2
let rendered = if collapsed {
let separator = style.locale.terms
.get("citation-range-delimiter")
.unwrap_or("–")
// Render both endpoints independently: node-level formatting and affixes
// belong to each number, not to the range punctuation between them.
render_nodes(layout.children, entry.item, style, format) +
output_escape(separator, format) +
render_nodes(layout.children, entries[end].item, style, format)
} else {
render_nodes(layout.children, entry.item, style, format)
}
let value = output_escape(entry.cite.prefix, format) +
rendered +
output_escape(entry.cite.suffix, format)
if !value.is_empty() {
if emitted {
let delimiter = if previous_collapsed {
style.after_collapse_delimiter.unwrap_or(style.citation_delimiter)
} else {
style.citation_delimiter
}
buffer.write_string(output_escape(delimiter, format))
}
buffer.write_string(value)
emitted = true
previous_collapsed = collapsed
}
i = if collapsed { end + 1 } else { i + 1 }
}
Ok(apply_affix(layout.affix, buffer.to_string(), format~))
}
///|
priv struct CitationEntry {
item : Item
cite : Cite
order : Int32
}
///|
fn collapsible_cite(cite : Cite) -> Bool {
cite.locator.is_empty() &&
cite.prefix.is_empty() &&
cite.suffix.is_empty() &&
!cite.suppress_author
}
///|
fn numeric_layout(nodes : Array[Node], style : Style) -> Bool {
numeric_node_count(nodes, style) == Some(1)
}
///|
fn numeric_node_count(nodes : Array[Node], style : Style) -> Int32? {
let mut count = 0
for node in nodes {
let n = match node {
Text(_, spec) =>
if !spec.macro_name.is_empty() {
match style.macros.get(spec.macro_name) {
Some(body) => numeric_node_count(body, style)
None => None
}
} else if spec.value.is_empty() && spec.term.is_empty() {
if spec.variable == "citation-number" {
Some(1)
} else if spec.variable == "locator" {
Some(0)
} else {
None
}
} else {
None
}
Number(_, spec) =>
if spec.variable == "citation-number" &&
(spec.form.is_empty() || spec.form == "numeric") {
Some(1)
} else if spec.variable == "locator" {
Some(0)
} else {
None
}
Label(_, spec) => if spec.variable == "locator" { Some(0) } else { None }
Group(_, children) => numeric_node_count(children, style)
_ => None
}
match n {
Some(n) => count = count + n
None => return None
}
}
Some(count)
}
///|
pub fn Processor::render_citation_html(
self : Processor,
items : Array[Item],
cites : Array[Cite],
) -> Result[String, CslError] {
self.render_citation(items, cites, format="html")
}