///|
fn apply_affix(affix : Affix, text : String) -> String {
if text.is_empty() {
return ""
}
let mut value = text
value = apply_text_case(value, affix.text_case)
if !affix.font_style.is_empty() && affix.font_style == "italic" {
value = "" + value + ""
}
if !affix.font_weight.is_empty() && affix.font_weight == "bold" {
value = "" + value + ""
}
if !affix.vertical_align.is_empty() && affix.vertical_align == "sup" {
value = "" + value + ""
}
affix.prefix + value + affix.suffix
}
///|
fn format_number(value : String, form : String) -> String {
match parse_int(value) {
Some(n) =>
if form == "ordinal" {
ordinal_en(n)
} else if form == "long-ordinal" {
ordinal_en(n)
} else if form == "roman" {
roman_upper(n)
} else {
n.to_string()
}
None => value
}
}
///|
fn format_date(
date : CslDate,
parts : Array[DatePart],
delimiter : String,
) -> String {
let values = []
for part in parts {
let mut value = ""
if part.name == "year" && date.year != 0 {
value = date.year.to_string()
}
if part.name == "month" && date.month != 0 {
value = if part.form == "numeric-leading-zeros" {
pad2(date.month)
} else {
date.month.to_string()
}
}
if part.name == "day" && date.day != 0 {
value = if part.form == "ordinal" {
ordinal_en(date.day)
} else if part.form == "numeric-leading-zeros" {
pad2(date.day)
} else {
date.day.to_string()
}
}
if !value.is_empty() {
values.push(apply_affix(part_to_affix(part), value))
}
}
join_nonempty(values, delimiter)
}
///|
fn part_to_affix(part : DatePart) -> Affix {
{
prefix: part.prefix,
suffix: part.suffix,
delimiter: "",
text_case: part.text_case,
font_style: "",
font_weight: "",
vertical_align: "",
}
}
///|
fn name_initial(given : String, marker : String) -> String {
let words = split_ws(given)
let out = []
for word in words {
if !word.is_empty() {
out.push(
match word.to_array().get(0) {
Some(c) => c.to_string() + marker
None => ""
},
)
}
}
join_nonempty(out, " ")
}
///|
fn format_name(name : CslName, node : NameNode) -> String {
let mut given = name.given
if node.initialize {
given = name_initial(given, node.initialize_with)
}
if node.form == "short" {
return name.family
}
let mut family = name.family
if !name.non_dropping_particle.is_empty() {
family = name.non_dropping_particle + " " + family
}
if !name.suffix.is_empty() {
family = family + ", " + name.suffix
}
if node.name_as_sort_order == "all" || node.name_as_sort_order == "first" {
if !given.is_empty() {
family + ", " + given
} else {
family
}
} else {
join_nonempty([given, family], " ")
}
}
///|
fn format_names(names : Array[CslName], node : NameNode) -> String {
if names.length() == 0 {
return ""
}
let mut limit = names.length()
if node.et_al_min > 0 && names.length() >= node.et_al_min {
limit = node.et_al_use_first
}
let shown = []
for i = 0; i < limit && i < names.length(); i = i + 1 {
match names.get(i) {
Some(n) => shown.push(format_name(n, node))
None => ()
}
}
let mut result = join_nonempty(shown, node.delimiter)
if limit < names.length() {
result = result + node.delimiter + "et al."
}
result
}
///|
fn term_for(variable : String, form : String) -> String {
if form == "short" {
match variable {
"page" => "p."
"chapter" => "chap."
_ => variable
}
} else {
match variable {
"page" => "page"
"chapter" => "chapter"
"section" => "section"
_ => variable
}
}
}
///|
fn item_value(item : Item, variable : String) -> String {
match variable {
"citation-number" => item.strings.get("citation-number").unwrap_or("")
"year-suffix" => item.strings.get("year-suffix").unwrap_or("")
"author" | "editor" | "translator" => first_family(item, variable)
_ => item.string(variable)
}
}
///|
fn node_value(
node : Node,
item : Item,
style : Style,
format : String,
) -> String {
match node {
Text(affix, spec) => {
let value = if !spec.value.is_empty() {
spec.value
} else if !spec.macro_name.is_empty() {
match style.macros.get(spec.macro_name) {
Some(xs) => render_nodes(xs, item, style, format)
None => ""
}
} else if !spec.term.is_empty() {
term_for(spec.term, spec.form)
} else {
item_value(item, spec.variable)
}
apply_affix(
affix,
if format == "html" {
html_escape(value)
} else {
value
},
)
}
Names(affix, spec) =>
apply_affix(affix, format_names(item.name(spec.variable), spec))
Number(affix, spec) =>
apply_affix(
affix,
format_number(item_value(item, spec.variable), spec.form),
)
Date(affix, spec) =>
match item.date(spec.variable) {
Some(d) =>
apply_affix(affix, format_date(d, spec.parts, spec.delimiter))
None => ""
}
Group(affix, children) =>
apply_affix(
affix,
join_rendered(children, item, style, format, affix.delimiter),
)
Choose(affix, branches) =>
apply_affix(affix, render_choose(branches, item, style, format))
}
}
///|
fn condition_ok(condition : Condition, item : Item) -> Bool {
match condition {
Variable(v) => item.has(v)
Type(v) => item.item_type == v
Numeric(v) => {
let value = item_value(item, v)
is_csl_numeric(value)
}
IsUncertain(v) =>
match item.date(v) {
Some(d) => d.circa
None => false
}
IsPlural(v) => item.name(v).length() > 1
Position(_) => true
}
}
///|
fn branch_ok(branch : ChooseBranch, item : Item) -> Bool {
if branch.conditions.length() == 0 {
return true
}
let mut hits = 0
for c in branch.conditions {
if condition_ok(c, item) {
hits = hits + 1
}
}
if branch.match_mode == "any" {
hits > 0
} else if branch.match_mode == "none" {
hits == 0
} else {
hits == branch.conditions.length()
}
}
///|
fn render_choose(
branches : Array[ChooseBranch],
item : Item,
style : Style,
format : String,
) -> String {
for branch in branches {
if branch_ok(branch, item) {
return render_nodes(branch.children, item, style, format)
}
}
""
}
///|
fn render_nodes(
nodes : Array[Node],
item : Item,
style : Style,
format : String,
) -> String {
let out = []
let delimiter = ""
for node in nodes {
let value = node_value(node, item, style, format)
if !value.is_empty() {
out.push(value)
}
}
join_nonempty(out, delimiter)
}
///|
fn join_rendered(
children : Array[Node],
item : Item,
style : Style,
format : String,
delimiter : String,
) -> String {
let out = []
for child in children {
let value = node_value(child, item, style, format)
if !value.is_empty() {
out.push(value)
}
}
join_nonempty(out, delimiter).replace(old=". .", new=".")
}
///|
fn render_layout(
layout : Layout,
item : Item,
style : Style,
format : String,
) -> String {
apply_affix(
layout.affix,
join_rendered(layout.children, item, style, format, layout.affix.delimiter),
)
}
///|
fn sort_value(item : Item, key : SortKey, style : Style) -> String {
if !key.variable.is_empty() {
item_value(item, key.variable)
} else if !key.macro_name.is_empty() {
match style.macros.get(key.macro_name) {
Some(xs) => render_nodes(xs, item, style, "text")
None => ""
}
} else {
item.id
}
}
///|
fn sorted_items(
items : Array[Item],
keys : Array[SortKey],
style : Style,
) -> Array[Item] {
let out = items.copy()
out.sort_by(fn(a, b) {
for key in keys {
let c = cmp_str(sort_value(a, key, style), sort_value(b, key, style))
if c != 0 {
return if key.sort_order == "descending" { -c } else { c }
}
}
cmp_str(a.id, b.id)
})
out
}
///|
pub(all) struct Processor {
style : Style
}
///|
pub fn Processor::from_style(style_xml : String) -> Result[Processor, CslError] {
let _ = quality_helpers(style_xml)
match parse_style(style_xml) {
Ok(style) => Ok({ style, })
Err(e) => Err(e)
}
}
///|
pub fn Processor::render_bibliography(
self : Processor,
items : Array[Item],
format? : String = "text",
) -> Result[String, CslError] {
match self.style.bibliography {
None => Err(MissingStyle)
Some(layout) => {
let ordered = sorted_items(
items,
self.style.bibliography_sort,
self.style,
)
let out = []
for item in ordered {
out.push(render_layout(layout, item, self.style, format))
}
Ok(join_nonempty(out, "\n"))
}
}
}
///|
pub fn Processor::render_citation(
self : Processor,
items : Array[Item],
cites : Array[Cite],
format? : String = "text",
) -> Result[String, CslError] {
match self.style.citation {
None => Err(MissingStyle)
Some(layout) => {
let out = []
for cite in cites {
for item in items {
if item.id == cite.id {
out.push(render_layout(layout, item, self.style, format))
}
}
}
Ok(join_nonempty(out, self.style.citation_delimiter))
}
}
}
///|
pub fn Processor::render_bibliography_html(
self : Processor,
items : Array[Item],
) -> Result[String, CslError] {
self.render_bibliography(items, format="html")
}
///|
pub fn Processor::render_citation_html(
self : Processor,
items : Array[Item],
cites : Array[Cite],
) -> Result[String, CslError] {
self.render_citation(items, cites, format="html")
}
///|
pub fn render_one(style_xml : String, item : Item) -> Result[String, CslError] {
match Processor::from_style(style_xml) {
Ok(p) => p.render_bibliography([item])
Err(e) => Err(e)
}
}