1#![cfg_attr(not(test), warn(missing_docs))]
191mod from_xml;
192pub mod schema;
193pub mod security;
194pub mod streaming;
195mod to_xml;
196
197#[cfg(feature = "async")]
198pub mod async_api;
199
200pub use from_xml::{from_xml, EntityPolicy, FromXmlConfig};
201pub use schema::{SchemaCache, SchemaValidator, ValidationError};
202pub use security::{SecurityViolation, XmlSecurityValidator};
203pub use streaming::{from_xml_stream, StreamConfig, StreamItem, XmlStreamingParser};
204pub use to_xml::{to_xml, ToXmlConfig};
205
206use hedl_core::Document;
207
208pub fn hedl_to_xml(doc: &Document) -> Result<String, String> {
210 to_xml(doc, &ToXmlConfig::default())
211}
212
213pub fn xml_to_hedl(xml: &str) -> Result<Document, String> {
215 from_xml(xml, &FromXmlConfig::default())
216}
217
218#[cfg(test)]
219mod tests {
220 use super::*;
221 use hedl_core::{Document, Item, MatrixList, Node, Reference, Value};
222 use std::collections::BTreeMap;
223
224 #[test]
225 fn test_round_trip_scalars() {
226 let mut doc = Document::new((1, 0));
227 doc.root
228 .insert("null_val".to_string(), Item::Scalar(Value::Null));
229 doc.root
230 .insert("bool_val".to_string(), Item::Scalar(Value::Bool(true)));
231 doc.root
232 .insert("int_val".to_string(), Item::Scalar(Value::Int(42)));
233 doc.root
234 .insert("float_val".to_string(), Item::Scalar(Value::Float(3.25)));
235 doc.root.insert(
236 "string_val".to_string(),
237 Item::Scalar(Value::String("hello".to_string().into())),
238 );
239
240 let xml = hedl_to_xml(&doc).unwrap();
241 let doc2 = xml_to_hedl(&xml).unwrap();
242
243 assert_eq!(
244 doc2.root.get("bool_val").and_then(|i| i.as_scalar()),
245 Some(&Value::Bool(true))
246 );
247 assert_eq!(
248 doc2.root.get("int_val").and_then(|i| i.as_scalar()),
249 Some(&Value::Int(42))
250 );
251 assert_eq!(
252 doc2.root.get("string_val").and_then(|i| i.as_scalar()),
253 Some(&Value::String("hello".to_string().into()))
254 );
255 }
256
257 #[test]
258 fn test_round_trip_object() {
259 let mut doc = Document::new((1, 0));
260 let mut inner = BTreeMap::new();
261 inner.insert(
262 "name".to_string(),
263 Item::Scalar(Value::String("test".to_string().into())),
264 );
265 inner.insert("value".to_string(), Item::Scalar(Value::Int(100)));
266 doc.root.insert("config".to_string(), Item::Object(inner));
267
268 let xml = hedl_to_xml(&doc).unwrap();
269 let doc2 = xml_to_hedl(&xml).unwrap();
270
271 let config_obj = doc2.root.get("config").and_then(|i| i.as_object()).unwrap();
272 assert_eq!(
273 config_obj.get("name").and_then(|i| i.as_scalar()),
274 Some(&Value::String("test".to_string().into()))
275 );
276 assert_eq!(
277 config_obj.get("value").and_then(|i| i.as_scalar()),
278 Some(&Value::Int(100))
279 );
280 }
281
282 #[test]
283 fn test_round_trip_reference() {
284 let mut doc = Document::new((1, 0));
285 doc.root.insert(
286 "ref1".to_string(),
287 Item::Scalar(Value::Reference(Reference::local("user123"))),
288 );
289 doc.root.insert(
290 "ref2".to_string(),
291 Item::Scalar(Value::Reference(Reference::qualified("User", "456"))),
292 );
293
294 let xml = hedl_to_xml(&doc).unwrap();
295 let doc2 = xml_to_hedl(&xml).unwrap();
296
297 assert_eq!(
298 doc2.root.get("ref1").and_then(|i| i.as_scalar()),
299 Some(&Value::Reference(Reference::local("user123")))
300 );
301 assert_eq!(
302 doc2.root.get("ref2").and_then(|i| i.as_scalar()),
303 Some(&Value::Reference(Reference::qualified("User", "456")))
304 );
305 }
306
307 #[test]
308 fn test_round_trip_expression() {
309 use hedl_core::lex::{ExprLiteral, Expression, Span};
310
311 let mut doc = Document::new((1, 0));
312 let expr = Expression::Call {
313 name: "add".to_string(),
314 args: vec![
315 Expression::Identifier {
316 name: "x".to_string(),
317 span: Span::synthetic(),
318 },
319 Expression::Literal {
320 value: ExprLiteral::Int(1),
321 span: Span::synthetic(),
322 },
323 ],
324 span: Span::synthetic(),
325 };
326 doc.root.insert(
327 "expr".to_string(),
328 Item::Scalar(Value::Expression(Box::new(expr.clone()))),
329 );
330
331 let xml = hedl_to_xml(&doc).unwrap();
332 let doc2 = xml_to_hedl(&xml).unwrap();
333
334 if let Some(Item::Scalar(Value::Expression(e))) = doc2.root.get("expr") {
336 assert_eq!(e.to_string(), expr.to_string());
338 } else {
339 panic!("Expected expression value");
340 }
341 }
342
343 #[test]
344 fn test_matrix_list() {
345 let mut doc = Document::new((1, 0));
346 let mut list = MatrixList::new("User", vec!["id".to_string(), "name".to_string()]);
347
348 let node1 = Node::new(
349 "User",
350 "user1",
351 vec![
352 Value::String("user1".to_string().into()),
353 Value::String("Alice".to_string().into()),
354 ],
355 );
356 let node2 = Node::new(
357 "User",
358 "user2",
359 vec![
360 Value::String("user2".to_string().into()),
361 Value::String("Bob".to_string().into()),
362 ],
363 );
364
365 list.add_row(node1);
366 list.add_row(node2);
367
368 doc.root.insert("users".to_string(), Item::List(list));
369
370 let xml = hedl_to_xml(&doc).unwrap();
371 assert!(xml.contains("<users"));
372 assert!(xml.contains("user1"));
373 assert!(xml.contains("user2"));
374 }
375
376 #[test]
377 fn test_special_characters_escaping() {
378 let mut doc = Document::new((1, 0));
379 doc.root.insert(
380 "text".to_string(),
381 Item::Scalar(Value::String(
382 "hello & goodbye <tag> \"quoted\"".to_string().into(),
383 )),
384 );
385
386 let xml = hedl_to_xml(&doc).unwrap();
387 let doc2 = xml_to_hedl(&xml).unwrap();
388
389 let original = doc.root.get("text").and_then(|i| i.as_scalar());
391 let parsed = doc2.root.get("text").and_then(|i| i.as_scalar());
392
393 assert_eq!(original, parsed);
394 }
395
396 #[test]
397 fn test_nested_objects() {
398 let mut doc = Document::new((1, 0));
399
400 let mut level2 = BTreeMap::new();
401 level2.insert(
402 "deep".to_string(),
403 Item::Scalar(Value::String("value".to_string().into())),
404 );
405
406 let mut level1 = BTreeMap::new();
407 level1.insert("nested".to_string(), Item::Object(level2));
408
409 doc.root.insert("outer".to_string(), Item::Object(level1));
410
411 let xml = hedl_to_xml(&doc).unwrap();
412 let doc2 = xml_to_hedl(&xml).unwrap();
413
414 assert!(doc2.root.contains_key("outer"));
415 }
416
417 #[test]
418 fn test_config_pretty_print() {
419 let mut doc = Document::new((1, 0));
420 doc.root.insert(
421 "test".to_string(),
422 Item::Scalar(Value::String("value".to_string().into())),
423 );
424
425 let config_pretty = ToXmlConfig {
426 pretty: true,
427 indent: " ".to_string(),
428 ..Default::default()
429 };
430
431 let config_compact = ToXmlConfig {
432 pretty: false,
433 ..Default::default()
434 };
435
436 let xml_pretty = to_xml(&doc, &config_pretty).unwrap();
437 let xml_compact = to_xml(&doc, &config_compact).unwrap();
438
439 assert!(xml_pretty.len() > xml_compact.len());
441 }
442
443 #[test]
444 fn test_config_custom_root() {
445 let doc = Document::new((1, 0));
446
447 let config = ToXmlConfig {
448 root_element: "custom_root".to_string(),
449 ..Default::default()
450 };
451
452 let xml = to_xml(&doc, &config).unwrap();
453 assert!(xml.contains("<custom_root"));
454 assert!(xml.contains("</custom_root>"));
455 }
456
457 #[test]
458 fn test_config_metadata() {
459 let doc = Document::new((2, 1));
460
461 let config = ToXmlConfig {
462 include_metadata: true,
463 ..Default::default()
464 };
465
466 let xml = to_xml(&doc, &config).unwrap();
467 assert!(xml.contains("version=\"2.1\""));
468 }
469
470 #[test]
471 fn test_empty_values() {
472 let mut doc = Document::new((1, 0));
473 doc.root
474 .insert("empty".to_string(), Item::Scalar(Value::Null));
475
476 let xml = hedl_to_xml(&doc).unwrap();
477 let doc2 = xml_to_hedl(&xml).unwrap();
478
479 assert!(doc2.root.contains_key("empty"));
480 }
481
482 #[test]
483 fn test_tensor_values() {
484 use hedl_core::lex::Tensor;
485
486 let mut doc = Document::new((1, 0));
487 let tensor = Tensor::Array(vec![
488 Tensor::Scalar(1.0),
489 Tensor::Scalar(2.0),
490 Tensor::Scalar(3.0),
491 ]);
492 doc.root.insert(
493 "tensor".to_string(),
494 Item::Scalar(Value::Tensor(Box::new(tensor))),
495 );
496
497 let xml = hedl_to_xml(&doc).unwrap();
498 assert!(xml.contains("<tensor>"));
499 assert!(xml.contains("<item>"));
500 }
501
502 #[test]
503 fn test_infer_lists_config() {
504 let xml = r#"<?xml version="1.0" encoding="UTF-8"?>
505 <hedl>
506 <user id="1"><name>Alice</name></user>
507 <user id="2"><name>Bob</name></user>
508 </hedl>"#;
509
510 let config = FromXmlConfig {
511 infer_lists: true,
512 ..Default::default()
513 };
514
515 let doc = from_xml(xml, &config).unwrap();
516
517 assert!(doc.root.contains_key("user"));
519 if let Some(Item::List(list)) = doc.root.get("user") {
520 assert_eq!(list.rows.len(), 2);
521 }
522 }
523
524 #[test]
525 fn test_attributes_as_values() {
526 let xml = r#"<?xml version="1.0" encoding="UTF-8"?>
527 <hedl>
528 <item id="123" name="test" active="true"/>
529 </hedl>"#;
530
531 let config = FromXmlConfig::default();
532 let doc = from_xml(xml, &config).unwrap();
533
534 assert!(doc.root.contains_key("item"));
535 if let Some(Item::Object(obj)) = doc.root.get("item") {
536 assert_eq!(
538 obj.get("id").and_then(|i| i.as_scalar()),
539 Some(&Value::Int(123))
540 );
541 assert_eq!(
542 obj.get("name").and_then(|i| i.as_scalar()),
543 Some(&Value::String("test".to_string().into()))
544 );
545 assert_eq!(
546 obj.get("active").and_then(|i| i.as_scalar()),
547 Some(&Value::Bool(true))
548 );
549 }
550 }
551}