use crate::options::bibliography::SubsequentAuthorSubstituteRule;
use crate::template::{TemplateComponent, WrapConfig, WrapPunctuation};
use crate::{BibliographySpec, CitationSpec, Style, Template};
#[cfg(feature = "schema")]
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[cfg_attr(feature = "schema", derive(JsonSchema))]
#[serde(rename_all = "kebab-case")]
pub enum LabelWrap {
None,
Parentheses,
Brackets,
Superscript,
}
impl LabelWrap {
#[must_use]
pub fn as_wrap_config(self) -> Option<WrapConfig> {
match self {
LabelWrap::None => None,
LabelWrap::Parentheses => Some(WrapConfig::from(WrapPunctuation::Parentheses)),
LabelWrap::Brackets => Some(WrapConfig::from(WrapPunctuation::Brackets)),
LabelWrap::Superscript => None,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[cfg_attr(feature = "schema", derive(JsonSchema))]
#[serde(rename_all = "kebab-case")]
pub enum BibliographyLabelWrap {
None,
Parentheses,
Brackets,
}
impl BibliographyLabelWrap {
#[must_use]
pub fn as_wrap_config(self) -> Option<WrapConfig> {
match self {
BibliographyLabelWrap::None => None,
BibliographyLabelWrap::Parentheses => {
Some(WrapConfig::from(WrapPunctuation::Parentheses))
}
BibliographyLabelWrap::Brackets => Some(WrapConfig::from(WrapPunctuation::Brackets)),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[cfg_attr(feature = "schema", derive(JsonSchema))]
#[serde(rename_all = "kebab-case")]
pub enum CitationGroupDelimiter {
Comma,
Semicolon,
Space,
}
impl CitationGroupDelimiter {
fn as_str(self) -> &'static str {
match self {
CitationGroupDelimiter::Comma => ", ",
CitationGroupDelimiter::Semicolon => "; ",
CitationGroupDelimiter::Space => " ",
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[cfg_attr(feature = "schema", derive(JsonSchema))]
#[serde(rename_all = "kebab-case")]
pub enum BibliographyLabelMode {
None,
Numeric,
AuthorDate,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[cfg_attr(feature = "schema", derive(JsonSchema))]
#[serde(rename_all = "kebab-case")]
pub enum DatePosition {
AfterAuthor,
AfterTitle,
Terminal,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[cfg_attr(feature = "schema", derive(JsonSchema))]
#[serde(rename_all = "kebab-case")]
pub enum TitleTerminator {
Period,
Comma,
None,
}
impl TitleTerminator {
fn as_suffix(self) -> Option<&'static str> {
match self {
TitleTerminator::Period => Some("."),
TitleTerminator::Comma => Some(","),
TitleTerminator::None => None,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[cfg_attr(feature = "schema", derive(JsonSchema))]
#[serde(rename_all = "kebab-case")]
pub enum RepeatedAuthorRendering {
Full,
Dash,
DashWithSpace,
}
pub(crate) fn apply_scoped_style_options(style: &mut Style) {
if let Some(citation) = style.citation.as_mut() {
apply_citation_options_recursive(citation);
}
if let Some(bibliography) = style.bibliography.as_mut() {
apply_bibliography_options(bibliography);
}
}
fn apply_citation_options_recursive(citation: &mut CitationSpec) {
let options = citation.options.clone();
if let Some(options) = options {
if let Some(delimiter) = options.group_delimiter {
citation.multi_cite_delimiter = Some(delimiter.as_str().to_string());
}
if let Some(wrap) = options.label_wrap {
if citation.template.is_none() && citation.template_ref.is_some() {
citation.template = citation.resolve_template();
}
set_citation_wrap(citation, wrap);
}
}
for child in [
citation.integral.as_deref_mut(),
citation.non_integral.as_deref_mut(),
citation.subsequent.as_deref_mut(),
citation.ibid.as_deref_mut(),
]
.into_iter()
.flatten()
{
apply_citation_options_recursive(child);
}
}
fn apply_bibliography_options(bibliography: &mut BibliographySpec) {
let options = bibliography.options.clone();
let Some(options) = options else {
return;
};
let needs_template = options.label_mode.is_some()
|| options.label_wrap.is_some()
|| options.date_position.is_some()
|| options.title_terminator.is_some();
if needs_template && bibliography.template.is_none() && bibliography.template_ref.is_some() {
bibliography.template = bibliography.resolve_template();
}
if let Some(mode) = options.label_mode {
apply_bibliography_label_mode(bibliography, mode);
}
if let Some(wrap) = options.label_wrap {
apply_bibliography_label_wrap(bibliography, wrap);
}
if let Some(position) = options.date_position {
apply_date_position(bibliography, position);
}
if let Some(terminator) = options.title_terminator {
apply_title_terminator(bibliography, terminator);
}
if let Some(repeated) = options.repeated_author_rendering {
apply_repeated_author_rendering(bibliography, repeated);
}
}
fn set_citation_wrap(citation: &mut CitationSpec, wrap: LabelWrap) {
if wrap == LabelWrap::Superscript {
citation.wrap = None;
apply_citation_wrap_recursive(citation, wrap);
return;
}
citation.wrap = wrap.as_wrap_config();
apply_citation_wrap_recursive(citation, wrap);
}
fn apply_bibliography_label_mode(bibliography: &mut BibliographySpec, mode: BibliographyLabelMode) {
update_label_mode(bibliography.template.as_mut(), mode);
if let Some(variants) = bibliography.type_variants.as_mut() {
for variant in variants.values_mut() {
update_label_mode(variant.as_template_mut(), mode);
}
}
}
fn apply_bibliography_label_wrap(bibliography: &mut BibliographySpec, wrap: BibliographyLabelWrap) {
update_label_wrap(bibliography.template.as_mut(), wrap);
if let Some(variants) = bibliography.type_variants.as_mut() {
for variant in variants.values_mut() {
update_label_wrap(variant.as_template_mut(), wrap);
}
}
}
fn apply_date_position(bibliography: &mut BibliographySpec, position: DatePosition) {
reposition_date(bibliography.template.as_mut(), position);
if let Some(variants) = bibliography.type_variants.as_mut() {
for variant in variants.values_mut() {
reposition_date(variant.as_template_mut(), position);
}
}
}
fn apply_title_terminator(bibliography: &mut BibliographySpec, terminator: TitleTerminator) {
update_title_terminator(bibliography.template.as_mut(), terminator);
if let Some(variants) = bibliography.type_variants.as_mut() {
for variant in variants.values_mut() {
update_title_terminator(variant.as_template_mut(), terminator);
}
}
}
fn apply_repeated_author_rendering(
bibliography: &mut BibliographySpec,
repeated: RepeatedAuthorRendering,
) {
let options = bibliography.options.get_or_insert_with(Default::default);
match repeated {
RepeatedAuthorRendering::Full => {
options.subsequent_author_substitute = None;
options.subsequent_author_substitute_rule = None;
}
RepeatedAuthorRendering::Dash => {
options.subsequent_author_substitute = Some("———".to_string());
options.subsequent_author_substitute_rule =
Some(SubsequentAuthorSubstituteRule::CompleteAll);
}
RepeatedAuthorRendering::DashWithSpace => {
options.subsequent_author_substitute = Some("——— ".to_string());
options.subsequent_author_substitute_rule =
Some(SubsequentAuthorSubstituteRule::CompleteAll);
}
}
}
fn update_label_mode(template: Option<&mut Template>, mode: BibliographyLabelMode) {
let Some(template) = template else {
return;
};
match mode {
BibliographyLabelMode::None | BibliographyLabelMode::AuthorDate => {
template.retain(|component| {
!matches!(
component,
TemplateComponent::Number(number)
if matches!(
number.number,
crate::template::NumberVariable::CitationNumber
| crate::template::NumberVariable::CitationLabel
)
)
});
}
BibliographyLabelMode::Numeric => {
let has_label = template.iter().any(|component| {
matches!(
component,
TemplateComponent::Number(number)
if matches!(number.number, crate::template::NumberVariable::CitationNumber)
)
});
if !has_label {
template.insert(
0,
TemplateComponent::Number(crate::TemplateNumber {
number: crate::template::NumberVariable::CitationNumber,
..Default::default()
}),
);
}
}
}
}
trait LabelWrapConfig {
fn wrap_config(self) -> Option<WrapConfig>;
}
impl LabelWrapConfig for LabelWrap {
fn wrap_config(self) -> Option<WrapConfig> {
self.as_wrap_config()
}
}
impl LabelWrapConfig for BibliographyLabelWrap {
fn wrap_config(self) -> Option<WrapConfig> {
self.as_wrap_config()
}
}
fn update_label_wrap<W>(template: Option<&mut Template>, wrap: W)
where
W: Copy + LabelWrapConfig,
{
let Some(template) = template else {
return;
};
for component in template.iter_mut() {
if let TemplateComponent::Number(number) = component
&& matches!(
number.number,
crate::template::NumberVariable::CitationNumber
| crate::template::NumberVariable::CitationLabel
)
{
number.rendering.wrap = wrap.wrap_config();
}
}
}
fn apply_citation_superscript(template: Option<&mut Template>) {
let Some(template) = template else {
return;
};
for component in template.iter_mut() {
if let TemplateComponent::Number(number) = component
&& matches!(
number.number,
crate::template::NumberVariable::CitationNumber
| crate::template::NumberVariable::CitationLabel
)
{
number.rendering.vertical_align = Some(crate::VerticalAlign::Superscript);
number.rendering.wrap = None;
}
}
}
fn apply_citation_wrap_recursive(citation: &mut CitationSpec, wrap: LabelWrap) {
if wrap == LabelWrap::Superscript && citation.template.is_none() {
citation.template = citation.resolve_template();
}
if wrap == LabelWrap::Superscript {
apply_citation_superscript(citation.template.as_mut());
if let Some(variants) = citation.type_variants.as_mut() {
for variant in variants.values_mut() {
apply_citation_superscript(variant.as_template_mut());
}
}
} else {
update_label_wrap(citation.template.as_mut(), wrap);
if let Some(variants) = citation.type_variants.as_mut() {
for variant in variants.values_mut() {
update_label_wrap(variant.as_template_mut(), wrap);
}
}
}
for child in [
citation.integral.as_deref_mut(),
citation.non_integral.as_deref_mut(),
citation.subsequent.as_deref_mut(),
citation.ibid.as_deref_mut(),
]
.into_iter()
.flatten()
{
child.wrap = if wrap == LabelWrap::Superscript {
None
} else {
wrap.as_wrap_config()
};
apply_citation_wrap_recursive(child, wrap);
}
}
fn reposition_date(template: Option<&mut Template>, position: DatePosition) {
let Some(template) = template else {
return;
};
let Some(index) = template.iter().position(|component| {
matches!(
component,
TemplateComponent::Date(date) if date.date == crate::template::DateVariable::Issued
)
}) else {
return;
};
let date = template.remove(index);
let target = match position {
DatePosition::AfterAuthor => template
.iter()
.position(|component| matches!(component, TemplateComponent::Contributor(_)))
.map(|idx| idx + 1)
.unwrap_or(0),
DatePosition::AfterTitle => template
.iter()
.position(|component| matches!(component, TemplateComponent::Title(_)))
.map(|idx| idx + 1)
.unwrap_or(template.len()),
DatePosition::Terminal => template.len(),
};
template.insert(target, date);
}
fn update_title_terminator(template: Option<&mut Template>, terminator: TitleTerminator) {
let Some(template) = template else {
return;
};
for component in template.iter_mut() {
if let TemplateComponent::Title(title) = component
&& title.title == crate::template::TitleType::Primary
{
title.rendering.suffix = terminator.as_suffix().map(ToString::to_string);
}
}
}