citum_engine/processor/
note_context.rs1use super::Processor;
13use crate::reference::{Citation, CitationItem};
14use citum_schema::citation::Position;
15
16fn effective_locator_string(item: &CitationItem) -> Option<String> {
21 item.locator
22 .as_ref()
23 .map(citum_schema::citation::CitationLocator::canonical_string)
24}
25
26impl Processor {
27 pub(crate) fn annotate_positions(&self, citations: &mut [Citation]) {
39 let mut seen_items: std::collections::HashMap<String, Option<String>> =
40 std::collections::HashMap::new();
41 let mut previous_items: Option<Vec<(String, Option<String>)>> = None;
42
43 for citation in citations.iter_mut() {
44 if citation.position.is_some() {
45 let current_items: Vec<(String, Option<String>)> = citation
46 .items
47 .iter()
48 .map(|item| (item.id.clone(), effective_locator_string(item)))
49 .collect();
50 previous_items = Some(current_items);
51 for item in &citation.items {
52 seen_items.insert(item.id.clone(), effective_locator_string(item));
53 }
54 continue;
55 }
56
57 if citation.items.len() == 1 {
58 #[allow(clippy::indexing_slicing, reason = "citation.items.len() == 1")]
59 let current_id = &citation.items[0].id;
60 #[allow(clippy::indexing_slicing, reason = "citation.items.len() == 1")]
61 let current_locator = effective_locator_string(&citation.items[0]);
62
63 if let Some(previous) = previous_items.as_ref()
64 && previous.len() == 1
65 && let Some(prev_item) = previous.first()
66 && prev_item.0 == *current_id
67 {
68 let previous_locator = &prev_item.1;
69 citation.position = Some(if previous_locator == ¤t_locator {
70 Position::Ibid
71 } else {
72 Position::IbidWithLocator
73 });
74 }
75
76 if citation.position.is_none() {
77 citation.position = Some(if seen_items.contains_key(current_id) {
78 Position::Subsequent
79 } else {
80 Position::First
81 });
82 }
83
84 seen_items.insert(current_id.clone(), current_locator);
85 } else {
86 let all_seen = citation
87 .items
88 .iter()
89 .all(|item| seen_items.contains_key(&item.id));
90
91 citation.position = Some(if all_seen {
92 Position::Subsequent
93 } else {
94 Position::First
95 });
96
97 for item in &citation.items {
98 seen_items.insert(item.id.clone(), effective_locator_string(item));
99 }
100 }
101
102 previous_items = Some(
103 citation
104 .items
105 .iter()
106 .map(|item| (item.id.clone(), effective_locator_string(item)))
107 .collect(),
108 );
109 }
110 }
111
112 pub fn normalize_note_context(&self, citations: &[Citation]) -> Vec<Citation> {
117 if !self.is_note_style() {
118 return citations.to_vec();
119 }
120
121 let mut next_note = 1_u32;
122 citations
123 .iter()
124 .cloned()
125 .map(|mut citation| {
126 if let Some(note_number) = citation.note_number {
127 if note_number >= next_note {
128 next_note = note_number.saturating_add(1);
129 }
130 } else {
131 citation.note_number = Some(next_note);
132 next_note = next_note.saturating_add(1);
133 }
134 citation
135 })
136 .collect()
137 }
138}