edifact_mapper/
element_scopes.rs1use std::collections::HashMap;
9
10use mig_assembly::assembler::{AssembledGroup, AssembledGroupInstance, AssembledTree};
11use mig_bo4e::MappingEngine;
12use serde_json::Value;
13
14pub type ElementKey = (String, Option<usize>);
17
18pub type InstancePath = Vec<(String, usize)>;
20
21pub fn entity_element_scopes(
31 engine: &MappingEngine,
32 json: &Value,
33 tree: &AssembledTree,
34) -> HashMap<ElementKey, InstancePath> {
35 let mut scopes = HashMap::new();
36 let Some(entities) = json.as_object() else {
37 return scopes;
38 };
39 for (key, value) in entities {
40 let indices: Vec<Option<usize>> = match value.as_array() {
41 Some(elements) => (0..elements.len()).map(Some).collect(),
42 None => vec![None],
43 };
44 for index in indices {
45 let mut without = json.clone();
46 match index {
47 Some(i) => {
48 without[key.as_str()]
49 .as_array_mut()
50 .expect("array")
51 .remove(i);
52 }
53 None => {
54 without.as_object_mut().expect("object").remove(key);
55 }
56 }
57 let reduced = engine.map_all_reverse(&without, None);
58 let mut removed = Vec::new();
59 removed_instances(&tree.groups, &reduced.groups, &mut Vec::new(), &mut removed);
60 if let [path] = &removed[..] {
61 scopes.insert((key.clone(), index), path.clone());
62 }
63 }
64 }
65 scopes
66}
67
68fn removed_instances(
76 full: &[AssembledGroup],
77 reduced: &[AssembledGroup],
78 prefix: &mut InstancePath,
79 out: &mut Vec<InstancePath>,
80) {
81 for group in full {
82 let remaining: &[AssembledGroupInstance] = reduced
83 .iter()
84 .find(|g| g.group_id == group.group_id)
85 .map_or(&[], |g| &g.repetitions);
86 let repetitions = &group.repetitions;
87 if repetitions.len() == remaining.len() {
88 for (i, (a, b)) in repetitions.iter().zip(remaining).enumerate() {
89 if a != b {
90 prefix.push((group.group_id.clone(), i));
91 removed_instances(&a.child_groups, &b.child_groups, prefix, out);
92 prefix.pop();
93 }
94 }
95 continue;
96 }
97 let mut next = 0;
101 for (i, repetition) in repetitions.iter().enumerate() {
102 if remaining.get(next) == Some(repetition) {
103 next += 1;
104 } else {
105 let mut path = prefix.clone();
106 path.push((group.group_id.clone(), i));
107 out.push(path);
108 }
109 }
110 }
111}
112
113#[cfg(test)]
114mod tests {
115 use super::*;
116 use mig_assembly::assembler::AssembledSegment;
117
118 fn rep(seq: &str) -> AssembledGroupInstance {
119 AssembledGroupInstance {
120 segments: vec![AssembledSegment {
121 tag: "SEQ".to_string(),
122 elements: vec![vec![seq.to_string()]],
123 mig_number: None,
124 segment_number: None,
125 }],
126 child_groups: vec![],
127 entry_mig_number: None,
128 variant_mig_numbers: vec![],
129 skipped_segments: vec![],
130 skipped_positions: Vec::new(),
131 }
132 }
133
134 fn sg8(seqs: &[&str]) -> Vec<AssembledGroup> {
135 vec![AssembledGroup {
136 group_id: "SG8".to_string(),
137 repetitions: seqs.iter().map(|s| rep(s)).collect(),
138 }]
139 }
140
141 fn removed(full: &[&str], reduced: &[&str]) -> Vec<InstancePath> {
142 let mut out = Vec::new();
143 removed_instances(&sg8(full), &sg8(reduced), &mut Vec::new(), &mut out);
144 out
145 }
146
147 #[test]
148 fn the_missing_repetition_is_found_by_order() {
149 assert_eq!(
150 removed(&["Z01", "Z03", "Z20"], &["Z01", "Z20"]),
151 [vec![("SG8".to_string(), 1)]]
152 );
153 }
154
155 #[test]
156 fn a_reduced_tree_that_does_not_line_up_names_no_single_instance() {
157 let out = removed(&["Z01", "Z03", "Z20"], &["Z01", "Z99"]);
158 assert_ne!(out.len(), 1, "{out:?}");
159 }
160}