1use super::Processor;
14use super::disambiguation::Disambiguator;
15use super::run_state::RunState;
16use crate::error::ProcessorError;
17use crate::reference::{Bibliography, CitationItem, Reference};
18use crate::values::ProcHints;
19use citum_schema::Style;
20use citum_schema::locale::Locale;
21use citum_schema::options::{
22 BibliographyPartitionKind, BibliographyPartitionMode, BibliographySortPartitioning, Config,
23 SortingMultilingualMode, bibliography::BibliographyConfig,
24};
25use indexmap::IndexMap;
26use std::collections::HashMap;
27
28impl Default for Processor {
29 fn default() -> Self {
30 let compound_sets = IndexMap::new();
31 let (compound_set_by_ref, compound_member_index) =
32 Self::build_compound_set_indexes(&compound_sets);
33 Self {
34 style: Style::default(),
35 bibliography: Bibliography::default(),
36 locale: Locale::en_us(),
37 default_config: Config::default(),
38 hints: HashMap::new(),
39 compound_sets,
40 compound_set_by_ref,
41 compound_member_index,
42 show_semantics: true,
43 inject_ast_indices: false,
44 abbreviation_map: None,
45 }
46 }
47}
48
49impl Processor {
50 fn build_processor(
54 style: Style,
55 bibliography: Bibliography,
56 locale: Locale,
57 compound_sets: IndexMap<String, Vec<String>>,
58 ) -> Self {
59 let style = style.into_resolved();
60 Self::build_processor_pre_resolved(style, bibliography, locale, compound_sets)
61 }
62
63 pub(super) fn build_processor_pre_resolved(
69 style: Style,
70 bibliography: Bibliography,
71 locale: Locale,
72 compound_sets: IndexMap<String, Vec<String>>,
73 ) -> Self {
74 let (compound_set_by_ref, compound_member_index) =
75 Self::build_compound_set_indexes(&compound_sets);
76 let mut processor = Processor {
77 style,
78 bibliography,
79 locale,
80 default_config: Config::default(),
81 hints: HashMap::new(),
82 compound_sets,
83 compound_set_by_ref,
84 compound_member_index,
85 show_semantics: true,
86 inject_ast_indices: false,
87 abbreviation_map: None,
88 };
89
90 processor.hints = processor.calculate_hints();
92 processor
93 }
94
95 fn try_validate_compound_sets(
97 bibliography: &Bibliography,
98 compound_sets: IndexMap<String, Vec<String>>,
99 ) -> Result<IndexMap<String, Vec<String>>, ProcessorError> {
100 super::validate_compound_sets(Some(compound_sets), bibliography)
101 .map(Option::unwrap_or_default)
102 }
103
104 fn validate_compound_sets_or_default(
106 bibliography: &Bibliography,
107 compound_sets: IndexMap<String, Vec<String>>,
108 ) -> IndexMap<String, Vec<String>> {
109 Self::try_validate_compound_sets(bibliography, compound_sets).unwrap_or_default()
110 }
111
112 fn build_compound_set_indexes(
117 sets: &IndexMap<String, Vec<String>>,
118 ) -> (HashMap<String, String>, HashMap<String, usize>) {
119 let mut by_ref = HashMap::new();
120 let mut member_index = HashMap::new();
121 for (set_id, members) in sets {
122 for (idx, member) in members.iter().enumerate() {
123 by_ref.insert(member.clone(), set_id.clone());
124 member_index.insert(member.clone(), idx);
125 }
126 }
127 (by_ref, member_index)
128 }
129
130 pub(crate) fn is_note_style(&self) -> bool {
132 self.get_config()
133 .processing
134 .as_ref()
135 .is_some_and(|processing| matches!(processing, citum_schema::options::Processing::Note))
136 }
137
138 fn is_numeric_style(&self) -> bool {
140 self.get_config()
141 .processing
142 .as_ref()
143 .is_some_and(|processing| {
144 matches!(processing, citum_schema::options::Processing::Numeric)
145 })
146 }
147
148 fn is_numeric_bibliography_style(&self) -> bool {
150 self.get_bibliography_config()
151 .processing
152 .as_ref()
153 .is_some_and(|processing| {
154 matches!(processing, citum_schema::options::Processing::Numeric)
155 })
156 }
157
158 fn resolved_bibliography_sort(&self) -> Option<(citum_schema::grouping::GroupSort, bool)> {
171 if let Some(sort_spec) = self
172 .style
173 .bibliography
174 .as_ref()
175 .and_then(|bibliography| bibliography.sort.as_ref())
176 {
177 return Some((sort_spec.resolve(), false));
178 }
179
180 let bibliography_config = self.get_bibliography_config();
181
182 if let Some(preset) = bibliography_config
183 .processing
184 .as_ref()
185 .and_then(citum_schema::options::Processing::default_bibliography_sort)
186 {
187 return Some((preset.group_sort(), false));
188 }
189
190 bibliography_config
191 .processing
192 .clone()
193 .unwrap_or_default()
194 .config()
195 .sort
196 .map(|sort_entry| (sort_entry.resolve().group_sort(), true))
197 }
198
199 pub(crate) fn initialize_numeric_citation_numbers(&self, run: &mut RunState) {
209 if !self.is_numeric_style() {
210 return;
211 }
212
213 self.initialize_numeric_numbers(run, self.sort_citation_number_order());
214 }
215
216 pub(crate) fn initialize_numeric_bibliography_numbers(&self, run: &mut RunState) {
218 if !self.is_numeric_bibliography_style() {
219 return;
220 }
221
222 self.initialize_numeric_numbers(run, self.sort_bibliography_number_order());
223 }
224
225 fn initialize_numeric_numbers(&self, run: &mut RunState, ordered_ids: Vec<String>) {
227 if !run
228 .citation_numbers
229 .read()
230 .unwrap_or_else(std::sync::PoisonError::into_inner)
231 .is_empty()
232 {
233 return;
234 }
235
236 self.initialize_numeric_citation_numbers_from_ordered_ids(run, ordered_ids);
237 }
238
239 fn sort_citation_number_order(&self) -> Vec<String> {
241 self.sort_references(self.bibliography.values().collect())
242 .into_iter()
243 .filter_map(citum_schema::reference::InputReference::id)
244 .map(String::from)
245 .collect()
246 }
247
248 fn sort_bibliography_number_order(&self) -> Vec<String> {
250 self.sort_references(self.bibliography.values().collect())
251 .into_iter()
252 .filter_map(citum_schema::reference::InputReference::id)
253 .map(String::from)
254 .collect()
255 }
256
257 fn initialize_numeric_citation_numbers_from_ordered_ids(
262 &self,
263 run: &mut RunState,
264 ordered_ids: Vec<String>,
265 ) {
266 let mut numbers = run
267 .citation_numbers
268 .write()
269 .unwrap_or_else(std::sync::PoisonError::into_inner);
270 if !numbers.is_empty() {
271 return;
272 }
273
274 let compound_config = self.get_bibliography_options().compound_numeric.clone();
275
276 if compound_config.is_some() {
277 let mut set_first_seen: IndexMap<String, usize> = IndexMap::new();
278 let mut current_number = 1usize;
279 run.compound_groups.clear();
280
281 for ref_id in &ordered_ids {
282 if let Some(set_id) = self.compound_set_by_ref.get(ref_id) {
283 if let Some(&number) = set_first_seen.get(set_id) {
284 numbers.insert(ref_id.clone(), number);
285 } else {
286 set_first_seen.insert(set_id.clone(), current_number);
287 if let Some(members) = self.compound_sets.get(set_id) {
288 let present_members: Vec<String> = members
289 .iter()
290 .filter(|id| self.bibliography.contains_key(*id))
291 .cloned()
292 .collect();
293 for member in &present_members {
294 numbers.insert(member.clone(), current_number);
295 }
296 if present_members.len() > 1 {
297 run.compound_groups.insert(current_number, present_members);
298 }
299 } else {
300 numbers.insert(ref_id.clone(), current_number);
301 }
302 current_number += 1;
303 }
304 } else if !numbers.contains_key(ref_id) {
305 numbers.insert(ref_id.clone(), current_number);
306 current_number += 1;
307 }
308 }
309 } else {
310 for (index, ref_id) in ordered_ids.into_iter().enumerate() {
311 numbers.insert(ref_id, index + 1);
312 }
313 }
314 }
315
316 #[must_use]
326 pub fn begin_run(&self) -> RunState {
327 let mut run = RunState::default();
328 self.initialize_numeric_citation_numbers(&mut run);
329 self.initialize_numeric_bibliography_numbers(&mut run);
330 run
331 }
332
333 #[must_use]
335 pub fn new(style: Style, bibliography: Bibliography) -> Self {
336 Self::with_compound_sets(style, bibliography, IndexMap::new())
337 }
338
339 pub fn try_with_compound_sets(
346 style: Style,
347 bibliography: Bibliography,
348 compound_sets: IndexMap<String, Vec<String>>,
349 ) -> Result<Self, ProcessorError> {
350 Self::try_with_locale_and_compound_sets(style, bibliography, Locale::en_us(), compound_sets)
351 }
352
353 #[must_use]
358 pub fn with_compound_sets(
359 style: Style,
360 bibliography: Bibliography,
361 compound_sets: IndexMap<String, Vec<String>>,
362 ) -> Self {
363 let validated_sets = Self::validate_compound_sets_or_default(&bibliography, compound_sets);
364 Self::build_processor(style, bibliography, Locale::en_us(), validated_sets)
365 }
366
367 #[must_use]
371 pub fn with_locale(style: Style, bibliography: Bibliography, locale: Locale) -> Self {
372 Self::with_locale_and_compound_sets(style, bibliography, locale, IndexMap::new())
373 }
374
375 pub fn try_with_locale_and_compound_sets(
383 style: Style,
384 bibliography: Bibliography,
385 locale: Locale,
386 compound_sets: IndexMap<String, Vec<String>>,
387 ) -> Result<Self, ProcessorError> {
388 let validated_sets = Self::try_validate_compound_sets(&bibliography, compound_sets)?;
389 Ok(Self::build_processor(
390 style,
391 bibliography,
392 locale,
393 validated_sets,
394 ))
395 }
396
397 #[must_use]
404 pub fn with_locale_and_compound_sets(
405 style: Style,
406 bibliography: Bibliography,
407 locale: Locale,
408 compound_sets: IndexMap<String, Vec<String>>,
409 ) -> Self {
410 let validated_sets = Self::validate_compound_sets_or_default(&bibliography, compound_sets);
411 Self::build_processor(style, bibliography, locale, validated_sets)
412 }
413
414 #[must_use]
419 pub fn with_style_locale(
420 style: Style,
421 bibliography: Bibliography,
422 locales_dir: &std::path::Path,
423 ) -> Self {
424 let style = style.into_resolved();
425 let locale = if let Some(ref locale_id) = style.info.default_locale {
426 Locale::load(locale_id, locales_dir)
427 } else {
428 Locale::en_us()
429 };
430 Self::with_locale_and_compound_sets(style, bibliography, locale, IndexMap::new())
431 }
432
433 #[must_use]
435 pub fn with_inject_ast_indices(mut self, inject_ast_indices: bool) -> Self {
436 self.inject_ast_indices = inject_ast_indices;
437 self
438 }
439
440 pub fn set_inject_ast_indices(&mut self, inject_ast_indices: bool) {
442 self.inject_ast_indices = inject_ast_indices;
443 }
444
445 pub fn get_config(&self) -> &Config {
447 self.style.options.as_ref().unwrap_or(&self.default_config)
448 }
449
450 pub fn get_citation_config(&self) -> std::borrow::Cow<'_, Config> {
454 let base = self.get_config();
455 match self
456 .style
457 .citation
458 .as_ref()
459 .and_then(|citation| citation.options.as_ref())
460 {
461 Some(citation_options) => std::borrow::Cow::Owned(citation_options.merged_with(base)),
462 None => std::borrow::Cow::Borrowed(base),
463 }
464 }
465
466 pub fn get_bibliography_config(&self) -> std::borrow::Cow<'_, Config> {
470 let base = self.get_config();
471 match self
472 .style
473 .bibliography
474 .as_ref()
475 .and_then(|bibliography| bibliography.options.as_ref())
476 {
477 Some(bibliography_options) => {
478 std::borrow::Cow::Owned(bibliography_options.merged_with(base))
479 }
480 None => std::borrow::Cow::Borrowed(base),
481 }
482 }
483
484 pub fn get_bibliography_options(&self) -> std::borrow::Cow<'_, BibliographyConfig> {
486 match self
487 .style
488 .bibliography
489 .as_ref()
490 .and_then(|bibliography| bibliography.options.as_ref())
491 {
492 Some(bibliography_options) => {
493 std::borrow::Cow::Owned(bibliography_options.to_bibliography_config())
494 }
495 None => std::borrow::Cow::Owned(BibliographyConfig::default()),
496 }
497 }
498
499 pub fn sort_references<'a>(&self, references: Vec<&'a Reference>) -> Vec<&'a Reference> {
503 let bibliography_config = self.get_bibliography_config();
504 let mut sorted_refs = match self.resolved_bibliography_sort() {
505 Some((sort_spec, true)) => {
506 let sorter = crate::sorting::ReferenceSorter::with_bibliography_config(
507 &self.locale,
508 &bibliography_config,
509 );
510 sorter.sort_references_with_id_tiebreak(references, &sort_spec)
511 }
512 Some((sort_spec, false)) => {
513 let sorter = crate::sorting::ReferenceSorter::with_bibliography_config(
514 &self.locale,
515 &bibliography_config,
516 );
517 sorter.sort_references(references, &sort_spec)
518 }
519 None => references,
520 };
521
522 let bibliography_options = self.get_bibliography_options();
523 if let Some(partitioning) =
524 effective_sort_partitioning(&bibliography_options, &bibliography_config).as_ref()
525 && crate::sort_partitioning::should_sort_flat(partitioning)
526 {
527 crate::sort_partitioning::sort_by_partition(
528 sorted_refs.as_mut_slice(),
529 &self.locale,
530 partitioning,
531 );
532 }
533
534 sorted_refs
535 }
536
537 pub fn sort_citation_items(
539 &self,
540 items: Vec<CitationItem>,
541 spec: &citum_schema::CitationSpec,
542 ) -> Vec<CitationItem> {
543 if let Some(sort_spec) = &spec.sort {
544 let mut items_with_refs: Vec<(CitationItem, Option<&Reference>)> = items
545 .into_iter()
546 .map(|item| {
547 let reference = self.bibliography.get(&item.id);
548 (item, reference)
549 })
550 .collect();
551
552 let resolved_sort = sort_spec.resolve();
553 let sorter = crate::sorting::ReferenceSorter::new(&self.locale);
554 items_with_refs.sort_by(|left, right| match (left.1, right.1) {
555 (Some(left_reference), Some(right_reference)) => {
556 for sort_key in &resolved_sort.template {
557 let cmp = sorter.compare_by_key(left_reference, right_reference, sort_key);
558 if cmp != std::cmp::Ordering::Equal {
559 return cmp;
560 }
561 }
562 std::cmp::Ordering::Equal
563 }
564 (Some(_), None) => std::cmp::Ordering::Less,
565 (None, Some(_)) => std::cmp::Ordering::Greater,
566 (None, None) => std::cmp::Ordering::Equal,
567 });
568
569 return items_with_refs
570 .into_iter()
571 .map(|(item, _reference)| item)
572 .collect();
573 }
574
575 items
576 }
577
578 pub fn calculate_hints(&self) -> HashMap<String, ProcHints> {
583 let citation_config = self.get_citation_config();
584 let config = citation_config.as_ref();
585 let bibliography_sort = self.resolved_bibliography_sort();
586
587 let disambiguator = if let Some((resolved_sort, _id_tiebreak)) = &bibliography_sort {
588 Disambiguator::with_group_sort(&self.bibliography, config, &self.locale, resolved_sort)
589 } else {
590 Disambiguator::new(&self.bibliography, config, &self.locale)
591 };
592
593 disambiguator.calculate_hints()
594 }
595}
596
597fn effective_sort_partitioning(
598 bibliography_options: &BibliographyConfig,
599 bibliography_config: &Config,
600) -> Option<BibliographySortPartitioning> {
601 if let Some(partitioning) = &bibliography_options.sort_partitioning {
602 return Some(partitioning.clone());
603 }
604
605 bibliography_config
606 .sorting
607 .as_ref()
608 .is_some_and(|sorting| {
609 sorting.effective_multilingual() == SortingMultilingualMode::PerScript
610 })
611 .then(|| BibliographySortPartitioning {
612 by: BibliographyPartitionKind::Script,
613 mode: BibliographyPartitionMode::SortOnly,
614 order: Vec::new(),
615 headings: HashMap::new(),
616 unknown_fields: Default::default(),
617 })
618}