hl7_parser/locate/
mod.rs

1use std::{collections::HashMap, fmt::Display, ops::Range};
2
3use crate::{
4    message::{
5        Component, DecodedSeparatorsDisplay, Field, Repeat, Segment, Separators, Subcomponent,
6    },
7    Message,
8};
9
10/// Results from locating a cursor within a message
11#[derive(Debug, Clone, PartialEq, Eq)]
12#[cfg_attr(feature = "serde", derive(serde::Serialize))]
13pub struct LocatedCursor<'s> {
14    pub message: &'s Message<'s>,
15    /// The (segment name, and 1-based segment index) containing the cursor
16    pub segment: Option<(&'s str, usize, &'s Segment<'s>)>,
17    /// The (1-based field index, field) containing the cursor
18    pub field: Option<(usize, &'s Field<'s>)>,
19    /// The (1-based repeat index, repeat) containing the cursor
20    pub repeat: Option<(usize, &'s Repeat<'s>)>,
21    /// The (1-based component index, component) containing the cursor
22    pub component: Option<(usize, &'s Component<'s>)>,
23    /// The (1-based sub-component index, sub-component) containing the cursor
24    pub sub_component: Option<(usize, &'s Subcomponent<'s>)>,
25}
26
27/// Locate a cursor within a message. The offset is the character (distinct from byte) offset
28/// within the message.
29pub fn locate_cursor<'m>(message: &'m Message<'m>, offset: usize) -> Option<LocatedCursor<'m>> {
30    let mut cursor = LocatedCursor {
31        message,
32        segment: None,
33        field: None,
34        repeat: None,
35        component: None,
36        sub_component: None,
37    };
38
39    if offset >= message.source.len() {
40        return None;
41    }
42    let mut seg_indices: HashMap<&str, usize> = HashMap::new();
43    for seg in message.segments() {
44        *seg_indices.entry(seg.name).or_insert(0) += 1;
45        // note: inclusive range at both ends to include the segment separator
46        if offset >= seg.range.start && offset <= seg.range.end {
47            cursor.segment = Some((
48                seg.name,
49                *seg_indices.get(seg.name).expect("seg exists in hashmap"),
50                seg,
51            ));
52            break;
53        }
54    }
55
56    cursor.segment?;
57    for (i, field) in cursor.segment.as_ref().unwrap().2.fields().enumerate() {
58        // note: inclusive range at both ends to include the field separator
59        if offset >= field.range.start && offset <= field.range.end {
60            cursor.field = Some((i + 1, field));
61            break;
62        }
63    }
64
65    if cursor.field.is_none() {
66        return Some(cursor);
67    }
68    for (i, repeat) in cursor.field.as_ref().unwrap().1.repeats().enumerate() {
69        // note: inclusive range at both ends to include the repetition separator
70        if offset >= repeat.range.start && offset <= repeat.range.end {
71            cursor.repeat = Some((i + 1, repeat));
72            break;
73        }
74    }
75
76    if cursor.repeat.is_none() {
77        return Some(cursor);
78    }
79    for (i, component) in cursor.repeat.as_ref().unwrap().1.components().enumerate() {
80        // note: inclusive range at both ends to include the component separator
81        if offset >= component.range.start && offset <= component.range.end {
82            cursor.component = Some((i + 1, component));
83            break;
84        }
85    }
86
87    if cursor.component.is_none() {
88        return Some(cursor);
89    }
90    for (i, sub_component) in cursor
91        .component
92        .as_ref()
93        .unwrap()
94        .1
95        .subcomponents()
96        .enumerate()
97    {
98        // note: inclusive range at both ends to include the sub-component separator
99        if offset >= sub_component.range.start && offset <= sub_component.range.end {
100            cursor.sub_component = Some((i + 1, sub_component));
101            break;
102        }
103    }
104
105    Some(cursor)
106}
107
108impl Display for LocatedCursor<'_> {
109    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
110        if let Some((seg_name, seg_idx, _)) = self.segment {
111            write!(f, "{}", seg_name)?;
112            if self.message.segment_count(seg_name) > 1 {
113                write!(f, "[{}]", seg_idx)?;
114            }
115        }
116        if let Some((field_idx, field)) = self.field {
117            write!(f, ".{}", field_idx)?;
118            if let Some((repeat_idx, repeat)) = self.repeat {
119                if field.has_repeats() {
120                    write!(f, "[{}]", repeat_idx)?;
121                }
122                if let Some((component_idx, component)) = self.component {
123                    if repeat.has_components() {
124                        write!(f, ".{}", component_idx)?;
125                    }
126                    if let Some((sub_component_idx, _)) = self.sub_component {
127                        if component.has_subcomponents() {
128                            write!(f, ".{}", sub_component_idx)?;
129                        }
130                    }
131                }
132            }
133        }
134        Ok(())
135    }
136}
137
138impl<'m> LocatedCursor<'m> {
139    /// Get the raw value of the field, repeat, component, or sub-component at the cursor location.
140    /// Returns `None` if the cursor is not located at a field, repeat, component, or
141    /// sub-component.
142    pub fn raw_value(&self) -> Option<&str> {
143        self.sub_component
144            .map(|(_, sub_component)| sub_component.raw_value())
145            .or_else(|| self.component.map(|(_, component)| component.raw_value()))
146            .or_else(|| self.repeat.map(|(_, repeat)| repeat.raw_value()))
147            .or_else(|| self.field.map(|(_, field)| field.raw_value()))
148    }
149
150    /// Get the range of the field, repeat, component, or sub-component at the cursor location.
151    /// Returns `None` if the cursor is not located at a field, repeat, component, or
152    /// sub-component.
153    pub fn range(&self) -> Option<&'m Range<usize>> {
154        self.sub_component
155            .map(|(_, sub_component)| &sub_component.range)
156            .or_else(|| self.component.map(|(_, component)| &component.range))
157            .or_else(|| self.repeat.map(|(_, repeat)| &repeat.range))
158            .or_else(|| self.field.map(|(_, field)| &field.range))
159    }
160
161    /// Get the decoded value of the field, repeat, component, or sub-component at the cursor
162    /// location. Returns `None` if the cursor is not located at a field, repeat, component, or
163    /// sub-component.
164    pub fn value(&'m self, separators: &'m Separators) -> Option<DecodedSeparatorsDisplay<'m>> {
165        self.raw_value()
166            .map(|raw_value| separators.decode(raw_value))
167    }
168}
169
170#[cfg(test)]
171mod test {
172    use super::*;
173
174    #[test]
175    fn can_locate_cursor_in_segment() {
176        let message = Message::parse("MSH|^~\\&|asdf\rPID|1|0").unwrap();
177        let cursor = locate_cursor(&message, 0).expect("cursor is located");
178        assert_eq!(cursor.segment.unwrap().0, "MSH");
179        assert_eq!(cursor.segment.unwrap().1, 1);
180
181        let cursor = locate_cursor(&message, 18).expect("cursor is located");
182        assert_eq!(cursor.segment.unwrap().0, "PID");
183        assert_eq!(cursor.segment.unwrap().1, 1);
184        assert_eq!(cursor.field.unwrap().0, 1);
185        assert_eq!(cursor.field.unwrap().1.raw_value(), "1");
186    }
187
188    #[test]
189    fn can_locate_cursor_in_repeated_segment() {
190        let message = Message::parse("MSH|^~\\&|asdf\rPID|1|0\rPID|2|1").unwrap();
191        let cursor = locate_cursor(&message, 26).expect("cursor is located");
192        assert_eq!(cursor.segment.unwrap().0, "PID");
193        assert_eq!(cursor.segment.unwrap().1, 2);
194        assert_eq!(cursor.field.unwrap().0, 1);
195        assert_eq!(cursor.field.unwrap().1.raw_value(), "2");
196    }
197
198    #[test]
199    fn can_locate_cursor_at_field_boundaries() {
200        let message = Message::parse("MSH|^~\\&|asdf\rPID|1|0").unwrap();
201        let cursor = locate_cursor(&message, 19).expect("cursor is located");
202        assert_eq!(cursor.segment.unwrap().0, "PID");
203        assert_eq!(cursor.segment.unwrap().1, 1);
204        assert_eq!(cursor.field.unwrap().0, 1);
205        assert_eq!(cursor.field.unwrap().1.raw_value(), "1");
206
207        let message = Message::parse("MSH|^~\\&|asdf\rPID||0").unwrap();
208        let cursor = locate_cursor(&message, 18).expect("cursor is located");
209        assert_eq!(cursor.segment.unwrap().0, "PID");
210        assert_eq!(cursor.segment.unwrap().1, 1);
211        assert_eq!(cursor.field.unwrap().0, 1);
212        assert_eq!(cursor.field.unwrap().1.raw_value(), "");
213    }
214
215    #[test]
216    fn can_get_cursor_location_value() {
217        let message = Message::parse("MSH|^~\\&|asdf\rPID|1\\S\\2|0").unwrap();
218        let cursor = locate_cursor(&message, 19).expect("cursor is located");
219        let value = cursor
220            .value(&message.separators)
221            .expect("value is decoded")
222            .to_string();
223        assert_eq!(value, "1^2");
224    }
225}