citum_engine/values/contributor/
mod.rs1pub(crate) mod labels;
12pub mod names;
13mod substitute;
14
15use crate::reference::Reference;
16use crate::values::{ComponentValues, ProcHints, ProcValues, RenderContext, RenderOptions};
17use citum_schema::options::SubsequentNameForm;
18use citum_schema::template::{ContributorForm, ContributorRole, TemplateContributor};
19
20#[cfg(test)]
21pub(crate) use names::{NameFormatContext, format_single_name};
22pub use names::{NamesOverrides, format_contributors_short, format_names};
23
24pub(super) fn contributor_for_role(
30 reference: &Reference,
31 role: &ContributorRole,
32) -> Option<citum_schema::reference::Contributor> {
33 match role {
34 ContributorRole::Author => reference.author(),
35 ContributorRole::Editor => reference.editor(),
36 ContributorRole::Translator => reference.translator(),
37 _ => contributor_role_to_reference_role(role).and_then(|role| reference.contributor(role)),
38 }
39}
40
41pub(super) fn contributor_role_to_reference_role(
43 role: &ContributorRole,
44) -> Option<citum_schema::reference::ContributorRole> {
45 match role {
46 ContributorRole::Author => Some(citum_schema::reference::ContributorRole::Author),
47 ContributorRole::Editor => Some(citum_schema::reference::ContributorRole::Editor),
48 ContributorRole::Translator => Some(citum_schema::reference::ContributorRole::Translator),
49 ContributorRole::Recipient => Some(citum_schema::reference::ContributorRole::Recipient),
50 ContributorRole::Chair => Some(citum_schema::reference::ContributorRole::Unknown(
51 "chair".to_string(),
52 )),
53 ContributorRole::Interviewer => Some(citum_schema::reference::ContributorRole::Interviewer),
54 ContributorRole::Guest => Some(citum_schema::reference::ContributorRole::Guest),
55 ContributorRole::Performer => Some(citum_schema::reference::ContributorRole::Performer),
56 ContributorRole::Director => Some(citum_schema::reference::ContributorRole::Director),
57 ContributorRole::Composer => Some(citum_schema::reference::ContributorRole::Composer),
58 ContributorRole::Writer => Some(citum_schema::reference::ContributorRole::Writer),
59 ContributorRole::Illustrator => Some(citum_schema::reference::ContributorRole::Illustrator),
60 ContributorRole::Inventor => Some(citum_schema::reference::ContributorRole::Unknown(
61 "inventor".to_string(),
62 )),
63 ContributorRole::Counsel => Some(citum_schema::reference::ContributorRole::Unknown(
64 "counsel".to_string(),
65 )),
66 ContributorRole::CollectionEditor => Some(
67 citum_schema::reference::ContributorRole::Unknown("collection-editor".to_string()),
68 ),
69 ContributorRole::ContainerAuthor => Some(
70 citum_schema::reference::ContributorRole::Unknown("container-author".to_string()),
71 ),
72 ContributorRole::EditorialDirector => Some(
73 citum_schema::reference::ContributorRole::Unknown("editorial-director".to_string()),
74 ),
75 ContributorRole::TextualEditor => Some(citum_schema::reference::ContributorRole::Unknown(
76 "textual-editor".to_string(),
77 )),
78 ContributorRole::OriginalAuthor => Some(citum_schema::reference::ContributorRole::Unknown(
79 "original-author".to_string(),
80 )),
81 ContributorRole::ReviewedAuthor => Some(citum_schema::reference::ContributorRole::Unknown(
82 "reviewed-author".to_string(),
83 )),
84 ContributorRole::Unknown(role) => Some(match role.as_str() {
85 "compiler" => citum_schema::reference::ContributorRole::Compiler,
86 "performer" => citum_schema::reference::ContributorRole::Performer,
87 "narrator" => citum_schema::reference::ContributorRole::Narrator,
88 "host" => citum_schema::reference::ContributorRole::Host,
89 "producer" | "executive-producer" => citum_schema::reference::ContributorRole::Producer,
90 "writer" => citum_schema::reference::ContributorRole::Writer,
91 _ => citum_schema::reference::ContributorRole::Unknown(role.clone()),
92 }),
93 ContributorRole::Interviewee | ContributorRole::Publisher => None,
94 _ => None,
95 }
96}
97
98pub(super) fn is_role_label_omitted(options: &RenderOptions<'_>, role: &ContributorRole) -> bool {
102 options
103 .config
104 .contributors
105 .as_ref()
106 .and_then(|c| c.role.as_ref())
107 .is_some_and(|role_opts| {
108 role_opts
109 .omit
110 .iter()
111 .any(|entry| entry.eq_ignore_ascii_case(role.as_str()))
112 })
113}
114
115pub(super) fn format_role_term<F: crate::render::format::OutputFormat<Output = String>>(
120 term: &str,
121 fmt: &F,
122 effective_rendering: &citum_schema::template::Rendering,
123 options: &RenderOptions<'_>,
124 prefix: &str,
125 suffix: &str,
126) -> String {
127 let term_str = if crate::values::should_strip_periods(effective_rendering, options) {
128 crate::values::strip_trailing_periods(term)
129 } else {
130 term.to_string()
131 };
132 let term_str = match effective_rendering.text_case {
138 Some(citum_schema::options::titles::TextCase::CapitalizeFirst) => {
139 crate::values::text_case::capitalize_first_word(&term_str)
140 }
141 _ => term_str,
142 };
143 fmt.text(&format!("{prefix}{term_str}{suffix}"))
144}
145
146fn apply_integral_subsequent_form(
149 component: &mut TemplateContributor,
150 hints: &ProcHints,
151 options: &RenderOptions<'_>,
152) {
153 if options.context != RenderContext::Citation {
154 return;
155 }
156 if !matches!(options.mode, citum_schema::citation::CitationMode::Integral) {
157 return;
158 }
159 if !matches!(component.contributor, ContributorRole::Author) {
160 return;
161 }
162 if !matches!(
163 hints.integral_name_state,
164 Some(citum_schema::citation::IntegralNameState::Subsequent)
165 ) {
166 return;
167 }
168 let Some(memory) = options.config.integral_name_memory.as_ref() else {
169 return;
170 };
171 component.form = match memory.resolve().subsequent_form {
172 SubsequentNameForm::Short => ContributorForm::Short,
173 SubsequentNameForm::FamilyOnly => ContributorForm::FamilyOnly,
174 };
175}
176
177fn format_contributor_names(
179 component: &TemplateContributor,
180 names_vec: &[crate::reference::FlatName],
181 effective_rendering: &citum_schema::template::Rendering,
182 options: &RenderOptions<'_>,
183 hints: &ProcHints,
184) -> String {
185 let effective_name_order = component.name_order.as_ref().or_else(|| {
186 options
187 .config
188 .contributors
189 .as_ref()?
190 .effective_role_name_order(&component.contributor)
191 });
192 let effective_shorten = component
193 .shorten
194 .as_ref()
195 .or_else(|| options.config.contributors.as_ref()?.shorten.as_ref());
196
197 let effective_name_form = component.name_form.or(effective_rendering.name_form);
202
203 let name_overrides = names::NamesOverrides {
204 name_order: effective_name_order,
205 sort_separator: component.sort_separator.as_ref(),
206 shorten: effective_shorten,
207 and: component.and.as_ref(),
208 initialize_with: effective_rendering.initialize_with.as_ref(),
209 name_form: effective_name_form,
210 };
211 names::format_names(names_vec, &component.form, options, &name_overrides, hints)
212}
213
214impl ComponentValues for TemplateContributor {
215 #[allow(
216 clippy::too_many_lines,
217 reason = "large match statement for contributor role dispatch"
218 )]
219 fn values<F: crate::render::format::OutputFormat<Output = String>>(
220 &self,
221 reference: &Reference,
222 hints: &ProcHints,
223 options: &RenderOptions<'_>,
224 ) -> Option<ProcValues<F::Output>> {
225 let fmt = F::default();
226
227 let mut component = self.clone();
228 let effective_rendering = self.rendering.clone();
229
230 apply_integral_subsequent_form(&mut component, hints, options);
232
233 if effective_rendering.suppress == Some(true) {
235 return None;
236 }
237
238 let contributor = match &component.contributor {
239 ContributorRole::Author => {
240 if options.suppress_author {
241 None
242 } else {
243 contributor_for_role(reference, &component.contributor)
244 }
245 }
246 _ => contributor_for_role(reference, &component.contributor),
247 };
248
249 let default_substitute = citum_schema::options::SubstituteConfig::default();
251 let substitute_config = options
252 .config
253 .substitute
254 .as_ref()
255 .unwrap_or(&default_substitute);
256 let substitute = substitute_config.resolve();
257
258 if substitute::is_role_suppressed_by_substitute(
260 &component.contributor,
261 &substitute,
262 reference,
263 ) {
264 return None;
265 }
266
267 let names_vec = if let Some(contrib) = contributor {
269 substitute::resolve_multilingual_for_contrib(&contrib, options)
270 } else {
271 Vec::new()
272 };
273
274 if names_vec.is_empty()
276 && matches!(component.contributor, ContributorRole::Author)
277 && options.suppress_author
278 {
279 return None;
280 }
281
282 if names_vec.is_empty() && matches!(component.contributor, ContributorRole::Author) {
284 return substitute::resolve_author_substitute::<F>(
285 &component,
286 hints,
287 options,
288 reference,
289 &effective_rendering,
290 &fmt,
291 &substitute,
292 );
293 }
294
295 if names_vec.is_empty() {
297 return substitute::resolve_role_substitute::<F>(
298 &component.contributor,
299 &component,
300 hints,
301 options,
302 reference,
303 &effective_rendering,
304 &fmt,
305 &substitute,
306 );
307 }
308
309 let formatted =
310 format_contributor_names(&component, &names_vec, &effective_rendering, options, hints);
311
312 let role_omitted = is_role_label_omitted(options, &component.contributor);
313 let (role_prefix, role_suffix) = labels::resolve_role_labels::<F>(
314 &component,
315 reference,
316 names_vec.len(),
317 &effective_rendering,
318 options,
319 &fmt,
320 role_omitted,
321 );
322
323 let is_pre_formatted = role_prefix.is_some() || role_suffix.is_some();
324 let formatted = crate::values::apply_abbreviation(formatted, options.abbreviation_map);
325 let final_value = if is_pre_formatted {
326 fmt.text(&formatted)
327 } else {
328 formatted
329 };
330
331 Some(ProcValues {
332 value: final_value,
333 prefix: role_prefix,
334 suffix: role_suffix,
335 url: crate::values::resolve_effective_url(
336 component.links.as_ref(),
337 options.config.links.as_ref(),
338 reference,
339 citum_schema::options::LinkAnchor::Component,
340 ),
341 substituted_key: None,
342 pre_formatted: is_pre_formatted,
343 })
344 }
345}