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::{IntegralNameForm, IntegralNameRule};
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(
126 component: &mut TemplateContributor,
127 hints: &ProcHints,
128 options: &RenderOptions<'_>,
129) {
130 if options.context != RenderContext::Citation {
131 return;
132 }
133 if !matches!(options.mode, citum_schema::citation::CitationMode::Integral) {
134 return;
135 }
136 if !matches!(component.contributor, ContributorRole::Author) {
137 return;
138 }
139 if !matches!(
140 hints.integral_name_state,
141 Some(citum_schema::citation::IntegralNameState::Subsequent)
142 ) {
143 return;
144 }
145 if !options
146 .config
147 .integral_names
148 .as_ref()
149 .is_some_and(|cfg| matches!(cfg.resolve().rule, IntegralNameRule::FullThenShort))
150 {
151 return;
152 }
153 let subsequent_form = options
154 .config
155 .integral_names
156 .as_ref()
157 .map_or(IntegralNameForm::Short, |cfg| cfg.resolve().subsequent_form);
158 component.form = match subsequent_form {
159 IntegralNameForm::Short => ContributorForm::Short,
160 IntegralNameForm::FamilyOnly => ContributorForm::FamilyOnly,
161 };
162}
163
164fn format_contributor_names(
166 component: &TemplateContributor,
167 names_vec: &[crate::reference::FlatName],
168 effective_rendering: &citum_schema::template::Rendering,
169 options: &RenderOptions<'_>,
170 hints: &ProcHints,
171) -> String {
172 let effective_name_order = component.name_order.as_ref().or_else(|| {
173 options
174 .config
175 .contributors
176 .as_ref()?
177 .effective_role_name_order(&component.contributor)
178 });
179 let effective_shorten = component
180 .shorten
181 .as_ref()
182 .or_else(|| options.config.contributors.as_ref()?.shorten.as_ref());
183
184 let effective_name_form = component.name_form.or(effective_rendering.name_form);
189
190 let name_overrides = names::NamesOverrides {
191 name_order: effective_name_order,
192 sort_separator: component.sort_separator.as_ref(),
193 shorten: effective_shorten,
194 and: component.and.as_ref(),
195 initialize_with: effective_rendering.initialize_with.as_ref(),
196 name_form: effective_name_form,
197 };
198 names::format_names(names_vec, &component.form, options, &name_overrides, hints)
199}
200
201impl ComponentValues for TemplateContributor {
202 #[allow(
203 clippy::too_many_lines,
204 reason = "large match statement for contributor role dispatch"
205 )]
206 fn values<F: crate::render::format::OutputFormat<Output = String>>(
207 &self,
208 reference: &Reference,
209 hints: &ProcHints,
210 options: &RenderOptions<'_>,
211 ) -> Option<ProcValues<F::Output>> {
212 let fmt = F::default();
213
214 let mut component = self.clone();
215 let effective_rendering = self.rendering.clone();
216
217 apply_integral_subsequent_form(&mut component, hints, options);
219
220 if effective_rendering.suppress == Some(true) {
222 return None;
223 }
224
225 let contributor = match &component.contributor {
226 ContributorRole::Author => {
227 if options.suppress_author {
228 None
229 } else {
230 contributor_for_role(reference, &component.contributor)
231 }
232 }
233 _ => contributor_for_role(reference, &component.contributor),
234 };
235
236 let default_substitute = citum_schema::options::SubstituteConfig::default();
238 let substitute_config = options
239 .config
240 .substitute
241 .as_ref()
242 .unwrap_or(&default_substitute);
243 let substitute = substitute_config.resolve();
244
245 if substitute::is_role_suppressed_by_substitute(
247 &component.contributor,
248 &substitute,
249 reference,
250 ) {
251 return None;
252 }
253
254 let names_vec = if let Some(contrib) = contributor {
256 substitute::resolve_multilingual_for_contrib(&contrib, options)
257 } else {
258 Vec::new()
259 };
260
261 if names_vec.is_empty()
263 && matches!(component.contributor, ContributorRole::Author)
264 && options.suppress_author
265 {
266 return None;
267 }
268
269 if names_vec.is_empty() && matches!(component.contributor, ContributorRole::Author) {
271 return substitute::resolve_author_substitute::<F>(
272 &component,
273 hints,
274 options,
275 reference,
276 &effective_rendering,
277 &fmt,
278 &substitute,
279 );
280 }
281
282 if names_vec.is_empty() {
284 return substitute::resolve_role_substitute::<F>(
285 &component.contributor,
286 &component,
287 hints,
288 options,
289 reference,
290 &effective_rendering,
291 &fmt,
292 &substitute,
293 );
294 }
295
296 let formatted =
297 format_contributor_names(&component, &names_vec, &effective_rendering, options, hints);
298
299 let role_omitted = is_role_label_omitted(options, &component.contributor);
300 let (role_prefix, role_suffix) = labels::resolve_role_labels::<F>(
301 &component,
302 reference,
303 names_vec.len(),
304 &effective_rendering,
305 options,
306 &fmt,
307 role_omitted,
308 );
309
310 let is_pre_formatted = role_prefix.is_some() || role_suffix.is_some();
311 let formatted = crate::values::apply_abbreviation(formatted, options.abbreviation_map);
312 let final_value = if is_pre_formatted {
313 fmt.text(&formatted)
314 } else {
315 formatted
316 };
317
318 Some(ProcValues {
319 value: final_value,
320 prefix: role_prefix,
321 suffix: role_suffix,
322 url: crate::values::resolve_effective_url(
323 component.links.as_ref(),
324 options.config.links.as_ref(),
325 reference,
326 citum_schema::options::LinkAnchor::Component,
327 ),
328 substituted_key: None,
329 pre_formatted: is_pre_formatted,
330 })
331 }
332}