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