citum_engine/values/contributor/
mod.rs1mod 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::Director => Some(citum_schema::reference::ContributorRole::Director),
56 ContributorRole::Composer => Some(citum_schema::reference::ContributorRole::Composer),
57 ContributorRole::Illustrator => Some(citum_schema::reference::ContributorRole::Illustrator),
58 ContributorRole::Inventor => Some(citum_schema::reference::ContributorRole::Unknown(
59 "inventor".to_string(),
60 )),
61 ContributorRole::Counsel => Some(citum_schema::reference::ContributorRole::Unknown(
62 "counsel".to_string(),
63 )),
64 ContributorRole::CollectionEditor => Some(
65 citum_schema::reference::ContributorRole::Unknown("collection-editor".to_string()),
66 ),
67 ContributorRole::ContainerAuthor => Some(
68 citum_schema::reference::ContributorRole::Unknown("container-author".to_string()),
69 ),
70 ContributorRole::EditorialDirector => Some(
71 citum_schema::reference::ContributorRole::Unknown("editorial-director".to_string()),
72 ),
73 ContributorRole::TextualEditor => Some(citum_schema::reference::ContributorRole::Unknown(
74 "textual-editor".to_string(),
75 )),
76 ContributorRole::OriginalAuthor => Some(citum_schema::reference::ContributorRole::Unknown(
77 "original-author".to_string(),
78 )),
79 ContributorRole::ReviewedAuthor => Some(citum_schema::reference::ContributorRole::Unknown(
80 "reviewed-author".to_string(),
81 )),
82 ContributorRole::Interviewee | ContributorRole::Publisher => None,
83 _ => None,
84 }
85}
86
87pub(super) fn is_role_label_omitted(options: &RenderOptions<'_>, role: &ContributorRole) -> bool {
91 options
92 .config
93 .contributors
94 .as_ref()
95 .and_then(|c| c.role.as_ref())
96 .is_some_and(|role_opts| {
97 role_opts
98 .omit
99 .iter()
100 .any(|entry| entry.eq_ignore_ascii_case(role.as_str()))
101 })
102}
103
104pub(super) fn format_role_term<F: crate::render::format::OutputFormat<Output = String>>(
109 term: &str,
110 fmt: &F,
111 effective_rendering: &citum_schema::template::Rendering,
112 options: &RenderOptions<'_>,
113 prefix: &str,
114 suffix: &str,
115) -> String {
116 let term_str = if crate::values::should_strip_periods(effective_rendering, options) {
117 crate::values::strip_trailing_periods(term)
118 } else {
119 term.to_string()
120 };
121 fmt.text(&format!("{prefix}{term_str}{suffix}"))
122}
123
124fn apply_integral_subsequent_form(
127 component: &mut TemplateContributor,
128 hints: &ProcHints,
129 options: &RenderOptions<'_>,
130) {
131 if options.context != RenderContext::Citation {
132 return;
133 }
134 if !matches!(options.mode, citum_schema::citation::CitationMode::Integral) {
135 return;
136 }
137 if !matches!(component.contributor, ContributorRole::Author) {
138 return;
139 }
140 if !matches!(
141 hints.integral_name_state,
142 Some(citum_schema::citation::IntegralNameState::Subsequent)
143 ) {
144 return;
145 }
146 let Some(memory) = options.config.integral_name_memory.as_ref() else {
147 return;
148 };
149 component.form = match memory.resolve().subsequent_form {
150 SubsequentNameForm::Short => ContributorForm::Short,
151 SubsequentNameForm::FamilyOnly => ContributorForm::FamilyOnly,
152 };
153}
154
155fn format_contributor_names(
157 component: &TemplateContributor,
158 names_vec: &[crate::reference::FlatName],
159 effective_rendering: &citum_schema::template::Rendering,
160 options: &RenderOptions<'_>,
161 hints: &ProcHints,
162) -> String {
163 let effective_name_order = component.name_order.as_ref().or_else(|| {
164 options
165 .config
166 .contributors
167 .as_ref()?
168 .effective_role_name_order(&component.contributor)
169 });
170 let effective_shorten = component
171 .shorten
172 .as_ref()
173 .or_else(|| options.config.contributors.as_ref()?.shorten.as_ref());
174
175 let effective_name_form = component.name_form.or(effective_rendering.name_form);
180
181 let name_overrides = names::NamesOverrides {
182 name_order: effective_name_order,
183 sort_separator: component.sort_separator.as_ref(),
184 shorten: effective_shorten,
185 and: component.and.as_ref(),
186 initialize_with: effective_rendering.initialize_with.as_ref(),
187 name_form: effective_name_form,
188 };
189 names::format_names(names_vec, &component.form, options, &name_overrides, hints)
190}
191
192impl ComponentValues for TemplateContributor {
193 #[allow(
194 clippy::too_many_lines,
195 reason = "large match statement for contributor role dispatch"
196 )]
197 fn values<F: crate::render::format::OutputFormat<Output = String>>(
198 &self,
199 reference: &Reference,
200 hints: &ProcHints,
201 options: &RenderOptions<'_>,
202 ) -> Option<ProcValues<F::Output>> {
203 let fmt = F::default();
204
205 let mut component = self.clone();
206 let effective_rendering = self.rendering.clone();
207
208 apply_integral_subsequent_form(&mut component, hints, options);
210
211 if effective_rendering.suppress == Some(true) {
213 return None;
214 }
215
216 let contributor = match &component.contributor {
217 ContributorRole::Author => {
218 if options.suppress_author {
219 None
220 } else {
221 contributor_for_role(reference, &component.contributor)
222 }
223 }
224 _ => contributor_for_role(reference, &component.contributor),
225 };
226
227 let default_substitute = citum_schema::options::SubstituteConfig::default();
229 let substitute_config = options
230 .config
231 .substitute
232 .as_ref()
233 .unwrap_or(&default_substitute);
234 let substitute = substitute_config.resolve();
235
236 if substitute::is_role_suppressed_by_substitute(
238 &component.contributor,
239 &substitute,
240 reference,
241 ) {
242 return None;
243 }
244
245 let names_vec = if let Some(contrib) = contributor {
247 substitute::resolve_multilingual_for_contrib(&contrib, options)
248 } else {
249 Vec::new()
250 };
251
252 if names_vec.is_empty()
254 && matches!(component.contributor, ContributorRole::Author)
255 && options.suppress_author
256 {
257 return None;
258 }
259
260 if names_vec.is_empty() && matches!(component.contributor, ContributorRole::Author) {
262 return substitute::resolve_author_substitute::<F>(
263 &component,
264 hints,
265 options,
266 reference,
267 &effective_rendering,
268 &fmt,
269 &substitute,
270 );
271 }
272
273 if names_vec.is_empty() {
275 return substitute::resolve_role_substitute::<F>(
276 &component.contributor,
277 &component,
278 hints,
279 options,
280 reference,
281 &effective_rendering,
282 &fmt,
283 &substitute,
284 );
285 }
286
287 let formatted =
288 format_contributor_names(&component, &names_vec, &effective_rendering, options, hints);
289
290 let role_omitted = is_role_label_omitted(options, &component.contributor);
291 let (role_prefix, role_suffix) = labels::resolve_role_labels::<F>(
292 &component,
293 reference,
294 names_vec.len(),
295 &effective_rendering,
296 options,
297 &fmt,
298 role_omitted,
299 );
300
301 let is_pre_formatted = role_prefix.is_some() || role_suffix.is_some();
302 let formatted = crate::values::apply_abbreviation(formatted, options.abbreviation_map);
303 let final_value = if is_pre_formatted {
304 fmt.text(&formatted)
305 } else {
306 formatted
307 };
308
309 Some(ProcValues {
310 value: final_value,
311 prefix: role_prefix,
312 suffix: role_suffix,
313 url: crate::values::resolve_effective_url(
314 component.links.as_ref(),
315 options.config.links.as_ref(),
316 reference,
317 citum_schema::options::LinkAnchor::Component,
318 ),
319 substituted_key: None,
320 pre_formatted: is_pre_formatted,
321 })
322 }
323}