1use crate::disassembler::DisassembledSegment;
7use edifact_primitives::EdifactDelimiters;
8
9pub fn render_edifact(segments: &[DisassembledSegment], delimiters: &EdifactDelimiters) -> String {
17 let mut out = String::new();
18
19 for seg in segments {
20 render_segment(seg, delimiters, &mut out);
21 }
22
23 out
24}
25
26fn render_segment(seg: &DisassembledSegment, delimiters: &EdifactDelimiters, out: &mut String) {
27 let elem_sep = delimiters.element as char;
28 let comp_sep = delimiters.component as char;
29 let seg_term = delimiters.segment as char;
30
31 out.push_str(&seg.tag);
32
33 for element in &seg.elements {
34 out.push(elem_sep);
35 for (j, component) in element.iter().enumerate() {
37 if j > 0 {
38 out.push(comp_sep);
39 }
40 escape_component(component, delimiters, out);
41 }
42 }
43
44 out.push(seg_term);
45}
46
47fn escape_component(value: &str, delimiters: &EdifactDelimiters, out: &mut String) {
56 let release = delimiters.release;
57 let special = [
58 delimiters.element,
59 delimiters.component,
60 delimiters.segment,
61 delimiters.release,
62 ];
63
64 let needs_escape = value.bytes().any(|b| special.contains(&b));
65 if !needs_escape {
66 out.push_str(value);
67 return;
68 }
69
70 for b in value.bytes() {
71 if special.contains(&b) {
72 out.push(release as char);
73 }
74 out.push(b as char);
75 }
76}
77
78#[cfg(test)]
79mod tests {
80 use super::*;
81
82 #[test]
83 fn test_render_segments_to_edifact() {
84 let segments = vec![
85 DisassembledSegment {
86 tag: "UNH".to_string(),
87 elements: vec![
88 vec!["1".to_string()],
89 vec!["UTILMD".to_string(), "D".to_string(), "11A".to_string()],
90 ],
91 },
92 DisassembledSegment {
93 tag: "BGM".to_string(),
94 elements: vec![vec!["E01".to_string()]],
95 },
96 ];
97
98 let delimiters = EdifactDelimiters::default();
99 let rendered = render_edifact(&segments, &delimiters);
100
101 assert_eq!(rendered, "UNH+1+UTILMD:D:11A'BGM+E01'");
102 }
103
104 #[test]
105 fn test_render_empty_segments() {
106 let delimiters = EdifactDelimiters::default();
107 let rendered = render_edifact(&[], &delimiters);
108 assert_eq!(rendered, "");
109 }
110
111 #[test]
112 fn test_render_segment_with_empty_components() {
113 let segments = vec![DisassembledSegment {
114 tag: "CAV".to_string(),
115 elements: vec![vec![
116 "SA".to_string(),
117 String::new(),
118 String::new(),
119 String::new(),
120 ]],
121 }];
122
123 let delimiters = EdifactDelimiters::default();
124 let rendered = render_edifact(&segments, &delimiters);
125
126 assert_eq!(rendered, "CAV+SA:::'");
128 }
129
130 #[test]
131 fn test_render_multiple_elements() {
132 let segments = vec![DisassembledSegment {
133 tag: "DTM".to_string(),
134 elements: vec![vec![
135 "137".to_string(),
136 "20250101".to_string(),
137 "102".to_string(),
138 ]],
139 }];
140
141 let delimiters = EdifactDelimiters::default();
142 let rendered = render_edifact(&segments, &delimiters);
143
144 assert_eq!(rendered, "DTM+137:20250101:102'");
145 }
146
147 #[test]
148 fn test_render_escapes_delimiter_chars_in_values() {
149 let segments = vec![DisassembledSegment {
151 tag: "DTM".to_string(),
152 elements: vec![vec![
153 "137".to_string(),
154 "202603021433+00".to_string(),
155 "303".to_string(),
156 ]],
157 }];
158
159 let delimiters = EdifactDelimiters::default();
160 let rendered = render_edifact(&segments, &delimiters);
161
162 assert_eq!(rendered, "DTM+137:202603021433?+00:303'");
163 }
164
165 #[test]
166 fn test_render_escapes_multiple_special_chars() {
167 let segments = vec![DisassembledSegment {
169 tag: "FTX".to_string(),
170 elements: vec![
171 vec!["ABO".to_string()],
172 vec![],
173 vec![],
174 vec!["hello?+world:test".to_string()],
175 ],
176 }];
177
178 let delimiters = EdifactDelimiters::default();
179 let rendered = render_edifact(&segments, &delimiters);
180
181 assert_eq!(rendered, "FTX+ABO+++hello???+world?:test'");
182 }
183
184 #[test]
185 fn test_render_no_escape_needed_for_plain_values() {
186 let segments = vec![DisassembledSegment {
187 tag: "BGM".to_string(),
188 elements: vec![vec!["312".to_string()], vec!["DOC001".to_string()]],
189 }];
190
191 let delimiters = EdifactDelimiters::default();
192 let rendered = render_edifact(&segments, &delimiters);
193
194 assert_eq!(rendered, "BGM+312+DOC001'");
195 }
196}