1use super::Processor;
14use super::disambiguation::Disambiguator;
15use crate::error::ProcessorError;
16use crate::reference::{Bibliography, CitationItem, Reference};
17use crate::values::ProcHints;
18use citum_schema::Style;
19use citum_schema::locale::Locale;
20use citum_schema::options::{Config, bibliography::BibliographyConfig};
21use indexmap::IndexMap;
22use std::cell::RefCell;
23use std::collections::{HashMap, HashSet};
24
25impl Default for Processor {
26 fn default() -> Self {
27 let compound_sets = IndexMap::new();
28 let (compound_set_by_ref, compound_member_index) =
29 Self::build_compound_set_indexes(&compound_sets);
30 Self {
31 style: Style::default(),
32 bibliography: Bibliography::default(),
33 locale: Locale::en_us(),
34 default_config: Config::default(),
35 hints: HashMap::new(),
36 citation_numbers: RefCell::new(HashMap::new()),
37 cited_ids: RefCell::new(HashSet::new()),
38 compound_sets,
39 compound_set_by_ref,
40 compound_member_index,
41 compound_groups: RefCell::new(IndexMap::new()),
42 dynamic_compound_set_by_ref: RefCell::new(HashMap::new()),
43 dynamic_compound_member_index: RefCell::new(HashMap::new()),
44 dynamic_compound_sets: RefCell::new(IndexMap::new()),
45 show_semantics: true,
46 inject_ast_indices: false,
47 abbreviation_map: None,
48 first_note_by_id: RefCell::new(HashMap::new()),
49 }
50 }
51}
52
53impl Processor {
54 fn build_processor(
58 style: Style,
59 bibliography: Bibliography,
60 locale: Locale,
61 compound_sets: IndexMap<String, Vec<String>>,
62 ) -> Self {
63 let style = style.into_resolved();
64 Self::build_processor_pre_resolved(style, bibliography, locale, compound_sets)
65 }
66
67 pub(super) fn build_processor_pre_resolved(
73 style: Style,
74 bibliography: Bibliography,
75 locale: Locale,
76 compound_sets: IndexMap<String, Vec<String>>,
77 ) -> Self {
78 let (compound_set_by_ref, compound_member_index) =
79 Self::build_compound_set_indexes(&compound_sets);
80 let mut processor = Processor {
81 style,
82 bibliography,
83 locale,
84 default_config: Config::default(),
85 hints: HashMap::new(),
86 citation_numbers: RefCell::new(HashMap::new()),
87 cited_ids: RefCell::new(HashSet::new()),
88 compound_sets,
89 compound_set_by_ref,
90 compound_member_index,
91 compound_groups: RefCell::new(IndexMap::new()),
92 dynamic_compound_set_by_ref: RefCell::new(HashMap::new()),
93 dynamic_compound_member_index: RefCell::new(HashMap::new()),
94 dynamic_compound_sets: RefCell::new(IndexMap::new()),
95 show_semantics: true,
96 inject_ast_indices: false,
97 abbreviation_map: None,
98 first_note_by_id: RefCell::new(HashMap::new()),
99 };
100
101 processor.hints = processor.calculate_hints();
103 processor
104 }
105
106 fn try_validate_compound_sets(
108 bibliography: &Bibliography,
109 compound_sets: IndexMap<String, Vec<String>>,
110 ) -> Result<IndexMap<String, Vec<String>>, ProcessorError> {
111 super::validate_compound_sets(Some(compound_sets), bibliography)
112 .map(Option::unwrap_or_default)
113 }
114
115 fn validate_compound_sets_or_default(
117 bibliography: &Bibliography,
118 compound_sets: IndexMap<String, Vec<String>>,
119 ) -> IndexMap<String, Vec<String>> {
120 Self::try_validate_compound_sets(bibliography, compound_sets).unwrap_or_default()
121 }
122
123 fn build_compound_set_indexes(
128 sets: &IndexMap<String, Vec<String>>,
129 ) -> (HashMap<String, String>, HashMap<String, usize>) {
130 let mut by_ref = HashMap::new();
131 let mut member_index = HashMap::new();
132 for (set_id, members) in sets {
133 for (idx, member) in members.iter().enumerate() {
134 by_ref.insert(member.clone(), set_id.clone());
135 member_index.insert(member.clone(), idx);
136 }
137 }
138 (by_ref, member_index)
139 }
140
141 pub(crate) fn is_note_style(&self) -> bool {
143 self.get_config()
144 .processing
145 .as_ref()
146 .is_some_and(|processing| matches!(processing, citum_schema::options::Processing::Note))
147 }
148
149 fn is_numeric_style(&self) -> bool {
151 self.get_config()
152 .processing
153 .as_ref()
154 .is_some_and(|processing| {
155 matches!(processing, citum_schema::options::Processing::Numeric)
156 })
157 }
158
159 fn is_numeric_bibliography_style(&self) -> bool {
161 self.get_bibliography_config()
162 .processing
163 .as_ref()
164 .is_some_and(|processing| {
165 matches!(processing, citum_schema::options::Processing::Numeric)
166 })
167 }
168
169 fn resolved_bibliography_sort(&self) -> Option<(citum_schema::grouping::GroupSort, bool)> {
182 if let Some(sort_spec) = self
183 .style
184 .bibliography
185 .as_ref()
186 .and_then(|bibliography| bibliography.sort.as_ref())
187 {
188 return Some((sort_spec.resolve(), false));
189 }
190
191 let bibliography_config = self.get_bibliography_config();
192
193 if let Some(preset) = bibliography_config
194 .processing
195 .as_ref()
196 .and_then(citum_schema::options::Processing::default_bibliography_sort)
197 {
198 return Some((preset.group_sort(), false));
199 }
200
201 bibliography_config
202 .processing
203 .clone()
204 .unwrap_or_default()
205 .config()
206 .sort
207 .map(|sort_entry| (sort_entry.resolve().group_sort(), true))
208 }
209
210 pub(crate) fn initialize_numeric_citation_numbers(&self) {
220 if !self.is_numeric_style() {
221 return;
222 }
223
224 self.initialize_numeric_numbers(self.sort_citation_number_order());
225 }
226
227 pub(crate) fn initialize_numeric_bibliography_numbers(&self) {
229 if !self.is_numeric_bibliography_style() {
230 return;
231 }
232
233 self.initialize_numeric_numbers(self.sort_bibliography_number_order());
234 }
235
236 fn initialize_numeric_numbers(&self, ordered_ids: Vec<String>) {
238 if !self.citation_numbers.borrow().is_empty() {
239 return;
240 }
241
242 self.initialize_numeric_citation_numbers_from_ordered_ids(ordered_ids);
243 }
244
245 fn sort_citation_number_order(&self) -> Vec<String> {
247 self.sort_references(self.bibliography.values().collect())
248 .into_iter()
249 .filter_map(citum_schema::reference::InputReference::id)
250 .map(String::from)
251 .collect()
252 }
253
254 fn sort_bibliography_number_order(&self) -> Vec<String> {
256 self.sort_references(self.bibliography.values().collect())
257 .into_iter()
258 .filter_map(citum_schema::reference::InputReference::id)
259 .map(String::from)
260 .collect()
261 }
262
263 fn initialize_numeric_citation_numbers_from_ordered_ids(&self, ordered_ids: Vec<String>) {
268 let mut numbers = self.citation_numbers.borrow_mut();
269 if !numbers.is_empty() {
270 return;
271 }
272
273 let compound_config = self.get_bibliography_options().compound_numeric.clone();
274
275 if compound_config.is_some() {
276 let mut set_first_seen: IndexMap<String, usize> = IndexMap::new();
277 let mut current_number = 1usize;
278 let mut compound_groups = self.compound_groups.borrow_mut();
279 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 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]
318 pub fn new(style: Style, bibliography: Bibliography) -> Self {
319 Self::with_compound_sets(style, bibliography, IndexMap::new())
320 }
321
322 pub fn try_with_compound_sets(
329 style: Style,
330 bibliography: Bibliography,
331 compound_sets: IndexMap<String, Vec<String>>,
332 ) -> Result<Self, ProcessorError> {
333 Self::try_with_locale_and_compound_sets(style, bibliography, Locale::en_us(), compound_sets)
334 }
335
336 #[must_use]
341 pub fn with_compound_sets(
342 style: Style,
343 bibliography: Bibliography,
344 compound_sets: IndexMap<String, Vec<String>>,
345 ) -> Self {
346 let validated_sets = Self::validate_compound_sets_or_default(&bibliography, compound_sets);
347 Self::build_processor(style, bibliography, Locale::en_us(), validated_sets)
348 }
349
350 #[must_use]
354 pub fn with_locale(style: Style, bibliography: Bibliography, locale: Locale) -> Self {
355 Self::with_locale_and_compound_sets(style, bibliography, locale, IndexMap::new())
356 }
357
358 pub fn try_with_locale_and_compound_sets(
366 style: Style,
367 bibliography: Bibliography,
368 locale: Locale,
369 compound_sets: IndexMap<String, Vec<String>>,
370 ) -> Result<Self, ProcessorError> {
371 let validated_sets = Self::try_validate_compound_sets(&bibliography, compound_sets)?;
372 Ok(Self::build_processor(
373 style,
374 bibliography,
375 locale,
376 validated_sets,
377 ))
378 }
379
380 #[must_use]
387 pub fn with_locale_and_compound_sets(
388 style: Style,
389 bibliography: Bibliography,
390 locale: Locale,
391 compound_sets: IndexMap<String, Vec<String>>,
392 ) -> Self {
393 let validated_sets = Self::validate_compound_sets_or_default(&bibliography, compound_sets);
394 Self::build_processor(style, bibliography, locale, validated_sets)
395 }
396
397 #[must_use]
402 pub fn with_style_locale(
403 style: Style,
404 bibliography: Bibliography,
405 locales_dir: &std::path::Path,
406 ) -> Self {
407 let style = style.into_resolved();
408 let locale = if let Some(ref locale_id) = style.info.default_locale {
409 Locale::load(locale_id, locales_dir)
410 } else {
411 Locale::en_us()
412 };
413 Self::with_locale_and_compound_sets(style, bibliography, locale, IndexMap::new())
414 }
415
416 #[must_use]
418 pub fn with_inject_ast_indices(mut self, inject_ast_indices: bool) -> Self {
419 self.inject_ast_indices = inject_ast_indices;
420 self
421 }
422
423 pub fn set_inject_ast_indices(&mut self, inject_ast_indices: bool) {
425 self.inject_ast_indices = inject_ast_indices;
426 }
427
428 pub fn get_config(&self) -> &Config {
430 self.style.options.as_ref().unwrap_or(&self.default_config)
431 }
432
433 pub fn get_citation_config(&self) -> std::borrow::Cow<'_, Config> {
437 let base = self.get_config();
438 match self
439 .style
440 .citation
441 .as_ref()
442 .and_then(|citation| citation.options.as_ref())
443 {
444 Some(citation_options) => std::borrow::Cow::Owned(citation_options.merged_with(base)),
445 None => std::borrow::Cow::Borrowed(base),
446 }
447 }
448
449 pub fn get_bibliography_config(&self) -> std::borrow::Cow<'_, Config> {
453 let base = self.get_config();
454 match self
455 .style
456 .bibliography
457 .as_ref()
458 .and_then(|bibliography| bibliography.options.as_ref())
459 {
460 Some(bibliography_options) => {
461 std::borrow::Cow::Owned(bibliography_options.merged_with(base))
462 }
463 None => std::borrow::Cow::Borrowed(base),
464 }
465 }
466
467 pub fn get_bibliography_options(&self) -> std::borrow::Cow<'_, BibliographyConfig> {
469 match self
470 .style
471 .bibliography
472 .as_ref()
473 .and_then(|bibliography| bibliography.options.as_ref())
474 {
475 Some(bibliography_options) => {
476 std::borrow::Cow::Owned(bibliography_options.to_bibliography_config())
477 }
478 None => std::borrow::Cow::Owned(BibliographyConfig::default()),
479 }
480 }
481
482 pub fn sort_references<'a>(&self, references: Vec<&'a Reference>) -> Vec<&'a Reference> {
486 let mut sorted_refs = match self.resolved_bibliography_sort() {
487 Some((sort_spec, true)) => {
488 let sorter = crate::sorting::ReferenceSorter::new(&self.locale);
489 sorter.sort_references_with_id_tiebreak(references, &sort_spec)
490 }
491 Some((sort_spec, false)) => {
492 let sorter = crate::sorting::ReferenceSorter::new(&self.locale);
493 sorter.sort_references(references, &sort_spec)
494 }
495 None => references,
496 };
497
498 let bibliography_options = self.get_bibliography_options();
499 if let Some(partitioning) = bibliography_options.sort_partitioning.as_ref()
500 && crate::sort_partitioning::should_sort_flat(partitioning)
501 {
502 crate::sort_partitioning::sort_by_partition(
503 sorted_refs.as_mut_slice(),
504 &self.locale,
505 partitioning,
506 );
507 }
508
509 sorted_refs
510 }
511
512 pub fn sort_citation_items(
514 &self,
515 items: Vec<CitationItem>,
516 spec: &citum_schema::CitationSpec,
517 ) -> Vec<CitationItem> {
518 if let Some(sort_spec) = &spec.sort {
519 let mut items_with_refs: Vec<(CitationItem, &Reference)> = items
520 .into_iter()
521 .filter_map(|item| {
522 self.bibliography
523 .get(&item.id)
524 .map(|reference| (item, reference))
525 })
526 .collect();
527
528 let resolved_sort = sort_spec.resolve();
529 let sorter = crate::sorting::ReferenceSorter::new(&self.locale);
530 items_with_refs.sort_by(|left, right| {
531 for sort_key in &resolved_sort.template {
532 let cmp = sorter.compare_by_key(left.1, right.1, sort_key);
533 if cmp != std::cmp::Ordering::Equal {
534 return cmp;
535 }
536 }
537 std::cmp::Ordering::Equal
538 });
539
540 return items_with_refs
541 .into_iter()
542 .map(|(item, _reference)| item)
543 .collect();
544 }
545
546 items
547 }
548
549 pub fn calculate_hints(&self) -> HashMap<String, ProcHints> {
554 let citation_config = self.get_citation_config();
555 let config = citation_config.as_ref();
556 let bibliography_sort = self.resolved_bibliography_sort();
557
558 let disambiguator = if let Some((resolved_sort, _id_tiebreak)) = &bibliography_sort {
559 Disambiguator::with_group_sort(&self.bibliography, config, &self.locale, resolved_sort)
560 } else {
561 Disambiguator::new(&self.bibliography, config, &self.locale)
562 };
563
564 disambiguator.calculate_hints()
565 }
566}