1use crate::assembler::{AssembledGroup, AssembledGroupInstance, AssembledSegment, AssembledTree};
7use mig_types::schema::mig::{MigSchema, MigSegmentGroup};
8
9#[derive(Debug, Clone)]
11pub struct DisassembledSegment {
12 pub tag: String,
13 pub elements: Vec<Vec<String>>,
14}
15
16pub struct Disassembler<'a> {
18 mig: &'a MigSchema,
19}
20
21impl<'a> Disassembler<'a> {
22 pub fn new(mig: &'a MigSchema) -> Self {
23 Self { mig }
24 }
25
26 pub fn disassemble(&self, tree: &AssembledTree) -> Vec<DisassembledSegment> {
37 let mut output = Vec::new();
38
39 let pre_group = &tree.segments[..tree.post_group_start];
41 let mut consumed = vec![false; pre_group.len()];
42 for mig_seg in &self.mig.segments {
43 if let Some(idx) = pre_group
44 .iter()
45 .enumerate()
46 .position(|(i, s)| !consumed[i] && s.tag == mig_seg.id)
47 {
48 output.push(assembled_to_disassembled(&pre_group[idx]));
49 consumed[idx] = true;
50 }
51 }
52
53 let mut consumed_groups = vec![false; tree.groups.len()];
58 let mut mig_group_idx = 0;
59 while mig_group_idx < self.mig.segment_groups.len() {
60 let mig_group = &self.mig.segment_groups[mig_group_idx];
61
62 if mig_group.variant_code.is_some() {
64 let variant_count = self.mig.segment_groups[mig_group_idx..]
65 .iter()
66 .take_while(|g| g.id == mig_group.id && g.variant_code.is_some())
67 .count();
68 let variant_defs =
69 &self.mig.segment_groups[mig_group_idx..mig_group_idx + variant_count];
70
71 if let Some(idx) = tree
72 .groups
73 .iter()
74 .enumerate()
75 .position(|(i, g)| !consumed_groups[i] && g.group_id == mig_group.id)
76 {
77 if let Some(inter_segs) = tree.inter_group_segments.get(&idx) {
78 for seg in inter_segs {
79 output.push(assembled_to_disassembled(seg));
80 }
81 }
82 self.emit_variant_group(&tree.groups[idx], variant_defs, &mut output);
83 consumed_groups[idx] = true;
84 }
85 mig_group_idx += variant_count;
86 } else {
87 if let Some(idx) = tree
88 .groups
89 .iter()
90 .enumerate()
91 .position(|(i, g)| !consumed_groups[i] && g.group_id == mig_group.id)
92 {
93 if let Some(inter_segs) = tree.inter_group_segments.get(&idx) {
95 for seg in inter_segs {
96 output.push(assembled_to_disassembled(seg));
97 }
98 }
99 self.emit_group(&tree.groups[idx], mig_group, &mut output);
100 consumed_groups[idx] = true;
101 }
102 mig_group_idx += 1;
103 }
104 }
105
106 let trailing_idx = tree.groups.len();
109 if let Some(inter_segs) = tree.inter_group_segments.get(&trailing_idx) {
110 for seg in inter_segs {
111 output.push(assembled_to_disassembled(seg));
112 }
113 }
114
115 for seg in &tree.segments[tree.post_group_start..] {
117 output.push(assembled_to_disassembled(seg));
118 }
119
120 output
121 }
122
123 fn emit_group(
124 &self,
125 group: &AssembledGroup,
126 mig_group: &MigSegmentGroup,
127 output: &mut Vec<DisassembledSegment>,
128 ) {
129 for instance in &group.repetitions {
130 self.emit_group_instance(instance, mig_group, output);
131 }
132 }
133
134 fn emit_variant_group(
140 &self,
141 group: &AssembledGroup,
142 variant_defs: &[MigSegmentGroup],
143 output: &mut Vec<DisassembledSegment>,
144 ) {
145 for instance in &group.repetitions {
146 let entry_qual = instance
147 .segments
148 .first()
149 .and_then(|s| s.elements.first())
150 .and_then(|e| e.first())
151 .map(|v| v.as_str())
152 .unwrap_or("");
153
154 let variant_def = variant_defs
155 .iter()
156 .find(|v| {
157 v.variant_code
158 .as_deref()
159 .map(|vc| vc.eq_ignore_ascii_case(entry_qual))
160 .unwrap_or(false)
161 })
162 .unwrap_or(&variant_defs[0]); self.emit_group_instance(instance, variant_def, output);
165 }
166 }
167
168 fn emit_group_instance(
169 &self,
170 instance: &AssembledGroupInstance,
171 mig_group: &MigSegmentGroup,
172 output: &mut Vec<DisassembledSegment>,
173 ) {
174 let mut consumed = vec![false; instance.segments.len()];
182 for mig_seg in &mig_group.segments {
183 if let Some(idx) = instance
184 .segments
185 .iter()
186 .enumerate()
187 .position(|(i, s)| !consumed[i] && s.tag == mig_seg.id)
188 {
189 output.push(assembled_to_disassembled(&instance.segments[idx]));
190 consumed[idx] = true;
191 }
192 }
193 for (i, seg) in instance.segments.iter().enumerate() {
195 if !consumed[i] {
196 output.push(assembled_to_disassembled(seg));
197 }
198 }
199
200 for skipped in &instance.skipped_segments {
202 output.push(assembled_to_disassembled(skipped));
203 }
204
205 let mut consumed_child = vec![false; instance.child_groups.len()];
208 let mut nested_idx = 0;
209 while nested_idx < mig_group.nested_groups.len() {
210 let nested_mig = &mig_group.nested_groups[nested_idx];
211
212 if nested_mig.variant_code.is_some() {
213 let variant_count = mig_group.nested_groups[nested_idx..]
214 .iter()
215 .take_while(|g| g.id == nested_mig.id && g.variant_code.is_some())
216 .count();
217 let variant_defs = &mig_group.nested_groups[nested_idx..nested_idx + variant_count];
218
219 if let Some(idx) = instance
220 .child_groups
221 .iter()
222 .enumerate()
223 .position(|(i, g)| !consumed_child[i] && g.group_id == nested_mig.id)
224 {
225 self.emit_variant_group(&instance.child_groups[idx], variant_defs, output);
226 consumed_child[idx] = true;
227 }
228 nested_idx += variant_count;
229 } else {
230 if let Some(idx) = instance
231 .child_groups
232 .iter()
233 .enumerate()
234 .position(|(i, g)| !consumed_child[i] && g.group_id == nested_mig.id)
235 {
236 self.emit_group(&instance.child_groups[idx], nested_mig, output);
237 consumed_child[idx] = true;
238 }
239 nested_idx += 1;
240 }
241 }
242 }
243}
244
245fn assembled_to_disassembled(seg: &AssembledSegment) -> DisassembledSegment {
246 DisassembledSegment {
247 tag: seg.tag.clone(),
248 elements: seg.elements.clone(),
249 }
250}
251
252#[cfg(test)]
253mod tests {
254 use super::*;
255 use crate::assembler::{
256 AssembledGroup, AssembledGroupInstance, AssembledSegment, AssembledTree,
257 };
258 use crate::test_support::{make_mig_group, make_mig_segment};
259 use mig_types::schema::mig::MigSchema;
260
261 #[test]
262 fn test_disassemble_top_level_only() {
263 let mig = MigSchema {
264 message_type: "UTILMD".to_string(),
265 variant: Some("Strom".to_string()),
266 version: "S2.1".to_string(),
267 publication_date: "2025-03-20".to_string(),
268 author: "BDEW".to_string(),
269 format_version: "FV2504".to_string(),
270 source_file: "test".to_string(),
271 segments: vec![make_mig_segment("UNH"), make_mig_segment("BGM")],
272 segment_groups: vec![],
273 };
274
275 let tree = AssembledTree {
276 segments: vec![
277 AssembledSegment {
278 tag: "UNH".to_string(),
279 elements: vec![
280 vec!["1".to_string()],
281 vec![
282 "UTILMD".to_string(),
283 "D".to_string(),
284 "11A".to_string(),
285 "UN".to_string(),
286 "S2.1".to_string(),
287 ],
288 ],
289 mig_number: None,
290 },
291 AssembledSegment {
292 tag: "BGM".to_string(),
293 elements: vec![
294 vec!["E01".to_string()],
295 vec!["MSG001".to_string()],
296 vec!["9".to_string()],
297 ],
298 mig_number: None,
299 },
300 ],
301 groups: vec![],
302 post_group_start: 2,
303 inter_group_segments: std::collections::BTreeMap::new(),
304 };
305
306 let disassembler = Disassembler::new(&mig);
307 let segments = disassembler.disassemble(&tree);
308
309 assert_eq!(segments.len(), 2);
310 assert_eq!(segments[0].tag, "UNH");
311 assert_eq!(segments[1].tag, "BGM");
312 assert_eq!(segments[0].elements[0], vec!["1"]);
313 }
314
315 #[test]
316 fn test_disassemble_with_groups() {
317 let mig = MigSchema {
318 message_type: "UTILMD".to_string(),
319 variant: None,
320 version: "S2.1".to_string(),
321 publication_date: "".to_string(),
322 author: "".to_string(),
323 format_version: "FV2504".to_string(),
324 source_file: "test".to_string(),
325 segments: vec![make_mig_segment("UNH"), make_mig_segment("BGM")],
326 segment_groups: vec![make_mig_group("SG2", vec!["NAD", "LOC"], vec![])],
327 };
328
329 let tree = AssembledTree {
330 segments: vec![
331 AssembledSegment {
332 tag: "UNH".to_string(),
333 elements: vec![vec!["1".to_string()]],
334 mig_number: None,
335 },
336 AssembledSegment {
337 tag: "BGM".to_string(),
338 elements: vec![vec!["E01".to_string()]],
339 mig_number: None,
340 },
341 ],
342 post_group_start: 2,
343 groups: vec![AssembledGroup {
344 group_id: "SG2".to_string(),
345 repetitions: vec![
346 AssembledGroupInstance {
347 segments: vec![AssembledSegment {
348 tag: "NAD".to_string(),
349 elements: vec![vec!["MS".to_string()]],
350 mig_number: None,
351 }],
352 child_groups: vec![],
353 entry_mig_number: None,
354 variant_mig_numbers: vec![],
355 skipped_segments: vec![],
356 skipped_positions: Vec::new(),
357 },
358 AssembledGroupInstance {
359 segments: vec![AssembledSegment {
360 tag: "NAD".to_string(),
361 elements: vec![vec!["MR".to_string()]],
362 mig_number: None,
363 }],
364 child_groups: vec![],
365 entry_mig_number: None,
366 variant_mig_numbers: vec![],
367 skipped_segments: vec![],
368 skipped_positions: Vec::new(),
369 },
370 ],
371 }],
372 inter_group_segments: std::collections::BTreeMap::new(),
373 };
374
375 let disassembler = Disassembler::new(&mig);
376 let segments = disassembler.disassemble(&tree);
377
378 assert_eq!(segments.len(), 4); assert_eq!(segments[0].tag, "UNH");
380 assert_eq!(segments[1].tag, "BGM");
381 assert_eq!(segments[2].tag, "NAD");
382 assert_eq!(segments[2].elements[0][0], "MS");
383 assert_eq!(segments[3].tag, "NAD");
384 assert_eq!(segments[3].elements[0][0], "MR");
385 }
386
387 #[test]
388 fn test_disassemble_nested_groups() {
389 let sg3 = make_mig_group("SG3", vec!["CTA", "COM"], vec![]);
390 let mig = MigSchema {
391 message_type: "UTILMD".to_string(),
392 variant: None,
393 version: "S2.1".to_string(),
394 publication_date: "".to_string(),
395 author: "".to_string(),
396 format_version: "FV2504".to_string(),
397 source_file: "test".to_string(),
398 segments: vec![make_mig_segment("UNH")],
399 segment_groups: vec![make_mig_group("SG2", vec!["NAD"], vec![sg3])],
400 };
401
402 let tree = AssembledTree {
403 segments: vec![AssembledSegment {
404 tag: "UNH".to_string(),
405 elements: vec![vec!["1".to_string()]],
406 mig_number: None,
407 }],
408 post_group_start: 1,
409 groups: vec![AssembledGroup {
410 group_id: "SG2".to_string(),
411 repetitions: vec![AssembledGroupInstance {
412 segments: vec![AssembledSegment {
413 tag: "NAD".to_string(),
414 elements: vec![vec!["MS".to_string()]],
415 mig_number: None,
416 }],
417 child_groups: vec![AssembledGroup {
418 group_id: "SG3".to_string(),
419 repetitions: vec![AssembledGroupInstance {
420 segments: vec![
421 AssembledSegment {
422 tag: "CTA".to_string(),
423 elements: vec![vec!["IC".to_string()]],
424 mig_number: None,
425 },
426 AssembledSegment {
427 tag: "COM".to_string(),
428 elements: vec![vec![
429 "040@ex.com".to_string(),
430 "EM".to_string(),
431 ]],
432 mig_number: None,
433 },
434 ],
435 child_groups: vec![],
436 entry_mig_number: None,
437 variant_mig_numbers: vec![],
438 skipped_segments: vec![],
439 skipped_positions: Vec::new(),
440 }],
441 }],
442 entry_mig_number: None,
443 variant_mig_numbers: vec![],
444 skipped_segments: vec![],
445 skipped_positions: Vec::new(),
446 }],
447 }],
448 inter_group_segments: std::collections::BTreeMap::new(),
449 };
450
451 let disassembler = Disassembler::new(&mig);
452 let segments = disassembler.disassemble(&tree);
453
454 assert_eq!(segments.len(), 4); assert_eq!(segments[0].tag, "UNH");
456 assert_eq!(segments[1].tag, "NAD");
457 assert_eq!(segments[2].tag, "CTA");
458 assert_eq!(segments[3].tag, "COM");
459 }
460
461 #[test]
462 fn test_disassemble_variant_groups_uses_per_variant_ordering() {
463 use crate::test_support::make_mig_group_with_variant;
464
465 let sg8_zd7 = make_mig_group_with_variant("SG8", vec!["SEQ", "RFF", "DTM"], vec![], "ZD7");
469 let sg8_z98 = make_mig_group_with_variant("SG8", vec!["SEQ", "CCI", "CAV"], vec![], "Z98");
470
471 let mig = MigSchema {
472 message_type: "UTILMD".to_string(),
473 variant: None,
474 version: "S2.1".to_string(),
475 publication_date: "".to_string(),
476 author: "".to_string(),
477 format_version: "FV2504".to_string(),
478 source_file: "test".to_string(),
479 segments: vec![make_mig_segment("UNH")],
480 segment_groups: vec![sg8_zd7, sg8_z98],
481 };
482
483 let tree = AssembledTree {
485 segments: vec![AssembledSegment {
486 tag: "UNH".to_string(),
487 elements: vec![vec!["1".to_string()]],
488 mig_number: None,
489 }],
490 post_group_start: 1,
491 groups: vec![AssembledGroup {
492 group_id: "SG8".to_string(),
493 repetitions: vec![
494 AssembledGroupInstance {
496 segments: vec![
497 AssembledSegment {
498 tag: "SEQ".to_string(),
499 elements: vec![vec!["ZD7".to_string()]],
500 mig_number: None,
501 },
502 AssembledSegment {
503 tag: "DTM".to_string(),
504 elements: vec![vec!["303".to_string()]],
505 mig_number: None,
506 },
507 AssembledSegment {
508 tag: "RFF".to_string(),
509 elements: vec![vec!["Z13".to_string()]],
510 mig_number: None,
511 },
512 ],
513 child_groups: vec![],
514 entry_mig_number: None,
515 variant_mig_numbers: vec![],
516 skipped_segments: vec![],
517 skipped_positions: Vec::new(),
518 },
519 AssembledGroupInstance {
521 segments: vec![
522 AssembledSegment {
523 tag: "SEQ".to_string(),
524 elements: vec![vec!["Z98".to_string()]],
525 mig_number: None,
526 },
527 AssembledSegment {
528 tag: "CAV".to_string(),
529 elements: vec![vec!["Z91".to_string()]],
530 mig_number: None,
531 },
532 AssembledSegment {
533 tag: "CCI".to_string(),
534 elements: vec![vec!["".to_string()]],
535 mig_number: None,
536 },
537 ],
538 child_groups: vec![],
539 entry_mig_number: None,
540 variant_mig_numbers: vec![],
541 skipped_segments: vec![],
542 skipped_positions: Vec::new(),
543 },
544 AssembledGroupInstance {
546 segments: vec![
547 AssembledSegment {
548 tag: "SEQ".to_string(),
549 elements: vec![vec!["ZD7".to_string()]],
550 mig_number: None,
551 },
552 AssembledSegment {
553 tag: "RFF".to_string(),
554 elements: vec![vec!["Z34".to_string()]],
555 mig_number: None,
556 },
557 ],
558 child_groups: vec![],
559 entry_mig_number: None,
560 variant_mig_numbers: vec![],
561 skipped_segments: vec![],
562 skipped_positions: Vec::new(),
563 },
564 ],
565 }],
566 inter_group_segments: std::collections::BTreeMap::new(),
567 };
568
569 let disassembler = Disassembler::new(&mig);
570 let segments = disassembler.disassemble(&tree);
571
572 assert_eq!(segments[0].tag, "UNH");
574
575 assert_eq!(segments[1].tag, "SEQ");
577 assert_eq!(segments[1].elements[0][0], "ZD7");
578 assert_eq!(segments[2].tag, "RFF"); assert_eq!(segments[3].tag, "DTM"); assert_eq!(segments[4].tag, "SEQ");
583 assert_eq!(segments[4].elements[0][0], "Z98");
584 assert_eq!(segments[5].tag, "CCI"); assert_eq!(segments[6].tag, "CAV"); assert_eq!(segments[7].tag, "SEQ");
589 assert_eq!(segments[7].elements[0][0], "ZD7");
590 assert_eq!(segments[8].tag, "RFF");
591
592 assert_eq!(segments.len(), 9);
593 }
594
595 #[test]
596 fn test_disassemble_empty_tree() {
597 let mig = MigSchema {
598 message_type: "UTILMD".to_string(),
599 variant: None,
600 version: "S2.1".to_string(),
601 publication_date: "".to_string(),
602 author: "".to_string(),
603 format_version: "FV2504".to_string(),
604 source_file: "test".to_string(),
605 segments: vec![make_mig_segment("UNH")],
606 segment_groups: vec![],
607 };
608
609 let tree = AssembledTree {
610 segments: vec![],
611 groups: vec![],
612 post_group_start: 0,
613 inter_group_segments: std::collections::BTreeMap::new(),
614 };
615
616 let disassembler = Disassembler::new(&mig);
617 let segments = disassembler.disassemble(&tree);
618 assert!(segments.is_empty());
619 }
620}