use std::borrow::Cow;
use std::fmt::Write;
use std::str::FromStr;
use citationberg::taxonomy::{
Locator, NumberVariable, OtherTerm, StandardVariable, Term, Variable,
};
use citationberg::{
ChooseBranch, CslMacro, DateDayForm, DateMonthForm, DatePartName, DateParts,
DateStrongAnyForm, GrammarGender, LabelPluralize, LayoutRenderingElement,
LongShortForm, NumberForm, TestPosition, TextCase, ToAffixes, ToFormatting,
};
use citationberg::{TermForm, TextTarget};
use crate::csl::taxonomy::NumberVariableResult;
use crate::lang::{Case, SentenceCase, TitleCase};
use crate::types::{ChunkedString, Date, MaybeTyped, Numeric};
use super::taxonomy::EntryLike;
use super::{Context, ElemMeta, IbidState, SpecialForm, UsageInfo};
pub mod names;
pub(crate) trait RenderCsl {
fn render<T: EntryLike>(&self, ctx: &mut Context<T>);
fn will_render<T: EntryLike>(&self, ctx: &mut Context<T>, var: Variable) -> bool;
fn will_have_info<T: EntryLike>(&self, ctx: &mut Context<T>) -> (bool, UsageInfo);
}
impl RenderCsl for citationberg::Text {
fn render<T: EntryLike>(&self, ctx: &mut Context<T>) {
let Some(target) = ResolvedTextTarget::compute(self, ctx) else { return };
let depth = ctx.push_elem(self.formatting);
#[allow(clippy::match_like_matches_macro)]
let print_affixes = match ctx.instance.kind {
Some(SpecialForm::VarOnly(_))
if matches!(&self.target, TextTarget::Macro { .. }) =>
{
false
}
_ => true,
};
let affix_loc = print_affixes.then(|| ctx.apply_prefix(&self.affixes));
if self.quotes {
ctx.push_quotes();
}
ctx.may_strip_periods(self.strip_periods);
let cidx = ctx.push_case(self.text_case);
match target {
ResolvedTextTarget::StandardVariable(var, val) => match var {
StandardVariable::URL => {
let str = val.to_string();
ctx.push_link(&val, str);
}
StandardVariable::DOI => {
let url = format!("https://doi.org/{}", val.to_str());
ctx.push_link(&val, url);
}
StandardVariable::PMID => {
let url =
format!("https://www.ncbi.nlm.nih.gov/pubmed/{}", val.to_str());
ctx.push_link(&val, url);
}
StandardVariable::PMCID => {
let url = format!(
"https://www.ncbi.nlm.nih.gov/pmc/articles/{}",
val.to_str()
);
ctx.push_link(&val, url);
}
_ => ctx.push_chunked(&val),
},
ResolvedTextTarget::NumberVariable(var, n) => match n {
NumberVariableResult::Regular(MaybeTyped::Typed(num))
if num.will_transform() =>
{
render_typed_num(num.as_ref(), NumberForm::default(), var, None, ctx);
}
NumberVariableResult::Regular(n)
if matches!(var, NumberVariable::Page) =>
{
ctx.push_str(&n.to_str().replace('-', "–"))
}
NumberVariableResult::Regular(n) => ctx.push_str(&n.to_str()),
NumberVariableResult::Transparent(n) => ctx.push_transparent(n),
},
ResolvedTextTarget::Macro(mac) => {
for child in &mac.children {
child.render(ctx);
}
}
ResolvedTextTarget::Term(s) => ctx.push_str(s),
ResolvedTextTarget::Value(val) => ctx.push_str(val),
}
ctx.pop_case(cidx);
ctx.stop_stripping_periods();
if self.quotes {
ctx.pop_quotes();
ctx.may_pull_punctuation();
}
if let Some(affix_loc) = affix_loc {
ctx.apply_suffix(&self.affixes, affix_loc);
}
ctx.commit_elem(
depth,
self.display,
match self.target {
TextTarget::Variable { var, .. }
if var == NumberVariable::CitationNumber.into() =>
{
Some(ElemMeta::CitationNumber)
}
TextTarget::Variable { .. } => Some(ElemMeta::Text),
_ => None,
},
);
}
fn will_render<T: EntryLike>(&self, ctx: &mut Context<T>, var: Variable) -> bool {
let Some(target) = ResolvedTextTarget::compute(self, ctx) else {
return false;
};
match target {
ResolvedTextTarget::StandardVariable(s, _) => var == Variable::Standard(s),
ResolvedTextTarget::NumberVariable(n, _) => var == Variable::Number(n),
ResolvedTextTarget::Macro(mac) => {
mac.children.iter().any(|c| c.will_render(ctx, var))
}
ResolvedTextTarget::Term(_) => false,
ResolvedTextTarget::Value(_) => false,
}
}
fn will_have_info<T: EntryLike>(&self, ctx: &mut Context<T>) -> (bool, UsageInfo) {
let suppressing = ctx.writing.suppress_queried_variables;
ctx.writing.stop_suppressing_queried_variables();
let lookup_result = ResolvedTextTarget::compute(self, ctx);
if suppressing {
ctx.writing.start_suppressing_queried_variables();
}
let targets_variable = matches!(self.target, TextTarget::Variable { .. });
let Some(target) = lookup_result else {
return (
false,
UsageInfo { has_vars: targets_variable, ..Default::default() },
);
};
match target {
ResolvedTextTarget::StandardVariable(_, s) if s.is_empty() => {
(false, UsageInfo { has_vars: true, ..Default::default() })
}
ResolvedTextTarget::StandardVariable(_, _)
| ResolvedTextTarget::NumberVariable(_, _) => (
true,
UsageInfo {
has_vars: true,
has_non_empty_vars: true,
..Default::default()
},
),
ResolvedTextTarget::Macro(mac) => {
let mut will_print = false;
let mut info = UsageInfo::default();
for child in &mac.children {
let (print, child_info) = child.will_have_info(ctx);
will_print |= print;
info = info.merge_child(child_info)
}
(will_print, UsageInfo { has_used_macros: will_print, ..info })
}
ResolvedTextTarget::Term(_) => (true, UsageInfo::default()),
ResolvedTextTarget::Value(v) => (!v.is_empty(), UsageInfo::default()),
}
}
}
enum ResolvedTextTarget<'a, 'b> {
StandardVariable(StandardVariable, Cow<'a, ChunkedString>),
NumberVariable(NumberVariable, NumberVariableResult<'a>),
Macro(&'a CslMacro),
Term(&'a str),
Value(&'b str),
}
impl<'a, 'b> ResolvedTextTarget<'a, 'b> {
fn compute<T: EntryLike>(
text: &'b citationberg::Text,
ctx: &mut Context<'a, T>,
) -> Option<Self> {
match ctx.instance.kind {
Some(SpecialForm::VarOnly(v))
if !matches!(&text.target, TextTarget::Macro { .. }) =>
{
match text.target {
TextTarget::Variable { var, .. } if var == v => {}
_ => return None,
}
}
Some(SpecialForm::OnlyFirstDate | SpecialForm::OnlyYearSuffix)
if !matches!(&text.target, TextTarget::Macro { .. }) =>
{
if !matches!(
text.target,
TextTarget::Variable {
var: Variable::Standard(StandardVariable::YearSuffix)
| Variable::Number(NumberVariable::Locator),
..
},
) {
return None;
}
}
_ => {}
}
match &text.target {
TextTarget::Variable { var: Variable::Standard(var), form } => ctx
.resolve_standard_variable(*form, *var)
.map(|s| ResolvedTextTarget::StandardVariable(*var, s)),
TextTarget::Variable { var: Variable::Number(var), .. } => ctx
.resolve_number_variable(*var)
.map(|n| ResolvedTextTarget::NumberVariable(*var, n)),
TextTarget::Variable { .. } => None,
TextTarget::Macro { name } => {
ctx.style.get_macro(name).map(ResolvedTextTarget::Macro)
}
TextTarget::Term { term, form, plural } => {
ctx.term(*term, *form, *plural).map(ResolvedTextTarget::Term)
}
TextTarget::Value { val } => Some(ResolvedTextTarget::Value(val)),
}
}
}
impl RenderCsl for citationberg::Number {
fn render<T: EntryLike>(&self, ctx: &mut Context<T>) {
if !self.will_render(ctx, self.variable.into()) {
return;
}
let value = ctx.resolve_number_variable(self.variable);
if ctx.instance.sorting {
if let Some(NumberVariableResult::Regular(MaybeTyped::Typed(n))) = value {
n.fmt_value(ctx, true).unwrap();
return;
}
}
let depth = ctx.push_elem(self.formatting);
let affix_loc = ctx.apply_prefix(&self.affixes);
let cidx = ctx.push_case(self.text_case);
let gender = ctx.gender(self.variable.into());
match value {
Some(NumberVariableResult::Regular(MaybeTyped::Typed(num)))
if num.will_transform() =>
{
render_typed_num(num.as_ref(), self.form, self.variable, gender, ctx);
}
Some(NumberVariableResult::Regular(MaybeTyped::Typed(num))) => {
write!(ctx, "{}", num).unwrap()
}
Some(NumberVariableResult::Regular(MaybeTyped::String(s))) => {
ctx.push_str(&s)
}
Some(NumberVariableResult::Transparent(n)) => ctx.push_transparent(n),
None => {}
}
ctx.pop_case(cidx);
ctx.apply_suffix(&self.affixes, affix_loc);
ctx.commit_elem(
depth,
self.display,
(self.variable == NumberVariable::CitationNumber)
.then_some(ElemMeta::CitationNumber)
.or(Some(ElemMeta::Number)),
);
}
fn will_render<T: EntryLike>(&self, ctx: &mut Context<T>, var: Variable) -> bool {
match ctx.instance.kind {
Some(SpecialForm::VarOnly(Variable::Number(n))) if self.variable != n => {
return false
}
Some(SpecialForm::OnlyFirstDate | SpecialForm::OnlyYearSuffix) => {
return self.variable == NumberVariable::Locator
}
Some(SpecialForm::VarOnly(_)) => return false,
_ => {}
}
var == Variable::Number(self.variable)
}
fn will_have_info<T: EntryLike>(&self, ctx: &mut Context<T>) -> (bool, UsageInfo) {
if self.will_render(ctx, self.variable.into()) {
let suppressing = ctx.writing.suppress_queried_variables;
ctx.writing.stop_suppressing_queried_variables();
let lookup_result = ctx.resolve_number_variable(self.variable);
if suppressing {
ctx.writing.start_suppressing_queried_variables();
}
(
lookup_result.is_some(),
UsageInfo {
has_vars: true,
has_non_empty_vars: lookup_result.is_some(),
..Default::default()
},
)
} else {
(false, UsageInfo { has_vars: true, ..Default::default() })
}
}
}
fn render_typed_num<T: EntryLike>(
num: &Numeric,
form: NumberForm,
variable: NumberVariable,
gender: Option<GrammarGender>,
ctx: &mut Context<T>,
) {
let normal_num = if form == NumberForm::Numeric && variable == NumberVariable::Page {
if let Some(range) = num.range() {
render_page_range(range, ctx);
false
} else {
true
}
} else {
true
};
if normal_num {
num.with_form(ctx, form, gender, ctx.ordinal_lookup()).unwrap();
}
}
fn render_page_range<T: EntryLike>(range: std::ops::Range<i32>, ctx: &mut Context<T>) {
ctx.style
.csl
.settings
.page_range_format
.unwrap_or_default()
.format(
range,
ctx,
ctx.term(OtherTerm::PageRangeDelimiter.into(), TermForm::default(), false)
.or(Some("–")),
)
.unwrap();
}
fn label_pluralization(
label: &citationberg::Label,
variable: NumberVariableResult,
) -> bool {
match label.label.plural {
LabelPluralize::Always => true,
LabelPluralize::Never => false,
LabelPluralize::Contextual => match variable {
NumberVariableResult::Regular(MaybeTyped::String(_)) => false,
NumberVariableResult::Regular(MaybeTyped::Typed(n)) => {
n.is_plural(label.variable.is_number_of_variable())
}
NumberVariableResult::Transparent(_) => false,
},
}
}
impl RenderCsl for citationberg::Label {
fn render<T: EntryLike>(&self, ctx: &mut Context<T>) {
if !self.will_have_info(ctx).0 {
return;
}
let Some(variable) = ctx.resolve_number_variable(self.variable) else {
return;
};
let depth = ctx.push_elem(citationberg::Formatting::default());
let plural = label_pluralization(self, variable);
let content = ctx
.term(Term::from(self.variable), self.label.form, plural)
.unwrap_or_default();
render_label_with_var(&self.label, ctx, content);
ctx.commit_elem(depth, None, Some(ElemMeta::Label));
}
fn will_render<T: EntryLike>(&self, _ctx: &mut Context<T>, _var: Variable) -> bool {
false
}
fn will_have_info<T: EntryLike>(&self, ctx: &mut Context<T>) -> (bool, UsageInfo) {
match ctx.instance.kind {
Some(SpecialForm::VarOnly(Variable::Number(n))) if self.variable != n => {
return (false, UsageInfo::default());
}
Some(
SpecialForm::VarOnly(_)
| SpecialForm::OnlyFirstDate
| SpecialForm::OnlyYearSuffix,
) => {
if self.variable != NumberVariable::Locator {
return (true, UsageInfo::default());
}
}
_ => {}
}
if self.variable == NumberVariable::Locator
&& ctx
.instance
.cite_props
.speculative
.locator
.map_or(false, |l| l.0 == Locator::Custom)
{
return (false, UsageInfo::default());
}
if let Some(num) = ctx.resolve_number_variable(self.variable) {
let plural = label_pluralization(self, num);
(
ctx.term(Term::from(self.variable), self.label.form, plural).is_some(),
UsageInfo::default(),
)
} else {
(false, UsageInfo::default())
}
}
}
fn render_label_with_var<T: EntryLike>(
label: &citationberg::VariablelessLabel,
ctx: &mut Context<T>,
content: &str,
) {
if content.is_empty() {
return;
}
let idx = ctx.push_format(label.formatting);
let affixes = &label.affixes;
let affix_loc = ctx.apply_prefix(affixes);
ctx.may_strip_periods(label.strip_periods);
let cidx = ctx.push_case(label.text_case);
ctx.push_str(content);
ctx.pop_case(cidx);
ctx.stop_stripping_periods();
ctx.apply_suffix(affixes, affix_loc);
ctx.pop_format(idx);
}
impl RenderCsl for citationberg::Date {
fn render<T: EntryLike>(&self, ctx: &mut Context<T>) {
let Some(variable) = self.variable else { return };
if !self.will_render(ctx, variable.into()) {
return;
}
let Some(date) = ctx.resolve_date_variable(variable) else { return };
if ctx.instance.sorting {
let year;
let mut month = false;
let mut day = false;
if self.is_localized() {
match self.parts {
Some(DateParts::Year) => year = true,
Some(DateParts::YearMonth) => {
year = true;
month = true;
}
Some(DateParts::YearMonthDay) | None => {
year = true;
month = true;
day = true;
}
}
} else {
year = self.date_part.iter().any(|i| i.name == DatePartName::Year);
month = self.date_part.iter().any(|i| i.name == DatePartName::Month);
day = self.date_part.iter().any(|i| i.name == DatePartName::Day);
};
if year {
write!(ctx, "{:04}", date.year).unwrap();
render_year_suffix_implicitly(ctx);
}
if month {
write!(
ctx,
"{:02}",
date.month.map(|m| m as i32 + 1).unwrap_or_default()
)
.unwrap();
}
if day {
write!(ctx, "{:02}", date.day.map(|d| d as i32 + 1).unwrap_or_default())
.unwrap();
}
return;
}
let base = if let Some(form) = self.form {
let Some(base) = ctx.localized_date(form) else { return };
Some(base)
} else {
None
};
let first = ctx.is_first_date();
let formatting = base
.map(|b| self.formatting.apply(b.formatting))
.unwrap_or(self.formatting);
let depth = ctx.push_elem(formatting);
let affix_loc = ctx.apply_prefix(&self.affixes);
let cidx = ctx.push_case(self.text_case.or(base.and_then(|b| b.text_case)));
let parts = self.parts.or(base.and_then(|b| b.parts)).unwrap_or_default();
let mut last_was_empty = true;
for part in &base.unwrap_or(self).date_part {
match part.name {
DatePartName::Month if !parts.has_month() => continue,
DatePartName::Day if !parts.has_day() => continue,
_ => {}
}
let cursor = ctx.writing.len();
if !last_was_empty {
if let Some(delim) = &self.delimiter {
ctx.push_str(delim);
}
}
let over_ride = base
.is_some()
.then(|| self.date_part.iter().find(|p| p.name == part.name))
.flatten();
render_date_part(part, &date, ctx, over_ride, first);
last_was_empty = cursor == ctx.writing.len();
}
ctx.pop_case(cidx);
ctx.apply_suffix(&self.affixes, affix_loc);
ctx.commit_elem(depth, self.display, Some(ElemMeta::Date));
}
fn will_render<T: EntryLike>(&self, ctx: &mut Context<T>, var: Variable) -> bool {
match ctx.instance.kind {
Some(SpecialForm::VarOnly(Variable::Date(d))) if self.variable != Some(d) => {
return false
}
Some(SpecialForm::OnlyFirstDate | SpecialForm::OnlyYearSuffix) => {
return ctx.peek_is_first_date()
}
Some(SpecialForm::VarOnly(_)) => return false,
_ => {}
}
Some(var) == self.variable.map(Variable::Date)
}
fn will_have_info<T: EntryLike>(&self, ctx: &mut Context<T>) -> (bool, UsageInfo) {
let suppressing = ctx.writing.suppress_queried_variables;
ctx.writing.stop_suppressing_queried_variables();
let Some(variable) = self.variable else {
return (false, UsageInfo { has_vars: true, ..Default::default() });
};
if suppressing {
ctx.writing.start_suppressing_queried_variables();
}
if !self.will_render(ctx, variable.into()) {
(false, UsageInfo::default())
} else {
let has_non_empty_vars = ctx.resolve_date_variable(variable).is_some();
(
has_non_empty_vars,
UsageInfo {
has_vars: true,
has_non_empty_vars,
..Default::default()
},
)
}
}
}
fn render_date_part<T: EntryLike>(
date_part: &citationberg::DatePart,
date: &Date,
ctx: &mut Context<T>,
over_ride: Option<&citationberg::DatePart>,
first: bool,
) {
let Some(val) = (match date_part.name {
DatePartName::Day => date.day.map(|i| i as i32 + 1),
DatePartName::Month => date.month.map(|i| i as i32 + 1),
DatePartName::Year => {
Some(if date.year > 0 { date.year } else { date.year.abs() + 1 })
}
}) else {
return;
};
let is_only_suffix = ctx.instance.kind == Some(SpecialForm::OnlyYearSuffix);
let form = over_ride
.map(citationberg::DatePart::form)
.unwrap_or_else(|| date_part.form());
let formatting = over_ride
.map(|p| p.formatting.apply(date_part.formatting))
.unwrap_or(date_part.formatting);
let idx = ctx.push_format(formatting);
let affixes = &date_part.affixes;
let affix_loc = (!is_only_suffix).then(|| ctx.apply_prefix(affixes));
if date_part.name == DatePartName::Month {
ctx.may_strip_periods(date_part.strip_periods);
}
let cidx = ctx.push_case(over_ride.and_then(|o| o.text_case).or(date_part.text_case));
if !is_only_suffix {
match form {
DateStrongAnyForm::Day(DateDayForm::NumericLeadingZeros)
| DateStrongAnyForm::Month(DateMonthForm::NumericLeadingZeros) => {
write!(ctx, "{:02}", val).unwrap();
}
DateStrongAnyForm::Day(DateDayForm::Ordinal)
if val != 1
|| !ctx
.style
.lookup_locale(|l| {
Some(
l.style_options
.and_then(|o| o.limit_day_ordinals_to_day_1)
.unwrap_or_default(),
)
})
.unwrap_or_default() =>
{
let gender = date
.month
.and_then(OtherTerm::month)
.and_then(|m| ctx.gender(m.into()));
write!(
ctx,
"{}{}",
val,
ctx.ordinal_lookup().lookup(val, gender).unwrap_or_default()
)
.unwrap();
}
DateStrongAnyForm::Day(DateDayForm::Numeric | DateDayForm::Ordinal)
| DateStrongAnyForm::Month(DateMonthForm::Numeric) => {
write!(ctx, "{}", val).unwrap();
}
DateStrongAnyForm::Month(DateMonthForm::Long) => {
if let Some(month) = OtherTerm::month((val - 1) as u8)
.and_then(|m| ctx.term(m.into(), TermForm::Long, false))
{
ctx.push_str(month);
} else {
write!(ctx, "{}", val).unwrap();
}
}
DateStrongAnyForm::Month(DateMonthForm::Short) => {
if let Some(month) = OtherTerm::month((val - 1) as u8)
.and_then(|m| ctx.term(m.into(), TermForm::Short, false))
{
ctx.push_str(month);
} else {
write!(ctx, "{}", val).unwrap();
}
}
DateStrongAnyForm::Year(LongShortForm::Short) => {
write!(ctx, "{:02}", (val % 100).abs()).unwrap();
}
DateStrongAnyForm::Year(LongShortForm::Long) => {
write!(ctx, "{}", val.abs()).unwrap();
}
}
if let DateStrongAnyForm::Year(_) = form {
if date.year < 1000 {
ctx.push_str(if date.year < 0 { "BC" } else { "AD" });
}
}
}
if let DateStrongAnyForm::Year(_) = form {
if first {
render_year_suffix_implicitly(ctx);
}
}
if let Some(affix_loc) = affix_loc {
ctx.apply_suffix(affixes, affix_loc);
}
ctx.stop_stripping_periods();
ctx.pop_case(cidx);
ctx.pop_format(idx);
}
fn render_year_suffix_implicitly<T: EntryLike>(ctx: &mut Context<T>) {
if ctx.renders_year_suffix_implicitly() {
if let Some(year_suffix) = ctx.resolve_standard_variable(
LongShortForm::default(),
StandardVariable::YearSuffix,
) {
ctx.push_chunked(year_suffix.as_ref());
}
}
}
fn choose_children<F, R, T: EntryLike>(
choose: &citationberg::Choose,
ctx: &mut Context<T>,
mut f: F,
) -> Option<R>
where
F: FnMut(&[LayoutRenderingElement], &mut Context<T>) -> R,
{
let supressed = ctx.writing.suppress_queried_variables;
ctx.writing.stop_suppressing_queried_variables();
let branch = choose
.branches()
.find(|branch| branch.match_.test(BranchConditionIter::from_branch(branch, ctx)));
ctx.writing.suppress_queried_variables = supressed;
branch
.map(|b| b.children.as_slice())
.or_else(|| choose.otherwise.as_ref().map(|f| f.children.as_slice()))
.map(|children| f(children, ctx))
}
impl RenderCsl for citationberg::Choose {
fn render<T: EntryLike>(&self, ctx: &mut Context<T>) {
choose_children(self, ctx, |children, ctx| {
render_with_delimiter(children, self.delimiter.as_deref(), ctx);
});
}
fn will_render<T: EntryLike>(&self, ctx: &mut Context<T>, var: Variable) -> bool {
choose_children(self, ctx, |children, ctx| {
children.iter().any(|c| c.will_render(ctx, var))
})
.unwrap_or_default()
}
fn will_have_info<T: EntryLike>(&self, ctx: &mut Context<T>) -> (bool, UsageInfo) {
choose_children(self, ctx, |children, ctx| {
let mut info = UsageInfo::default();
let mut will_print = false;
for child in children {
let (print, child_info) = child.will_have_info(ctx);
will_print |= print;
info = info.merge_child(child_info);
}
(will_print, info)
})
.unwrap_or_else(|| (false, UsageInfo::default()))
}
}
fn render_with_delimiter<T: EntryLike>(
children: &[LayoutRenderingElement],
delimiter: Option<&str>,
ctx: &mut Context<T>,
) {
let mut first = true;
let mut loc = None;
for child in children {
if !child.will_have_info(ctx).0 {
continue;
}
if !first {
if let Some(delim) = delimiter {
let prev_loc = std::mem::take(&mut loc);
if let Some(prev_loc) = prev_loc {
ctx.commit_elem(prev_loc, None, None);
}
loc = Some(ctx.push_elem(citationberg::Formatting::default()));
ctx.push_str(delim);
}
}
let pos = ctx.push_elem(citationberg::Formatting::default());
match child {
LayoutRenderingElement::Text(text) => text.render(ctx),
LayoutRenderingElement::Number(num) => num.render(ctx),
LayoutRenderingElement::Label(label) => label.render(ctx),
LayoutRenderingElement::Date(date) => date.render(ctx),
LayoutRenderingElement::Names(names) => names.render(ctx),
LayoutRenderingElement::Choose(choose) => choose.render(ctx),
LayoutRenderingElement::Group(_group) => _group.render(ctx),
}
first = false;
ctx.commit_elem(pos, None, None);
}
if let Some(loc) = loc {
ctx.commit_elem(loc, None, None);
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum BranchConditionPos {
Disambiguate,
IsNumeric,
IsUncertainDate,
Locator,
Position,
Type,
Variable,
}
impl Iterator for BranchConditionPos {
type Item = Self;
fn next(&mut self) -> Option<Self::Item> {
match self {
Self::Disambiguate => {
*self = Self::IsNumeric;
Some(Self::Disambiguate)
}
Self::IsNumeric => {
*self = Self::IsUncertainDate;
Some(Self::IsNumeric)
}
Self::IsUncertainDate => {
*self = Self::Locator;
Some(Self::IsUncertainDate)
}
Self::Locator => {
*self = Self::Position;
Some(Self::Locator)
}
Self::Position => {
*self = Self::Type;
Some(Self::Position)
}
Self::Type => {
*self = Self::Variable;
Some(Self::Type)
}
Self::Variable => None,
}
}
}
struct BranchConditionIter<'a, 'b, T: EntryLike> {
cond: &'a ChooseBranch,
ctx: &'a mut Context<'b, T>,
pos: BranchConditionPos,
idx: usize,
}
impl<'a, 'b, T: EntryLike> BranchConditionIter<'a, 'b, T> {
fn from_branch(cond: &'a ChooseBranch, ctx: &'a mut Context<'b, T>) -> Self {
Self {
cond,
ctx,
pos: BranchConditionPos::Disambiguate,
idx: 0,
}
}
fn next_case(&mut self) {
self.pos.next();
self.idx = 0;
}
}
impl<'a, 'b, T: EntryLike> Iterator for BranchConditionIter<'a, 'b, T> {
type Item = bool;
fn next(&mut self) -> Option<Self::Item> {
match self.pos {
BranchConditionPos::Disambiguate => {
self.pos.next();
if let Some(d) = self.cond.disambiguate {
Some(d == self.ctx.should_disambiguate())
} else {
self.next()
}
}
BranchConditionPos::IsNumeric => {
if let Some(vars) = &self.cond.is_numeric {
if self.idx >= vars.len() {
self.next_case();
return self.next();
}
let var = vars[self.idx];
self.idx += 1;
Some(match var {
Variable::Standard(var) => self
.ctx
.resolve_standard_variable(LongShortForm::default(), var)
.map(|v| Numeric::from_str(&v.to_string()).is_ok())
.unwrap_or_default(),
Variable::Number(var) => matches!(
self.ctx.resolve_number_variable(var),
Some(NumberVariableResult::Regular(MaybeTyped::Typed(_)))
),
_ => false,
})
} else {
self.next_case();
self.next()
}
}
BranchConditionPos::IsUncertainDate => {
if let Some(vars) = &self.cond.is_uncertain_date {
if self.idx >= vars.len() {
self.next_case();
return self.next();
}
let var = vars[self.idx];
self.idx += 1;
Some(
self.ctx
.resolve_date_variable(var)
.map_or(false, |d| d.approximate),
)
} else {
self.next_case();
self.next()
}
}
BranchConditionPos::Locator => {
if let Some(locs) = &self.cond.locator {
if self.idx >= locs.len() {
self.next_case();
return self.next();
}
let loc = locs[self.idx];
self.idx += 1;
Some(
self.ctx
.instance
.cite_props
.speculative
.locator
.map(|l| l.0)
.map_or(false, |l| l == loc),
)
} else {
self.next_case();
self.next()
}
}
BranchConditionPos::Position => {
if let Some(pos) = &self.cond.position {
if self.idx >= pos.len() {
self.next_case();
return self.next();
}
let spec_pos = pos[self.idx];
self.idx += 1;
let props = &self.ctx.instance.cite_props;
Some(match spec_pos {
TestPosition::First => props.certain.is_first,
TestPosition::Subsequent => !props.certain.is_first,
TestPosition::Ibid => props.speculative.ibid == IbidState::Ibid,
TestPosition::IbidWithLocator => {
props.speculative.ibid.is_ibid_with_locator()
}
TestPosition::NearNote => props.certain.is_near_note,
})
} else {
self.next_case();
self.next()
}
}
BranchConditionPos::Type => {
if let Some(kind) = &self.cond.type_ {
if self.idx >= kind.len() {
self.next_case();
return self.next();
}
let kind = kind[self.idx];
self.idx += 1;
Some(self.ctx.instance.entry.matches_entry_type(kind))
} else {
self.next_case();
self.next()
}
}
BranchConditionPos::Variable => {
if let Some(vars) = &self.cond.variable {
if self.idx >= vars.len() {
return None;
}
let var = vars[self.idx];
self.idx += 1;
Some(match var {
Variable::Standard(s) => {
let val = self
.ctx
.resolve_standard_variable(LongShortForm::default(), s);
val.map_or(false, |s| {
!s.to_string().chars().all(char::is_whitespace)
})
}
Variable::Number(n) => {
let val = self.ctx.resolve_number_variable(n);
val.is_some()
}
Variable::Date(d) => self.ctx.resolve_date_variable(d).is_some(),
Variable::Name(n) => {
!self.ctx.resolve_name_variable(n).is_empty()
}
})
} else {
None
}
}
}
}
}
impl RenderCsl for citationberg::Group {
fn render<T: EntryLike>(&self, ctx: &mut Context<T>) {
let idx = ctx.push_elem(self.to_formatting());
let affixes = self.to_affixes();
let affix_loc = ctx.apply_prefix(&affixes);
let info = self.will_have_info(ctx).1;
render_with_delimiter(&self.children, self.delimiter.as_deref(), ctx);
ctx.apply_suffix(&affixes, affix_loc);
if info.should_render_group() {
ctx.commit_elem(idx, self.display, None);
} else {
ctx.discard_elem(idx);
}
}
fn will_render<T: EntryLike>(&self, ctx: &mut Context<T>, var: Variable) -> bool {
self.children.iter().any(|e| e.will_render(ctx, var))
}
fn will_have_info<T: EntryLike>(&self, ctx: &mut Context<T>) -> (bool, UsageInfo) {
let mut info = UsageInfo::default();
let mut will_print = false;
for child in &self.children {
let (print, child_info) = child.will_have_info(ctx);
will_print |= print;
info = info.merge_child(child_info);
match child {
citationberg::LayoutRenderingElement::Group(_) if print => {
info.has_non_empty_group = true;
}
_ => {}
}
}
will_print &= info.should_render_group();
(will_print, info)
}
}
impl RenderCsl for citationberg::LayoutRenderingElement {
fn render<T: EntryLike>(&self, ctx: &mut Context<T>) {
match self {
citationberg::LayoutRenderingElement::Text(text) => text.render(ctx),
citationberg::LayoutRenderingElement::Number(num) => num.render(ctx),
citationberg::LayoutRenderingElement::Label(label) => label.render(ctx),
citationberg::LayoutRenderingElement::Date(date) => date.render(ctx),
citationberg::LayoutRenderingElement::Names(names) => names.render(ctx),
citationberg::LayoutRenderingElement::Choose(choose) => choose.render(ctx),
citationberg::LayoutRenderingElement::Group(group) => group.render(ctx),
}
}
fn will_render<T: EntryLike>(&self, ctx: &mut Context<T>, var: Variable) -> bool {
match self {
citationberg::LayoutRenderingElement::Text(text) => {
text.will_render(ctx, var)
}
citationberg::LayoutRenderingElement::Number(num) => {
num.will_render(ctx, var)
}
citationberg::LayoutRenderingElement::Label(label) => {
label.will_render(ctx, var)
}
citationberg::LayoutRenderingElement::Date(date) => {
date.will_render(ctx, var)
}
citationberg::LayoutRenderingElement::Names(names) => {
names.will_render(ctx, var)
}
citationberg::LayoutRenderingElement::Choose(choose) => {
choose.will_render(ctx, var)
}
citationberg::LayoutRenderingElement::Group(group) => {
group.will_render(ctx, var)
}
}
}
fn will_have_info<T: EntryLike>(&self, ctx: &mut Context<T>) -> (bool, UsageInfo) {
match self {
citationberg::LayoutRenderingElement::Text(text) => text.will_have_info(ctx),
citationberg::LayoutRenderingElement::Number(num) => num.will_have_info(ctx),
citationberg::LayoutRenderingElement::Label(label) => {
label.will_have_info(ctx)
}
citationberg::LayoutRenderingElement::Date(date) => date.will_have_info(ctx),
citationberg::LayoutRenderingElement::Names(names) => {
names.will_have_info(ctx)
}
citationberg::LayoutRenderingElement::Choose(choose) => {
choose.will_have_info(ctx)
}
citationberg::LayoutRenderingElement::Group(group) => {
group.will_have_info(ctx)
}
}
}
}
impl RenderCsl for citationberg::Layout {
fn render<T: EntryLike>(&self, ctx: &mut Context<T>) {
let fidx = ctx.push_format(self.to_formatting());
for e in &self.elements {
e.render(ctx);
}
ctx.pop_format(fidx);
}
fn will_render<T: EntryLike>(&self, ctx: &mut Context<T>, var: Variable) -> bool {
self.elements.iter().any(|e| e.will_render(ctx, var))
}
fn will_have_info<T: EntryLike>(&self, ctx: &mut Context<T>) -> (bool, UsageInfo) {
let mut info = UsageInfo::default();
let mut will_print = false;
for child in &self.elements {
let (print, child_info) = child.will_have_info(ctx);
will_print |= print;
info = info.merge_child(child_info);
}
(will_print, info)
}
}
impl RenderCsl for citationberg::RenderingElement {
fn render<T: EntryLike>(&self, ctx: &mut Context<T>) {
match self {
citationberg::RenderingElement::Layout(l) => l.render(ctx),
citationberg::RenderingElement::Other(o) => o.render(ctx),
}
}
fn will_render<T: EntryLike>(&self, ctx: &mut Context<T>, var: Variable) -> bool {
match self {
citationberg::RenderingElement::Layout(l) => l.will_render(ctx, var),
citationberg::RenderingElement::Other(o) => o.will_render(ctx, var),
}
}
fn will_have_info<T: EntryLike>(&self, ctx: &mut Context<T>) -> (bool, UsageInfo) {
match self {
citationberg::RenderingElement::Layout(l) => l.will_have_info(ctx),
citationberg::RenderingElement::Other(o) => o.will_have_info(ctx),
}
}
}
impl From<TextCase> for Case {
fn from(case: TextCase) -> Self {
match case {
TextCase::Uppercase => Case::Uppercase,
TextCase::Lowercase => Case::Lowercase,
TextCase::TitleCase => Case::Title(TitleCase::default()),
TextCase::SentenceCase => Case::Sentence(SentenceCase::default()),
TextCase::CapitalizeFirst => Case::FirstUpper,
TextCase::CapitalizeAll => Case::AllUpper,
}
}
}