pub mod contributor;
pub mod date;
pub mod list;
pub mod locator;
pub mod number;
pub mod range;
pub mod term;
pub mod text_case;
pub mod title;
pub mod variable;
#[cfg(test)]
#[allow(
clippy::unwrap_used,
clippy::expect_used,
clippy::panic,
clippy::indexing_slicing,
clippy::todo,
clippy::unimplemented,
clippy::unreachable,
clippy::get_unwrap,
reason = "Panicking is acceptable and often desired in tests."
)]
mod tests;
use crate::reference::Reference;
use citum_schema::locale::Locale;
use citum_schema::options::{Config, bibliography::BibliographyConfig};
use citum_schema::reference::types::Title;
use citum_schema::template::{TemplateComponent, TitleType};
pub use contributor::format_contributors_short;
pub use date::int_to_letter;
fn resolve_transliteration<'a>(
transliterations: &'a std::collections::HashMap<String, String>,
preferred_transliteration: Option<&[String]>,
preferred_script: Option<&String>,
) -> Option<&'a str> {
if let Some(tags) = preferred_transliteration {
for tag in tags {
if let Some(v) = transliterations.get(tag) {
return Some(v.as_str());
}
}
for tag in tags {
for (k, v) in transliterations {
if k.contains(tag.as_str()) {
return Some(v.as_str());
}
}
}
}
if let Some(script) = preferred_script {
if let Some(v) = transliterations.get(script) {
return Some(v.as_str());
}
for (k, v) in transliterations {
if k.contains(script.as_str()) {
return Some(v.as_str());
}
}
}
None
}
fn resolve_translation<'a>(
translations: &'a std::collections::HashMap<citum_schema::reference::LangID, String>,
style_locale: &str,
) -> Option<&'a str> {
translations
.get(style_locale)
.or_else(|| {
style_locale
.split(['-', '_'])
.next()
.and_then(|base| translations.get(base))
})
.map(String::as_str)
}
#[must_use]
pub fn resolve_multilingual_string(
string: &citum_schema::reference::types::MultilingualString,
mode: Option<&citum_schema::options::MultilingualMode>,
preferred_transliteration: Option<&[String]>,
preferred_script: Option<&String>,
style_locale: &str,
) -> String {
use citum_schema::options::MultilingualMode;
use citum_schema::reference::types::MultilingualString;
match string {
MultilingualString::Simple(s) => s.clone(),
MultilingualString::Complex(complex) => {
let mode = mode.unwrap_or(&MultilingualMode::Primary);
match mode {
MultilingualMode::Primary => complex.original.clone(),
MultilingualMode::Transliterated => {
if let Some(trans) = resolve_transliteration(
&complex.transliterations,
preferred_transliteration,
preferred_script,
) {
return trans.to_string();
}
complex
.transliterations
.values()
.next()
.cloned()
.unwrap_or_else(|| complex.original.clone())
}
MultilingualMode::Translated => {
resolve_translation(&complex.translations, style_locale)
.map(ToString::to_string)
.unwrap_or_else(|| complex.original.clone())
}
MultilingualMode::Combined => {
let trans = resolve_transliteration(
&complex.transliterations,
preferred_transliteration,
preferred_script,
);
let translation = resolve_translation(&complex.translations, style_locale);
match (trans, translation) {
(Some(t), Some(tr)) => format!("{t} [{tr}]"),
(Some(t), None) => t.to_string(),
(None, Some(tr)) => format!("{} [{}]", complex.original, tr),
(None, None) => complex.original.clone(),
}
}
MultilingualMode::Pattern(segments) => resolve_multilingual_pattern(
segments,
&complex.original,
&complex.transliterations,
&complex.translations,
preferred_transliteration,
preferred_script,
style_locale,
),
}
}
}
}
fn resolve_multilingual_pattern(
segments: &[citum_schema::options::MultilingualSegment],
original: &str,
transliterations: &std::collections::HashMap<String, String>,
translations: &std::collections::HashMap<citum_schema::reference::types::LangID, String>,
preferred_transliteration: Option<&[String]>,
preferred_script: Option<&String>,
style_locale: &str,
) -> String {
use citum_schema::options::{MultilingualView, SegmentWrap};
let mut parts: Vec<String> = Vec::with_capacity(segments.len());
let mut last_text: Option<String> = None;
for seg in segments {
let text: Option<String> = match &seg.view {
MultilingualView::Original => Some(original.to_string()),
MultilingualView::Transliterated => resolve_transliteration(
transliterations,
preferred_transliteration,
preferred_script,
)
.map(ToString::to_string),
MultilingualView::Translated => {
resolve_translation(translations, style_locale).map(ToString::to_string)
}
};
let Some(text) = text else { continue };
if text.is_empty() {
continue;
}
if last_text.as_deref() == Some(text.as_str()) {
continue;
}
let wrapped = match &seg.wrap {
SegmentWrap::None => text.clone(),
other => other.apply(&text),
};
last_text = Some(text);
parts.push(wrapped);
}
parts.join(" ")
}
#[must_use]
pub fn effective_field_language(
reference: &Reference,
scope: &str,
title: Option<&Title>,
) -> Option<String> {
reference
.field_languages()
.get(scope)
.map(ToString::to_string)
.or_else(|| match title {
Some(Title::Multilingual(multilingual)) => {
multilingual.lang.as_ref().map(ToString::to_string)
}
_ => None,
})
.or_else(|| reference.language().map(|lang| lang.to_string()))
}
#[must_use]
pub fn effective_item_language(reference: &Reference) -> Option<String> {
effective_field_language(reference, "title", reference.title().as_ref())
}
#[must_use]
pub fn effective_component_language(
reference: &Reference,
component: &TemplateComponent,
) -> Option<String> {
match component {
TemplateComponent::Title(title_component) => {
let title = match title_component.title {
TitleType::Primary => reference.title(),
TitleType::ParentMonograph => reference.container_title(),
TitleType::ParentSerial => reference.container_title(),
_ => reference.title(),
};
let scope = match title_component.title {
TitleType::Primary => "title",
TitleType::ParentMonograph => "parent-monograph.title",
TitleType::ParentSerial => "parent-serial.title",
_ => "title",
};
effective_field_language(reference, scope, title.as_ref())
}
_ => effective_item_language(reference),
}
}
fn select_by_transliteration<'a>(
m: &'a citum_schema::reference::contributor::MultilingualName,
preferred_transliteration: Option<&[String]>,
preferred_script: Option<&String>,
) -> &'a citum_schema::reference::contributor::StructuredName {
if let Some(tags) = preferred_transliteration {
for tag in tags {
if let Some(name) = m.transliterations.get(tag) {
return name;
}
}
for tag in tags {
if let Some((_, name)) = m
.transliterations
.iter()
.find(|(k, _)| k.contains(tag.as_str()))
{
return name;
}
}
}
if let Some(script) = preferred_script {
if let Some(name) = m.transliterations.get(script) {
return name;
}
if let Some((_, name)) = m
.transliterations
.iter()
.find(|(tag, _)| tag.contains(script))
{
return name;
}
}
m.transliterations.values().next().unwrap_or(&m.original)
}
#[must_use]
pub fn resolve_multilingual_name(
contributor: &citum_schema::reference::contributor::Contributor,
mode: Option<&citum_schema::options::MultilingualMode>,
preferred_transliteration: Option<&[String]>,
preferred_script: Option<&String>,
style_locale: &str,
) -> Vec<crate::reference::FlatName> {
use citum_schema::options::MultilingualMode;
use citum_schema::reference::contributor::Contributor;
match contributor {
Contributor::SimpleName(_) | Contributor::StructuredName(_) => contributor.to_names_vec(),
Contributor::Multilingual(m) => {
let mode = mode.unwrap_or(&MultilingualMode::Primary);
let selected_name = match mode {
MultilingualMode::Primary => &m.original,
MultilingualMode::Transliterated => {
select_by_transliteration(m, preferred_transliteration, preferred_script)
}
MultilingualMode::Translated => {
m.translations.get(style_locale).unwrap_or(&m.original)
}
MultilingualMode::Combined => {
select_by_transliteration(m, preferred_transliteration, preferred_script)
}
MultilingualMode::Pattern(_) => {
select_by_transliteration(m, preferred_transliteration, preferred_script)
}
};
vec![crate::reference::FlatName {
given: Some(selected_name.given.to_string()),
family: Some(selected_name.family.to_string()),
suffix: selected_name.suffix.clone(),
dropping_particle: selected_name.dropping_particle.clone(),
non_dropping_particle: selected_name.non_dropping_particle.clone(),
literal: None,
short_name: None,
}]
}
Contributor::ContributorList(l) => {
l.0.iter()
.flat_map(|c| {
resolve_multilingual_name(
c,
mode,
preferred_transliteration,
preferred_script,
style_locale,
)
})
.collect()
}
}
}
#[must_use]
pub fn resolve_url(
links: &citum_schema::options::LinksConfig,
reference: &Reference,
) -> Option<String> {
use citum_schema::options::LinkTarget;
let target = links.target.as_ref().unwrap_or(&LinkTarget::UrlOrDoi);
match target {
LinkTarget::Url => reference.url().map(|u| u.to_string()),
LinkTarget::Doi => reference.doi().map(|d| format!("https://doi.org/{d}")),
LinkTarget::UrlOrDoi => reference
.url()
.map(|u| u.to_string())
.or_else(|| reference.doi().map(|d| format!("https://doi.org/{d}"))),
LinkTarget::Pubmed => reference
.id()
.filter(|id| id.starts_with("pmid:"))
.map(|id| {
#[allow(clippy::string_slice, reason = "known ASCII prefix")]
let result = format!("https://pubmed.ncbi.nlm.nih.gov/{}/", &id[5..]);
result
}),
LinkTarget::Pmcid => reference
.id()
.filter(|id| id.starts_with("pmc:"))
.map(|id| {
#[allow(clippy::string_slice, reason = "known ASCII prefix")]
let result = format!("https://www.ncbi.nlm.nih.gov/pmc/articles/{}/", &id[4..]);
result
}),
}
}
#[must_use]
pub fn resolve_effective_url(
local_links: Option<&citum_schema::options::LinksConfig>,
global_links: Option<&citum_schema::options::LinksConfig>,
reference: &Reference,
component_anchor: citum_schema::options::LinkAnchor,
) -> Option<String> {
use citum_schema::options::LinkAnchor;
if let Some(links) = local_links {
let anchor = links.anchor.as_ref().unwrap_or(&LinkAnchor::Component);
if matches!(anchor, LinkAnchor::Component) || *anchor == component_anchor {
return resolve_url(links, reference);
}
}
if let Some(links) = global_links
&& let Some(anchor) = &links.anchor
&& *anchor == component_anchor
{
return resolve_url(links, reference);
}
None
}
#[derive(Debug, Clone, Default)]
pub struct ProcValues<T = String> {
pub value: T,
pub prefix: Option<String>,
pub suffix: Option<String>,
pub url: Option<String>,
pub substituted_key: Option<String>,
pub pre_formatted: bool,
}
#[derive(Debug, Clone, Default)]
pub struct ProcHints {
pub disamb_condition: bool,
pub group_index: usize,
pub group_length: usize,
pub group_key: String,
pub expand_given_names: bool,
pub expand_given_names_primary_only: bool,
pub min_names_to_show: Option<usize>,
pub citation_number: Option<usize>,
pub citation_sub_label: Option<String>,
pub position: Option<citum_schema::citation::Position>,
pub integral_name_state: Option<citum_schema::citation::IntegralNameState>,
pub org_abbreviation_state: Option<citum_schema::citation::IntegralNameState>,
pub first_reference_note_number: Option<u32>,
pub suppress_disambiguation_title: bool,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum RenderContext {
#[default]
Citation,
Bibliography,
}
#[derive(Clone)]
pub struct RenderOptions<'a> {
pub config: &'a Config,
pub bibliography_config: Option<BibliographyConfig>,
pub locale: &'a Locale,
pub context: RenderContext,
pub mode: citum_schema::citation::CitationMode,
pub suppress_author: bool,
pub locator_raw: Option<&'a citum_schema::citation::CitationLocator>,
pub ref_type: Option<String>,
pub show_semantics: bool,
pub current_template_index: Option<usize>,
pub abbreviation_map: Option<&'a crate::api::AbbreviationMap>,
}
pub trait ComponentValues {
fn values<F: crate::render::format::OutputFormat<Output = String>>(
&self,
reference: &Reference,
hints: &ProcHints,
options: &RenderOptions<'_>,
) -> Option<ProcValues<F::Output>>;
}
impl ComponentValues for TemplateComponent {
fn values<F: crate::render::format::OutputFormat<Output = String>>(
&self,
reference: &Reference,
hints: &ProcHints,
options: &RenderOptions<'_>,
) -> Option<ProcValues<F::Output>> {
match self {
TemplateComponent::Contributor(c) => c.values::<F>(reference, hints, options),
TemplateComponent::Date(d) => d.values::<F>(reference, hints, options),
TemplateComponent::Title(t) => t.values::<F>(reference, hints, options),
TemplateComponent::Number(n) => n.values::<F>(reference, hints, options),
TemplateComponent::Variable(v) => v.values::<F>(reference, hints, options),
TemplateComponent::Group(l) => l.values::<F>(reference, hints, options),
TemplateComponent::Term(t) => t.values::<F>(reference, hints, options),
_ => None,
}
}
}
#[must_use]
pub fn should_strip_periods(
rendering: &citum_schema::template::Rendering,
options: &RenderOptions<'_>,
) -> bool {
rendering
.strip_periods
.or(options.config.strip_periods)
.unwrap_or(false)
}
#[must_use]
pub fn strip_trailing_periods(s: &str) -> String {
s.trim_end_matches('.').to_string()
}
#[must_use]
pub fn apply_abbreviation(value: String, map: Option<&crate::api::AbbreviationMap>) -> String {
if let Some(abbr) = map.and_then(|m| m.0.get(&value)) {
return abbr.clone();
}
value
}