use super::contributor_role_to_reference_role;
use crate::reference::Reference;
use crate::render::format::OutputFormat;
use crate::values::RenderOptions;
use citum_schema::locale::{GrammaticalGender, TermForm};
use citum_schema::options::RoleLabelPreset;
use citum_schema::reference::ContributorGender;
use citum_schema::template::{ContributorForm, ContributorRole, Rendering, TemplateContributor};
fn map_contributor_gender(gender: ContributorGender) -> GrammaticalGender {
match gender {
ContributorGender::Masculine => GrammaticalGender::Masculine,
ContributorGender::Feminine => GrammaticalGender::Feminine,
ContributorGender::Neuter => GrammaticalGender::Neuter,
ContributorGender::Common => GrammaticalGender::Common,
}
}
#[derive(Debug, Clone)]
pub(super) enum RoleGenderRequest {
Specific(GrammaticalGender),
Neutral,
}
#[derive(Debug, Clone, Default)]
pub(super) struct RoleLabelTermOptions {
pub gender: Option<RoleGenderRequest>,
pub text_case: Option<citum_schema::options::titles::TextCase>,
}
fn requested_role_gender(
component: &TemplateContributor,
reference: &Reference,
) -> Option<RoleGenderRequest> {
if let Some(gender) = &component.gender {
return Some(RoleGenderRequest::Specific(gender.clone()));
}
let data_role = contributor_role_to_reference_role(&component.contributor)?;
let entries = reference.contributor_entries(&data_role);
let mut genders = entries
.iter()
.filter_map(|entry| entry.gender.map(map_contributor_gender));
let first = genders.next()?;
if genders.all(|gender| gender == first) {
Some(RoleGenderRequest::Specific(first))
} else {
Some(RoleGenderRequest::Neutral)
}
}
fn resolve_role_term_by_request(
locale: &citum_schema::locale::Locale,
role: &ContributorRole,
plural: bool,
term_form: TermForm,
requested_gender: Option<RoleGenderRequest>,
) -> Option<String> {
match requested_gender {
Some(RoleGenderRequest::Specific(gender)) => {
locale.resolved_role_term(role, plural, &term_form, Some(gender))
}
Some(RoleGenderRequest::Neutral) => {
locale.resolved_role_term_neutral(role, plural, &term_form)
}
None => locale.resolved_role_term(role, plural, &term_form, None),
}
}
pub(super) fn resolve_role_label_preset<F: OutputFormat<Output = String>>(
role: &ContributorRole,
preset: RoleLabelPreset,
names_count: usize,
term_opts: RoleLabelTermOptions,
effective_rendering: &Rendering,
options: &RenderOptions<'_>,
fmt: &F,
) -> (Option<String>, Option<String>) {
let plural = names_count > 1;
let language = options.locale.locale.as_str();
let RoleLabelTermOptions {
gender: requested_gender,
text_case,
} = term_opts;
match preset {
RoleLabelPreset::None => (None, None),
RoleLabelPreset::VerbPrefix => {
let term = options
.locale
.resolved_role_term(role, plural, &TermForm::Verb, None);
(
term.map(|t| {
super::format_role_term::<F>(&t, fmt, effective_rendering, options, "", " ")
}),
None,
)
}
RoleLabelPreset::VerbShortPrefix => {
let term = options
.locale
.resolved_role_term(role, plural, &TermForm::VerbShort, None);
(
term.map(|t| {
super::format_role_term::<F>(&t, fmt, effective_rendering, options, "", " ")
}),
None,
)
}
RoleLabelPreset::ShortSuffix => {
let term = resolve_role_term_by_request(
options.locale,
role,
plural,
TermForm::Short,
requested_gender,
)
.map(|t| apply_label_case(t, text_case, language));
(
None,
term.map(|t| {
super::format_role_term::<F>(&t, fmt, effective_rendering, options, " (", ")")
}),
)
}
RoleLabelPreset::ShortSuffixComma => {
let term = resolve_role_term_by_request(
options.locale,
role,
plural,
TermForm::Short,
requested_gender,
)
.map(|t| apply_label_case(t, text_case, language));
(
None,
term.map(|t| {
super::format_role_term::<F>(&t, fmt, effective_rendering, options, ", ", "")
}),
)
}
RoleLabelPreset::LongSuffix => {
let term = resolve_role_term_by_request(
options.locale,
role,
plural,
TermForm::Long,
requested_gender,
)
.map(|t| apply_label_case(t, text_case, language));
(
None,
term.map(|t| {
super::format_role_term::<F>(&t, fmt, effective_rendering, options, ", ", "")
}),
)
}
}
}
fn apply_label_case(
term: String,
text_case: Option<citum_schema::options::titles::TextCase>,
language: &str,
) -> String {
match text_case {
Some(case) => {
let resolved = crate::values::text_case::resolve_text_case(case, Some(language));
crate::values::text_case::apply_text_case(&term, resolved)
}
None => term,
}
}
fn resolve_explicit_label<F: OutputFormat<Output = String>>(
label_config: &citum_schema::template::RoleLabel,
component: &TemplateContributor,
reference: &Reference,
names_count: usize,
effective_rendering: &Rendering,
options: &RenderOptions<'_>,
fmt: &F,
) -> (Option<String>, Option<String>) {
use citum_schema::template::{LabelPlacement, RoleLabelForm};
let plural = names_count > 1;
let term_form = match label_config.form {
RoleLabelForm::Short => TermForm::Short,
RoleLabelForm::Long => TermForm::Long,
};
let role = match label_config.term.as_str() {
"chair" => Some(ContributorRole::Chair),
"editor" => Some(ContributorRole::Editor),
"translator" => Some(ContributorRole::Translator),
_ => Some(component.contributor.clone()),
};
let requested_gender = requested_role_gender(component, reference);
let term_text = role
.and_then(|r| {
resolve_role_term_by_request(options.locale, &r, plural, term_form, requested_gender)
})
.map(|t| apply_label_case(t, label_config.text_case, options.locale.locale.as_str()));
match label_config.placement {
LabelPlacement::Prefix => (
term_text.map(|t| {
super::format_role_term::<F>(&t, fmt, effective_rendering, options, "", " ")
}),
None,
),
LabelPlacement::Suffix => (
None,
term_text.map(|t| {
super::format_role_term::<F>(&t, fmt, effective_rendering, options, ", ", "")
}),
),
}
}
pub(super) fn resolve_role_labels<F: OutputFormat<Output = String>>(
component: &TemplateContributor,
reference: &Reference,
names_count: usize,
effective_rendering: &Rendering,
options: &RenderOptions<'_>,
fmt: &F,
role_omitted: bool,
) -> (Option<String>, Option<String>) {
if let Some(label_config) = &component.label {
return resolve_explicit_label(
label_config,
component,
reference,
names_count,
effective_rendering,
options,
fmt,
);
}
if role_omitted {
return (None, None);
}
if let Some(preset) = options
.config
.contributors
.as_ref()
.and_then(|contributors| contributors.effective_role_label_preset(&component.contributor))
{
let requested_gender = requested_role_gender(component, reference);
return resolve_role_label_preset(
&component.contributor,
preset,
names_count,
RoleLabelTermOptions {
gender: requested_gender,
text_case: None,
},
effective_rendering,
options,
fmt,
);
}
match (&component.form, &component.contributor) {
(ContributorForm::Verb | ContributorForm::VerbShort, role) => {
let plural = names_count > 1;
let term_form = match component.form {
ContributorForm::VerbShort => TermForm::VerbShort,
_ => TermForm::Verb,
};
let term = options
.locale
.resolved_role_term(role, plural, &term_form, None);
(
term.map(|t| {
super::format_role_term::<F>(&t, fmt, effective_rendering, options, "", " ")
}),
None,
)
}
(
ContributorForm::Long,
ContributorRole::Editor
| ContributorRole::Chair
| ContributorRole::Translator
| ContributorRole::Interviewer
| ContributorRole::Director
| ContributorRole::Illustrator
| ContributorRole::Composer,
) => {
let plural = names_count > 1;
let requested_gender = requested_role_gender(component, reference);
let term = resolve_role_term_by_request(
options.locale,
&component.contributor,
plural,
TermForm::Short,
requested_gender,
);
(
None,
term.map(|t| {
super::format_role_term::<F>(&t, fmt, effective_rendering, options, " (", ")")
}),
)
}
_ => (None, None),
}
}
#[cfg(test)]
mod tests {
use super::apply_label_case;
use citum_schema::options::titles::TextCase;
#[test]
fn capitalize_first_uppercases_label_initial() {
let out = apply_label_case("eds.".to_string(), Some(TextCase::CapitalizeFirst), "en-US");
assert_eq!(out, "Eds.");
}
#[test]
fn no_text_case_leaves_term_unchanged() {
let out = apply_label_case("eds.".to_string(), None, "en-US");
assert_eq!(out, "eds.");
}
}