use crate::dictionary::{Dictionary, VARIABLE};
use er7::{Component, Repetition, Segment, Separators};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Kind {
Group,
Segment,
Field,
Component,
Subcomponent,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Node {
name: String,
path: String,
kind: Kind,
text: String,
null: bool,
children: Vec<Node>,
}
impl Node {
#[must_use]
pub fn name(&self) -> &str {
&self.name
}
#[must_use]
pub fn path(&self) -> &str {
&self.path
}
#[must_use]
pub fn kind(&self) -> Kind {
self.kind
}
#[must_use]
pub fn text(&self) -> &str {
&self.text
}
#[must_use]
pub fn is_null(&self) -> bool {
self.null
}
#[must_use]
pub fn is_leaf(&self) -> bool {
self.children.is_empty()
}
#[must_use]
pub fn children(&self) -> &[Node] {
&self.children
}
#[must_use]
pub fn child(&self, name: &str) -> Option<&Node> {
self.children.iter().find(|child| child.name == name)
}
pub fn children_named<'a>(&'a self, name: &'a str) -> impl Iterator<Item = &'a Node> {
self.children.iter().filter(move |child| child.name == name)
}
#[must_use]
pub fn find(&self, name: &str) -> Option<&Node> {
self.descendants().find(|node| node.name == name)
}
pub fn find_all<'a>(&'a self, name: &'a str) -> impl Iterator<Item = &'a Node> {
self.descendants().filter(move |node| node.name == name)
}
#[must_use]
pub fn descendants(&self) -> Descendants<'_> {
Descendants {
stack: self.children.iter().rev().collect(),
}
}
}
#[derive(Debug)]
pub struct Descendants<'a> {
stack: Vec<&'a Node>,
}
impl<'a> Iterator for Descendants<'a> {
type Item = &'a Node;
fn next(&mut self) -> Option<&'a Node> {
let node = self.stack.pop()?;
self.stack.extend(node.children.iter().rev());
Some(node)
}
}
pub(crate) fn root(name: &str, children: Vec<Node>) -> Node {
let text = children
.iter()
.map(|child| child.text.as_str())
.collect::<Vec<&str>>()
.join("\r");
Node {
name: name.to_string(),
path: String::new(),
kind: Kind::Group,
text,
null: false,
children,
}
}
pub(crate) fn group(root_name: &str, name: &str, children: Vec<Node>) -> Node {
let mut node = root(&format!("{root_name}.{name}"), children);
node.path = String::new();
node
}
pub(crate) fn segment(
seg: &Segment,
occurrence: usize,
dictionary: &Dictionary,
separators: &Separators,
) -> Node {
let base = format!("{}[{occurrence}]", seg.name);
let variable = dictionary.variable_type(seg).map(str::to_string);
let mut children = Vec::new();
for (index, field) in seg.fields.iter().enumerate() {
if field.is_empty() {
continue;
}
let number = index + 1;
let name = format!("{}.{number}", seg.name);
let data_type = match dictionary.field_type(&seg.name, number) {
Some(VARIABLE) => variable.as_deref(),
other => other,
};
for (repetition, occurrence) in field.repetitions.iter().enumerate() {
if occurrence.is_empty() {
continue;
}
children.push(field_node(
&name,
&format!("{base}-{number}[{}]", repetition + 1),
data_type,
occurrence,
dictionary,
separators,
));
}
}
Node {
name: seg.name.clone(),
path: base,
kind: Kind::Segment,
text: seg.to_text(separators),
null: false,
children,
}
}
fn field_node(
name: &str,
path: &str,
data_type: Option<&str>,
repetition: &Repetition,
dictionary: &Dictionary,
separators: &Separators,
) -> Node {
let text = repetition.to_text(separators);
let mut node = Node {
name: name.to_string(),
path: path.to_string(),
kind: Kind::Field,
text,
null: repetition.is_null(),
children: Vec::new(),
};
if repetition.is_null() {
return node;
}
if let Some(components) = data_type.and_then(|dt| dictionary.composite_components(dt)) {
let data_type = data_type.unwrap_or_default();
for (index, component) in repetition.components.iter().enumerate() {
if component.is_empty() {
continue;
}
node.children.push(component_node(
&format!("{data_type}.{}", index + 1),
&format!("{path}.{}", index + 1),
components.get(index).map(String::as_str),
component,
dictionary,
separators,
));
}
return node;
}
if let [only] = repetition.components.as_slice()
&& only.subcomponents.len() <= 1
{
return node;
}
for (index, component) in repetition.components.iter().enumerate() {
if component.is_empty() {
continue;
}
node.children.push(component_node(
&format!("{name}.{}", index + 1),
&format!("{path}.{}", index + 1),
None,
component,
dictionary,
separators,
));
}
node
}
fn component_node(
name: &str,
path: &str,
data_type: Option<&str>,
component: &Component,
dictionary: &Dictionary,
separators: &Separators,
) -> Node {
let mut node = Node {
name: name.to_string(),
path: path.to_string(),
kind: Kind::Component,
text: component.to_text(separators),
null: component.is_null(),
children: Vec::new(),
};
if component.is_null() || component.subcomponents.len() <= 1 {
return node;
}
let composite = data_type.filter(|dt| dictionary.is_composite(dt));
for (index, subcomponent) in component.subcomponents.iter().enumerate() {
if subcomponent.is_empty() {
continue;
}
let number = index + 1;
node.children.push(Node {
name: match composite {
Some(data_type) => format!("{data_type}.{number}"),
None => format!("{name}.{number}"),
},
path: format!("{path}.{number}"),
kind: Kind::Subcomponent,
text: subcomponent.value(separators).into_owned(),
null: subcomponent.is_null(),
children: Vec::new(),
});
}
node
}
#[cfg(test)]
mod tests {
use super::*;
fn tree(text: &str) -> Node {
crate::parse(text).unwrap().tree()
}
const HEADER: &str = "MSH|^~\\&|hphis||EPIC||20131011093851||ORU^R01|14AAACVDD|P|2.5";
#[test]
fn names_known_types_after_the_type_and_the_rest_positionally() {
let tree = tree(&format!("{HEADER}\rPID|1||241900||TEST^FOUAZ\rZPD|a^b"));
let pid = tree.find("PID").unwrap();
let name = pid.child("PID.5").unwrap();
assert_eq!(name.text(), "TEST^FOUAZ");
assert_eq!(name.child("XPN.1").unwrap().text(), "TEST");
assert_eq!(name.child("XPN.2").unwrap().text(), "FOUAZ");
let zpd = tree.find("ZPD").unwrap();
assert_eq!(
zpd.child("ZPD.1").unwrap().child("ZPD.1.1").unwrap().text(),
"a"
);
assert_eq!(
zpd.child("ZPD.1").unwrap().child("ZPD.1.2").unwrap().text(),
"b"
);
}
#[test]
fn every_node_carries_the_path_that_reads_it_back() {
let message = crate::parse(&format!("{HEADER}\rPID|1||241900||TEST^FOUAZ")).unwrap();
let tree = message.tree();
let given = tree.find("XPN.2").unwrap();
assert_eq!(given.path(), "PID[1]-5[1].2");
assert_eq!(message.get(given.path()).unwrap().as_deref(), Some("FOUAZ"));
}
#[test]
fn repetitions_are_separate_siblings() {
let tree = tree(&format!("{HEADER}\rPID|1||A~B~C"));
let pid = tree.find("PID").unwrap();
let ids: Vec<&str> = pid.children_named("PID.3").map(Node::text).collect();
assert_eq!(ids, ["A", "B", "C"]);
assert_eq!(pid.child("PID.3").unwrap().path(), "PID[1]-3[1]");
assert_eq!(
pid.children_named("PID.3").nth(2).unwrap().path(),
"PID[1]-3[3]"
);
}
#[test]
fn the_explicit_null_survives() {
let tree = tree(&format!("{HEADER}\rPID|1||\"\""));
let field = tree.find("PID").unwrap().child("PID.3").unwrap();
assert!(field.is_null(), "explicit null must not read as absent");
assert!(tree.find("PID").unwrap().child("PID.4").is_none());
}
#[test]
fn obx_5_takes_its_type_from_obx_2() {
let coded = tree(&format!("{HEADER}\rOBX|1|CE|X||a^b^c"));
let value = coded.find("OBX").unwrap().child("OBX.5").unwrap();
assert_eq!(value.child("CE.1").unwrap().text(), "a");
let numeric = tree(&format!("{HEADER}\rOBX|1|NM|X||7.4"));
assert_eq!(
numeric.find("OBX").unwrap().child("OBX.5").unwrap().text(),
"7.4"
);
}
#[test]
fn groups_nest_under_the_structure_id() {
let tree = tree(&format!("{HEADER}\rPID|1\rOBR|1\rOBX|1|NM|X||7"));
assert_eq!(tree.name(), "ORU_R01");
let result = tree.child("ORU_R01.PATIENT_RESULT").unwrap();
let order = result.child("ORU_R01.ORDER_OBSERVATION").unwrap();
assert!(
order
.child("ORU_R01.OBSERVATION")
.unwrap()
.child("OBX")
.is_some()
);
assert!(tree.find("OBX").is_some());
}
#[test]
fn descendants_walks_everything_once() {
let tree = tree(&format!("{HEADER}\rPID|1||A~B"));
let count = tree.descendants().count();
let named = tree.find_all("PID.3").count();
assert_eq!(named, 2);
assert!(count > named);
}
}