mod labels;
pub mod names;
mod substitute;
use crate::reference::Reference;
use crate::values::{ComponentValues, ProcHints, ProcValues, RenderContext, RenderOptions};
use citum_schema::options::SubsequentNameForm;
use citum_schema::template::{ContributorForm, ContributorRole, TemplateContributor};
#[cfg(test)]
pub(crate) use names::{NameFormatContext, format_single_name};
pub use names::{NamesOverrides, format_contributors_short, format_names};
pub(super) fn contributor_for_role(
reference: &Reference,
role: &ContributorRole,
) -> Option<citum_schema::reference::Contributor> {
match role {
ContributorRole::Author => reference.author(),
ContributorRole::Editor => reference.editor(),
ContributorRole::Translator => reference.translator(),
_ => contributor_role_to_reference_role(role).and_then(|role| reference.contributor(role)),
}
}
pub(super) fn contributor_role_to_reference_role(
role: &ContributorRole,
) -> Option<citum_schema::reference::ContributorRole> {
match role {
ContributorRole::Author => Some(citum_schema::reference::ContributorRole::Author),
ContributorRole::Editor => Some(citum_schema::reference::ContributorRole::Editor),
ContributorRole::Translator => Some(citum_schema::reference::ContributorRole::Translator),
ContributorRole::Recipient => Some(citum_schema::reference::ContributorRole::Recipient),
ContributorRole::Chair => Some(citum_schema::reference::ContributorRole::Unknown(
"chair".to_string(),
)),
ContributorRole::Interviewer => Some(citum_schema::reference::ContributorRole::Interviewer),
ContributorRole::Guest => Some(citum_schema::reference::ContributorRole::Guest),
ContributorRole::Director => Some(citum_schema::reference::ContributorRole::Director),
ContributorRole::Composer => Some(citum_schema::reference::ContributorRole::Composer),
ContributorRole::Illustrator => Some(citum_schema::reference::ContributorRole::Illustrator),
ContributorRole::Inventor => Some(citum_schema::reference::ContributorRole::Unknown(
"inventor".to_string(),
)),
ContributorRole::Counsel => Some(citum_schema::reference::ContributorRole::Unknown(
"counsel".to_string(),
)),
ContributorRole::CollectionEditor => Some(
citum_schema::reference::ContributorRole::Unknown("collection-editor".to_string()),
),
ContributorRole::ContainerAuthor => Some(
citum_schema::reference::ContributorRole::Unknown("container-author".to_string()),
),
ContributorRole::EditorialDirector => Some(
citum_schema::reference::ContributorRole::Unknown("editorial-director".to_string()),
),
ContributorRole::TextualEditor => Some(citum_schema::reference::ContributorRole::Unknown(
"textual-editor".to_string(),
)),
ContributorRole::OriginalAuthor => Some(citum_schema::reference::ContributorRole::Unknown(
"original-author".to_string(),
)),
ContributorRole::ReviewedAuthor => Some(citum_schema::reference::ContributorRole::Unknown(
"reviewed-author".to_string(),
)),
ContributorRole::Interviewee | ContributorRole::Publisher => None,
_ => None,
}
}
pub(super) fn is_role_label_omitted(options: &RenderOptions<'_>, role: &ContributorRole) -> bool {
options
.config
.contributors
.as_ref()
.and_then(|c| c.role.as_ref())
.is_some_and(|role_opts| {
role_opts
.omit
.iter()
.any(|entry| entry.eq_ignore_ascii_case(role.as_str()))
})
}
pub(super) fn format_role_term<F: crate::render::format::OutputFormat<Output = String>>(
term: &str,
fmt: &F,
effective_rendering: &citum_schema::template::Rendering,
options: &RenderOptions<'_>,
prefix: &str,
suffix: &str,
) -> String {
let term_str = if crate::values::should_strip_periods(effective_rendering, options) {
crate::values::strip_trailing_periods(term)
} else {
term.to_string()
};
fmt.text(&format!("{prefix}{term_str}{suffix}"))
}
fn apply_integral_subsequent_form(
component: &mut TemplateContributor,
hints: &ProcHints,
options: &RenderOptions<'_>,
) {
if options.context != RenderContext::Citation {
return;
}
if !matches!(options.mode, citum_schema::citation::CitationMode::Integral) {
return;
}
if !matches!(component.contributor, ContributorRole::Author) {
return;
}
if !matches!(
hints.integral_name_state,
Some(citum_schema::citation::IntegralNameState::Subsequent)
) {
return;
}
let Some(memory) = options.config.integral_name_memory.as_ref() else {
return;
};
component.form = match memory.resolve().subsequent_form {
SubsequentNameForm::Short => ContributorForm::Short,
SubsequentNameForm::FamilyOnly => ContributorForm::FamilyOnly,
};
}
fn format_contributor_names(
component: &TemplateContributor,
names_vec: &[crate::reference::FlatName],
effective_rendering: &citum_schema::template::Rendering,
options: &RenderOptions<'_>,
hints: &ProcHints,
) -> String {
let effective_name_order = component.name_order.as_ref().or_else(|| {
options
.config
.contributors
.as_ref()?
.effective_role_name_order(&component.contributor)
});
let effective_shorten = component
.shorten
.as_ref()
.or_else(|| options.config.contributors.as_ref()?.shorten.as_ref());
let effective_name_form = component.name_form.or(effective_rendering.name_form);
let name_overrides = names::NamesOverrides {
name_order: effective_name_order,
sort_separator: component.sort_separator.as_ref(),
shorten: effective_shorten,
and: component.and.as_ref(),
initialize_with: effective_rendering.initialize_with.as_ref(),
name_form: effective_name_form,
};
names::format_names(names_vec, &component.form, options, &name_overrides, hints)
}
impl ComponentValues for TemplateContributor {
#[allow(
clippy::too_many_lines,
reason = "large match statement for contributor role dispatch"
)]
fn values<F: crate::render::format::OutputFormat<Output = String>>(
&self,
reference: &Reference,
hints: &ProcHints,
options: &RenderOptions<'_>,
) -> Option<ProcValues<F::Output>> {
let fmt = F::default();
let mut component = self.clone();
let effective_rendering = self.rendering.clone();
apply_integral_subsequent_form(&mut component, hints, options);
if effective_rendering.suppress == Some(true) {
return None;
}
let contributor = match &component.contributor {
ContributorRole::Author => {
if options.suppress_author {
None
} else {
contributor_for_role(reference, &component.contributor)
}
}
_ => contributor_for_role(reference, &component.contributor),
};
let default_substitute = citum_schema::options::SubstituteConfig::default();
let substitute_config = options
.config
.substitute
.as_ref()
.unwrap_or(&default_substitute);
let substitute = substitute_config.resolve();
if substitute::is_role_suppressed_by_substitute(
&component.contributor,
&substitute,
reference,
) {
return None;
}
let names_vec = if let Some(contrib) = contributor {
substitute::resolve_multilingual_for_contrib(&contrib, options)
} else {
Vec::new()
};
if names_vec.is_empty()
&& matches!(component.contributor, ContributorRole::Author)
&& options.suppress_author
{
return None;
}
if names_vec.is_empty() && matches!(component.contributor, ContributorRole::Author) {
return substitute::resolve_author_substitute::<F>(
&component,
hints,
options,
reference,
&effective_rendering,
&fmt,
&substitute,
);
}
if names_vec.is_empty() {
return substitute::resolve_role_substitute::<F>(
&component.contributor,
&component,
hints,
options,
reference,
&effective_rendering,
&fmt,
&substitute,
);
}
let formatted =
format_contributor_names(&component, &names_vec, &effective_rendering, options, hints);
let role_omitted = is_role_label_omitted(options, &component.contributor);
let (role_prefix, role_suffix) = labels::resolve_role_labels::<F>(
&component,
reference,
names_vec.len(),
&effective_rendering,
options,
&fmt,
role_omitted,
);
let is_pre_formatted = role_prefix.is_some() || role_suffix.is_some();
let formatted = crate::values::apply_abbreviation(formatted, options.abbreviation_map);
let final_value = if is_pre_formatted {
fmt.text(&formatted)
} else {
formatted
};
Some(ProcValues {
value: final_value,
prefix: role_prefix,
suffix: role_suffix,
url: crate::values::resolve_effective_url(
component.links.as_ref(),
options.config.links.as_ref(),
reference,
citum_schema::options::LinkAnchor::Component,
),
substituted_key: None,
pre_formatted: is_pre_formatted,
})
}
}