1use quick_xml::Writer;
10use quick_xml::events::{BytesEnd, BytesStart, Event};
11use roxmltree::Document;
12use serde_json::{Map, Value};
13use std::collections::HashMap;
14use std::io::Cursor;
15use thiserror::Error;
16
17const FHIR_NS: &str = "http://hl7.org/fhir";
18const XHTML_NS: &str = "http://www.w3.org/1999/xhtml";
19
20#[derive(Debug, Error)]
21pub enum FormatError {
22 #[error("expected a JSON object for the resource")]
23 ExpectedObject,
24 #[error("missing resourceType property")]
25 MissingResourceType,
26 #[error("JSON parse error: {0}")]
27 Json(#[from] serde_json::Error),
28 #[error("XML parse error: {0}")]
29 Xml(#[from] roxmltree::Error),
30 #[error("UTF-8 error: {0}")]
31 Utf8(#[from] std::string::FromUtf8Error),
32 #[error("XML write error: {0}")]
33 XmlWrite(#[from] quick_xml::Error),
34}
35
36pub fn json_to_xml(input: &str) -> Result<String, FormatError> {
38 let value: Value = serde_json::from_str(input)?;
39 let obj = value.as_object().ok_or(FormatError::ExpectedObject)?;
40 let resource_type = obj
41 .get("resourceType")
42 .and_then(Value::as_str)
43 .ok_or(FormatError::MissingResourceType)?;
44
45 let mut writer = Writer::new_with_indent(Cursor::new(Vec::new()), b' ', 2);
46 let mut root = BytesStart::new(resource_type);
47 root.push_attribute(("xmlns", FHIR_NS));
48 writer.write_event(Event::Start(root.clone()))?;
49
50 let mut meta = HashMap::new();
51 for (k, v) in obj {
52 if k.starts_with('_') {
53 meta.insert(k.trim_start_matches('_').to_string(), v.clone());
54 }
55 }
56
57 for (k, v) in obj {
58 if k == "resourceType" || k.starts_with('_') {
59 continue;
60 }
61 let meta_entry = meta.get(k);
62 write_json_value(&mut writer, k, v, meta_entry)?;
63 }
64
65 for (k, v) in &meta {
68 if !obj.contains_key(k) {
69 write_json_value(&mut writer, k, &Value::Null, Some(v))?;
71 }
72 }
73
74 writer.write_event(Event::End(BytesEnd::new(resource_type)))?;
75 let bytes = writer.into_inner().into_inner();
76 Ok(String::from_utf8(bytes)?)
77}
78
79pub fn xml_to_json(input: &str) -> Result<String, FormatError> {
81 let doc = Document::parse(input)?;
82 let root = doc.root_element();
83
84 let mut map = Map::new();
85 map.insert(
86 "resourceType".to_string(),
87 Value::String(root.tag_name().name().to_string()),
88 );
89
90 let mut accumulator = Map::new();
91 for child in root.children().filter(|n| n.is_element()) {
92 process_xml_child(input, &mut accumulator, &child)?;
93 }
94
95 map.extend(accumulator);
96 let json = Value::Object(map);
97 Ok(serde_json::to_string_pretty(&json)?)
98}
99
100fn write_json_value(
101 writer: &mut Writer<Cursor<Vec<u8>>>,
102 name: &str,
103 value: &Value,
104 meta: Option<&Value>,
105) -> Result<(), FormatError> {
106 match value {
107 Value::Array(items) => {
108 let meta_array = meta.and_then(Value::as_array);
109 for (idx, item) in items.iter().enumerate() {
110 let item_meta = meta_array.and_then(|m| m.get(idx));
111 write_json_value(writer, name, item, item_meta)?;
112 }
113 }
114 Value::Object(obj) => write_complex(writer, name, obj)?,
115 Value::Null => {}
116 primitive => write_primitive(writer, name, primitive, meta)?,
117 }
118 Ok(())
119}
120
121fn write_complex(
122 writer: &mut Writer<Cursor<Vec<u8>>>,
123 name: &str,
124 obj: &Map<String, Value>,
125) -> Result<(), FormatError> {
126 let mut meta = HashMap::new();
127 for (k, v) in obj {
128 if k.starts_with('_') {
129 meta.insert(k.trim_start_matches('_').to_string(), v.clone());
130 }
131 }
132
133 let mut start = BytesStart::new(name);
134 if let Some(Value::String(id)) = obj.get("id") {
135 start.push_attribute(("id", id.as_str()));
136 }
137
138 writer.write_event(Event::Start(start))?;
139
140 for (k, v) in obj {
141 if k.starts_with('_') || k == "id" {
142 continue;
143 }
144 let meta_entry = meta.get(k);
145 write_json_value(writer, k, v, meta_entry)?;
146 }
147
148 writer.write_event(Event::End(BytesEnd::new(name)))?;
149 Ok(())
150}
151
152fn write_primitive(
153 writer: &mut Writer<Cursor<Vec<u8>>>,
154 name: &str,
155 value: &Value,
156 meta: Option<&Value>,
157) -> Result<(), FormatError> {
158 let mut elem = BytesStart::new(name);
159
160 let has_value = !matches!(value, Value::Null);
162 if has_value {
163 elem.push_attribute(("value", primitive_to_string(value).as_str()));
164 }
165
166 let mut has_children = false;
167 if let Some(Value::Object(m)) = meta {
168 if let Some(Value::String(id)) = m.get("id") {
169 elem.push_attribute(("id", id.as_str()));
170 }
171 if m.get("extension").is_some() {
172 has_children = true;
173 }
174 }
175
176 if !has_value && !has_children {
178 return Ok(());
179 }
180
181 if has_children {
182 writer.write_event(Event::Start(elem.clone()))?;
183 if let Some(Value::Object(m)) = meta {
184 if let Some(ext) = m.get("extension") {
185 write_json_value(writer, "extension", ext, None)?;
186 }
187 }
188 writer.write_event(Event::End(BytesEnd::new(name)))?;
189 } else {
190 writer.write_event(Event::Empty(elem))?;
191 }
192 Ok(())
193}
194
195fn primitive_to_string(value: &Value) -> String {
196 match value {
197 Value::String(s) => s.clone(),
198 Value::Number(n) => n.to_string(),
199 Value::Bool(b) => b.to_string(),
200 Value::Null => "".to_string(),
201 other => other.to_string(),
202 }
203}
204
205fn process_xml_child(
206 source: &str,
207 target: &mut Map<String, Value>,
208 node: &roxmltree::Node,
209) -> Result<(), FormatError> {
210 let name = node.tag_name().name().to_string();
211 let (value, meta) = xml_element_to_value(source, node)?;
212
213 insert_json_property(target, &name, value, meta);
214 Ok(())
215}
216
217fn xml_element_to_value(
218 source: &str,
219 node: &roxmltree::Node,
220) -> Result<(Value, Option<Value>), FormatError> {
221 if node.tag_name().namespace().is_some_and(|ns| ns == XHTML_NS) {
222 let snippet = &source[node.range()];
223 return Ok((Value::String(snippet.to_string()), None));
224 }
225
226 let mut meta_map = Map::new();
227 if let Some(id) = node.attribute("id") {
228 meta_map.insert("id".to_string(), Value::String(id.to_string()));
229 }
230
231 if let Some(val) = node.attribute("value") {
232 let mut extensions = Vec::new();
233 for child in node.children().filter(|c| c.is_element()) {
234 if child.tag_name().name() == "extension" {
235 let (ext_val, _ext_meta) = xml_element_to_value(source, &child)?;
236 extensions.push(ext_val);
237 }
238 }
239 if !extensions.is_empty() {
240 meta_map.insert("extension".to_string(), Value::Array(extensions));
241 }
242 let prim = parse_primitive(val);
243 let meta = if meta_map.is_empty() {
244 None
245 } else {
246 Some(Value::Object(meta_map))
247 };
248 return Ok((prim, meta));
249 }
250
251 let mut obj = Map::new();
252 if let Some(id) = node.attribute("id") {
253 obj.insert("id".to_string(), Value::String(id.to_string()));
254 }
255
256 for child in node.children().filter(|c| c.is_element()) {
257 process_xml_child(source, &mut obj, &child)?;
258 }
259
260 Ok((Value::Object(obj), None))
261}
262
263fn insert_json_property(
264 map: &mut Map<String, Value>,
265 name: &str,
266 value: Value,
267 meta: Option<Value>,
268) {
269 let entry = map.entry(name.to_string());
270 match entry {
271 serde_json::map::Entry::Vacant(v) => {
272 v.insert(value);
273 }
274 serde_json::map::Entry::Occupied(mut o) => match o.get_mut() {
275 Value::Array(arr) => arr.push(value),
276 existing => {
277 let old = existing.take();
278 *existing = Value::Array(vec![old, value]);
279 }
280 },
281 }
282
283 if meta.is_none() && !map.contains_key(&format!("_{}", name)) {
284 return;
285 }
286
287 let meta_key = format!("_{}", name);
288 let value_is_array = matches!(map.get(name), Some(Value::Array(_)));
289 let value_count = match map.get(name) {
290 Some(Value::Array(arr)) => arr.len(),
291 Some(_) => 1,
292 None => 0,
293 };
294
295 match map.entry(meta_key) {
296 serde_json::map::Entry::Vacant(v) => {
297 if let Some(m) = meta {
298 if value_is_array {
299 let mut arr = Vec::new();
300 if value_count > 1 {
301 arr.resize(value_count - 1, Value::Null);
302 }
303 arr.push(m);
304 v.insert(Value::Array(arr));
305 } else {
306 v.insert(m);
307 }
308 }
309 }
310 serde_json::map::Entry::Occupied(mut o) => match o.get_mut() {
311 Value::Array(arr) => {
312 if let Some(m) = meta {
313 if arr.len() + 1 < value_count {
314 arr.resize(value_count - 1, Value::Null);
315 }
316 arr.push(m);
317 } else {
318 arr.push(Value::Null);
319 }
320 }
321 existing => {
322 if value_is_array {
323 let first = existing.take();
324 let mut arr = Vec::new();
325 arr.push(first);
326 if value_count > 1 {
327 arr.resize(value_count - 1, Value::Null);
328 }
329 if let Some(m) = meta {
330 arr.push(m);
331 } else {
332 arr.push(Value::Null);
333 }
334 *existing = Value::Array(arr);
335 } else if let Some(m) = meta {
336 *existing = m;
337 }
338 }
339 },
340 }
341}
342
343fn parse_primitive(input: &str) -> Value {
344 match input {
345 "true" => Value::Bool(true),
346 "false" => Value::Bool(false),
347 _ => {
348 if let Ok(int) = input.parse::<i64>() {
349 Value::Number(int.into())
350 } else {
351 Value::String(input.to_string())
352 }
353 }
354 }
355}
356
357#[cfg(test)]
358mod tests {
359 use super::*;
360
361 #[test]
362 fn json_to_xml_basic_patient() {
363 let json = r#"
364 {
365 "resourceType": "Patient",
366 "id": "pat-1",
367 "active": true,
368 "name": [
369 { "family": "Everyman", "given": ["Adam"] }
370 ]
371 }
372 "#;
373
374 let xml = json_to_xml(json).expect("conversion failed");
375 assert!(xml.contains("<Patient"));
376 assert!(xml.contains(r#"<id value="pat-1"/>"#));
377 assert!(xml.contains(r#"<active value="true"/>"#));
378 assert!(xml.contains(r#"<family value="Everyman"/>"#));
379 }
380
381 #[test]
382 fn xml_to_json_round_trip() {
383 let xml = r#"
384 <Patient xmlns="http://hl7.org/fhir">
385 <id value="p1"/>
386 <active value="true"/>
387 <name>
388 <family value="Everyman"/>
389 <given value="Adam"/>
390 </name>
391 </Patient>
392 "#;
393
394 let json = xml_to_json(xml).expect("xml->json failed");
395 let value: Value = serde_json::from_str(&json).unwrap();
396 assert_eq!(value["resourceType"], "Patient");
397 assert_eq!(value["id"], "p1");
398 assert_eq!(value["active"], true);
399 let family = if value["name"].is_array() {
400 value["name"][0]["family"].clone()
401 } else {
402 value["name"]["family"].clone()
403 };
404 assert_eq!(family, "Everyman");
405 }
406
407 #[test]
408 fn primitive_metadata_survives_roundtrip() {
409 let json = r#"
410 {
411 "resourceType": "Patient",
412 "birthDate": "1974-12-25",
413 "_birthDate": { "id": "bd1" }
414 }
415 "#;
416
417 let xml = json_to_xml(json).unwrap();
418 assert!(xml.contains("<birthDate"));
419 assert!(xml.contains(r#"value="1974-12-25""#));
420 assert!(xml.contains(r#"id="bd1""#));
421
422 let back = xml_to_json(&xml).unwrap();
423 let val: Value = serde_json::from_str(&back).unwrap();
424 assert_eq!(val["birthDate"], "1974-12-25");
425 assert_eq!(val["_birthDate"]["id"], "bd1");
426 }
427}