1use alloc::collections::BTreeMap;
23use alloc::format;
24use alloc::string::{String, ToString};
25use alloc::vec::Vec;
26
27use crate::errors::XmlError;
28use crate::parser::{XmlElement, parse_xml_tree};
29use crate::xtypes_def::{
30 PrimitiveType, StructMember, StructType, TypeDef, TypeLibrary, TypeRef, UnionDiscriminator,
31};
32
33#[derive(Debug, Clone, PartialEq)]
35pub enum SampleValue {
36 Primitive(PrimitiveValue),
38 Struct(BTreeMap<String, SampleValue>),
40 Sequence(Vec<SampleValue>),
42 Array(Vec<SampleValue>),
44 Union {
46 discriminator: String,
48 value: alloc::boxed::Box<SampleValue>,
50 },
51 EnumLiteral(String),
53}
54
55#[derive(Debug, Clone, PartialEq)]
57pub enum PrimitiveValue {
58 Bool(bool),
60 I8(i8),
62 U8(u8),
64 I16(i16),
66 U16(u16),
68 I32(i32),
70 U32(u32),
72 I64(i64),
74 U64(u64),
76 F32(f32),
78 F64(f64),
80 Str(String),
82 Char(char),
84}
85
86pub fn parse_sample(
99 xml: &str,
100 type_def: &TypeDef,
101 type_lib: &[TypeLibrary],
102) -> Result<SampleValue, XmlError> {
103 let doc = parse_xml_tree(xml)?;
104 parse_sample_element(&doc.root, type_def, type_lib)
105}
106
107pub fn parse_sample_element(
113 el: &XmlElement,
114 type_def: &TypeDef,
115 type_lib: &[TypeLibrary],
116) -> Result<SampleValue, XmlError> {
117 parse_value_for_typedef(el, type_def, type_lib)
118}
119
120fn parse_value_for_typedef(
121 el: &XmlElement,
122 type_def: &TypeDef,
123 type_lib: &[TypeLibrary],
124) -> Result<SampleValue, XmlError> {
125 match type_def {
126 TypeDef::Struct(s) => parse_struct_value(el, s, type_lib),
127 TypeDef::Enum(_) => Ok(SampleValue::EnumLiteral(el.text.clone())),
128 TypeDef::Union(u) => parse_union_value(el, u, type_lib),
129 TypeDef::Typedef(td) => {
130 let synthetic = StructMember {
131 name: td.name.clone(),
132 type_ref: td.type_ref.clone(),
133 array_dimensions: td.array_dimensions.clone(),
134 sequence_max_length: td.sequence_max_length,
135 string_max_length: td.string_max_length,
136 ..Default::default()
137 };
138 parse_member_value(el, &synthetic, type_lib)
139 }
140 TypeDef::Bitmask(_) => Ok(SampleValue::Primitive(PrimitiveValue::Str(el.text.clone()))),
141 TypeDef::Bitset(_) => Ok(SampleValue::Primitive(PrimitiveValue::Str(el.text.clone()))),
142 TypeDef::Module(m) => Err(XmlError::UnresolvedReference(format!(
143 "module `{}` is not directly serializable",
144 m.name
145 ))),
146 TypeDef::Include(i) => Err(XmlError::UnresolvedReference(format!(
147 "include `{}` cannot be serialized as sample value",
148 i.file
149 ))),
150 TypeDef::ForwardDcl(f) => Err(XmlError::UnresolvedReference(format!(
151 "forward declaration `{}` cannot be serialized — body missing",
152 f.name
153 ))),
154 TypeDef::Const(c) => Ok(SampleValue::Primitive(PrimitiveValue::Str(c.value.clone()))),
155 }
156}
157
158fn parse_struct_value(
159 el: &XmlElement,
160 s: &StructType,
161 type_lib: &[TypeLibrary],
162) -> Result<SampleValue, XmlError> {
163 let mut map = BTreeMap::new();
164 for member in &s.members {
165 if let Some(child) = el.child(&member.name) {
166 let v = parse_member_value(child, member, type_lib)?;
167 map.insert(member.name.clone(), v);
168 } else if !member.optional {
169 return Err(XmlError::MissingRequiredElement(member.name.clone()));
170 }
171 }
172 Ok(SampleValue::Struct(map))
173}
174
175fn parse_union_value(
176 el: &XmlElement,
177 u: &crate::xtypes_def::UnionType,
178 type_lib: &[TypeLibrary],
179) -> Result<SampleValue, XmlError> {
180 let disc_el = el
181 .child("discriminator")
182 .ok_or_else(|| XmlError::MissingRequiredElement("discriminator".into()))?;
183 let disc = disc_el.text.clone();
184
185 let case = u
187 .cases
188 .iter()
189 .find(|c| {
190 c.discriminators.iter().any(|d| match d {
191 UnionDiscriminator::Value(v) => v == &disc,
192 UnionDiscriminator::Default => false,
193 })
194 })
195 .or_else(|| {
196 u.cases.iter().find(|c| {
197 c.discriminators
198 .iter()
199 .any(|d| matches!(d, UnionDiscriminator::Default))
200 })
201 })
202 .ok_or_else(|| {
203 XmlError::UnresolvedReference(format!("union case for discriminator `{disc}`"))
204 })?;
205 let val_el = el.child(&case.member.name).ok_or_else(|| {
206 XmlError::MissingRequiredElement(format!("union member `{}`", case.member.name))
207 })?;
208 let value = parse_member_value(val_el, &case.member, type_lib)?;
209 Ok(SampleValue::Union {
210 discriminator: disc,
211 value: alloc::boxed::Box::new(value),
212 })
213}
214
215fn parse_member_value(
216 el: &XmlElement,
217 member: &StructMember,
218 type_lib: &[TypeLibrary],
219) -> Result<SampleValue, XmlError> {
220 if !member.array_dimensions.is_empty() {
222 let mut items = Vec::new();
223 for child in el.children_named("item") {
224 let inner = parse_inner_value(child, &member.type_ref, type_lib)?;
225 items.push(inner);
226 }
227 return Ok(SampleValue::Array(items));
228 }
229 let is_seq = member.sequence_max_length.is_some() || has_only_item_children(el);
234 if is_seq
235 && (has_item_children(el)
236 || (member.sequence_max_length.is_some() && el.children.is_empty()))
237 {
238 let mut items = Vec::new();
239 for child in el.children_named("item") {
240 let inner = parse_inner_value(child, &member.type_ref, type_lib)?;
241 items.push(inner);
242 }
243 return Ok(SampleValue::Sequence(items));
244 }
245 parse_inner_value(el, &member.type_ref, type_lib)
246}
247
248fn has_item_children(el: &XmlElement) -> bool {
249 el.children.iter().any(|c| c.name == "item")
250}
251
252fn has_only_item_children(el: &XmlElement) -> bool {
253 !el.children.is_empty() && el.children.iter().all(|c| c.name == "item")
254}
255
256fn parse_inner_value(
257 el: &XmlElement,
258 type_ref: &TypeRef,
259 type_lib: &[TypeLibrary],
260) -> Result<SampleValue, XmlError> {
261 match type_ref {
262 TypeRef::Primitive(p) => parse_primitive_value(&el.text, *p).map(SampleValue::Primitive),
263 TypeRef::Named(name) => {
264 let td = resolve_named_type(name, type_lib).ok_or_else(|| {
265 XmlError::UnresolvedReference(format!("type `{name}` not in libraries"))
266 })?;
267 parse_value_for_typedef(el, &td, type_lib)
268 }
269 }
270}
271
272fn resolve_named_type(name: &str, type_lib: &[TypeLibrary]) -> Option<TypeDef> {
273 let parts: Vec<&str> = name.split("::").collect();
274 for lib in type_lib {
275 if let Some(td) = walk(&lib.types, &parts) {
276 return Some(td);
277 }
278 }
279 None
280}
281
282fn walk(types: &[TypeDef], parts: &[&str]) -> Option<TypeDef> {
285 if parts.is_empty() {
286 return None;
287 }
288 let head = parts[0];
289 for t in types {
290 if t.name() == head {
291 if parts.len() == 1 {
292 return Some(t.clone());
293 }
294 if let TypeDef::Module(m) = t {
295 return walk(&m.types, &parts[1..]);
296 }
297 }
298 }
299 if parts.len() == 1 {
301 for t in types {
302 if let TypeDef::Module(m) = t {
303 if let Some(found) = walk(&m.types, parts) {
304 return Some(found);
305 }
306 }
307 }
308 }
309 None
310}
311
312fn parse_primitive_value(s: &str, p: PrimitiveType) -> Result<PrimitiveValue, XmlError> {
313 let t = s.trim();
314 match p {
315 PrimitiveType::Boolean => Ok(PrimitiveValue::Bool(crate::types::parse_bool(t)?)),
316 PrimitiveType::Octet => parse_uint::<u8>(t).map(PrimitiveValue::U8),
317 PrimitiveType::Char => {
318 let mut chars = t.chars();
319 let c = chars
320 .next()
321 .ok_or_else(|| XmlError::ValueOutOfRange("empty char".into()))?;
322 if chars.next().is_some() {
323 return Err(XmlError::ValueOutOfRange(format!(
324 "char `{t}` is multi-char"
325 )));
326 }
327 Ok(PrimitiveValue::Char(c))
328 }
329 PrimitiveType::WChar => {
330 let mut chars = t.chars();
331 let c = chars
332 .next()
333 .ok_or_else(|| XmlError::ValueOutOfRange("empty wchar".into()))?;
334 Ok(PrimitiveValue::Char(c))
335 }
336 PrimitiveType::Short => parse_int::<i16>(t).map(PrimitiveValue::I16),
337 PrimitiveType::UShort => parse_uint::<u16>(t).map(PrimitiveValue::U16),
338 PrimitiveType::Long => parse_int::<i32>(t).map(PrimitiveValue::I32),
339 PrimitiveType::ULong => parse_uint::<u32>(t).map(PrimitiveValue::U32),
340 PrimitiveType::LongLong => parse_int::<i64>(t).map(PrimitiveValue::I64),
341 PrimitiveType::ULongLong => parse_uint::<u64>(t).map(PrimitiveValue::U64),
342 PrimitiveType::Float => t
343 .parse::<f32>()
344 .map(PrimitiveValue::F32)
345 .map_err(|e| XmlError::ValueOutOfRange(format!("float `{t}`: {e}"))),
346 PrimitiveType::Double | PrimitiveType::LongDouble => t
347 .parse::<f64>()
348 .map(PrimitiveValue::F64)
349 .map_err(|e| XmlError::ValueOutOfRange(format!("double `{t}`: {e}"))),
350 PrimitiveType::String | PrimitiveType::WString => Ok(PrimitiveValue::Str(s.to_string())),
351 }
352}
353
354fn parse_int<T>(s: &str) -> Result<T, XmlError>
355where
356 T: core::str::FromStr,
357 T::Err: core::fmt::Display,
358{
359 s.parse::<T>()
360 .map_err(|e| XmlError::ValueOutOfRange(format!("int `{s}`: {e}")))
361}
362
363fn parse_uint<T>(s: &str) -> Result<T, XmlError>
364where
365 T: core::str::FromStr,
366 T::Err: core::fmt::Display,
367{
368 if let Some(hex) = s.strip_prefix("0x").or_else(|| s.strip_prefix("0X")) {
369 let v = u64::from_str_radix(hex, 16)
371 .map_err(|e| XmlError::ValueOutOfRange(format!("hex `{s}`: {e}")))?;
372 let s2 = alloc::format!("{v}");
373 return s2
374 .parse::<T>()
375 .map_err(|e| XmlError::ValueOutOfRange(format!("uint `{s}`: {e}")));
376 }
377 s.parse::<T>()
378 .map_err(|e| XmlError::ValueOutOfRange(format!("uint `{s}`: {e}")))
379}
380
381#[must_use]
387pub fn serialize_sample(
388 value: &SampleValue,
389 type_def: &TypeDef,
390 _type_lib: &[TypeLibrary],
391) -> String {
392 let mut out = String::new();
393 out.push_str("<sample type_ref=\"");
394 out.push_str(type_def.name());
395 out.push_str("\">");
396 serialize_value_body(value, &mut out);
397 out.push_str("</sample>");
398 out
399}
400
401fn serialize_value_body(value: &SampleValue, out: &mut String) {
404 match value {
405 SampleValue::Primitive(p) => out.push_str(&serialize_primitive(p)),
406 SampleValue::EnumLiteral(s) => out.push_str(&xml_escape(s)),
407 SampleValue::Struct(map) => {
408 for (k, v) in map {
409 out.push('<');
410 out.push_str(k);
411 out.push('>');
412 serialize_value_body(v, out);
413 out.push_str("</");
414 out.push_str(k);
415 out.push('>');
416 }
417 }
418 SampleValue::Sequence(items) | SampleValue::Array(items) => {
419 for it in items {
420 out.push_str("<item>");
421 serialize_value_body(it, out);
422 out.push_str("</item>");
423 }
424 }
425 SampleValue::Union {
426 discriminator,
427 value,
428 } => {
429 out.push_str("<discriminator>");
430 out.push_str(&xml_escape(discriminator));
431 out.push_str("</discriminator>");
432 out.push_str("<value>");
435 serialize_value_body(value, out);
436 out.push_str("</value>");
437 }
438 }
439}
440
441fn serialize_primitive(p: &PrimitiveValue) -> String {
442 match p {
443 PrimitiveValue::Bool(b) => if *b { "true" } else { "false" }.to_string(),
444 PrimitiveValue::I8(v) => alloc::format!("{v}"),
445 PrimitiveValue::U8(v) => alloc::format!("{v}"),
446 PrimitiveValue::I16(v) => alloc::format!("{v}"),
447 PrimitiveValue::U16(v) => alloc::format!("{v}"),
448 PrimitiveValue::I32(v) => alloc::format!("{v}"),
449 PrimitiveValue::U32(v) => alloc::format!("{v}"),
450 PrimitiveValue::I64(v) => alloc::format!("{v}"),
451 PrimitiveValue::U64(v) => alloc::format!("{v}"),
452 PrimitiveValue::F32(v) => alloc::format!("{v}"),
453 PrimitiveValue::F64(v) => alloc::format!("{v}"),
454 PrimitiveValue::Str(s) => xml_escape(s),
455 PrimitiveValue::Char(c) => xml_escape(&alloc::format!("{c}")),
456 }
457}
458
459fn xml_escape(s: &str) -> String {
460 let mut out = String::with_capacity(s.len());
461 for c in s.chars() {
462 match c {
463 '<' => out.push_str("<"),
464 '>' => out.push_str(">"),
465 '&' => out.push_str("&"),
466 '"' => out.push_str("""),
467 '\'' => out.push_str("'"),
468 c if (c as u32) > 0x7F => {
470 let code = c as u32;
471 out.push_str(&alloc::format!("&#x{code:x};"));
472 }
473 c => out.push(c),
474 }
475 }
476 out
477}
478
479#[cfg(test)]
480#[allow(clippy::expect_used, clippy::unwrap_used, clippy::panic)]
481mod tests {
482 use super::*;
483 use crate::xtypes_def::{StructMember, StructType, TypeRef};
484
485 #[test]
486 fn parse_simple_struct_sample() {
487 let td = TypeDef::Struct(StructType {
488 name: "X".into(),
489 members: alloc::vec![StructMember {
490 name: "id".into(),
491 type_ref: TypeRef::Primitive(PrimitiveType::Long),
492 ..Default::default()
493 }],
494 ..Default::default()
495 });
496 let xml = r#"<sample type_ref="X"><id>42</id></sample>"#;
497 let v = parse_sample(xml, &td, &[]).expect("parse");
498 let SampleValue::Struct(m) = v else { panic!() };
499 assert!(matches!(
500 m.get("id"),
501 Some(SampleValue::Primitive(PrimitiveValue::I32(42)))
502 ));
503 }
504
505 #[test]
506 fn missing_member_rejected() {
507 let td = TypeDef::Struct(StructType {
508 name: "X".into(),
509 members: alloc::vec![StructMember {
510 name: "id".into(),
511 type_ref: TypeRef::Primitive(PrimitiveType::Long),
512 ..Default::default()
513 }],
514 ..Default::default()
515 });
516 let xml = r#"<sample type_ref="X"></sample>"#;
517 let err = parse_sample(xml, &td, &[]).expect_err("missing");
518 assert!(matches!(err, XmlError::MissingRequiredElement(_)));
519 }
520
521 #[test]
524 fn parse_sample_with_sequence_using_item_tag() {
525 let td = TypeDef::Struct(StructType {
528 name: "WithSeq".into(),
529 members: alloc::vec![StructMember {
530 name: "ids".into(),
531 type_ref: TypeRef::Primitive(PrimitiveType::Long),
532 sequence_max_length: Some(10),
533 ..Default::default()
534 }],
535 ..Default::default()
536 });
537 let xml = r#"<sample type_ref="WithSeq"><ids><item>1</item><item>2</item><item>3</item></ids></sample>"#;
538 let v = parse_sample(xml, &td, &[]).expect("parse");
539 let SampleValue::Struct(m) = v else { panic!() };
540 let ids = m.get("ids").expect("ids");
541 let SampleValue::Sequence(items) = ids else {
542 panic!("expected Sequence, got {ids:?}")
543 };
544 assert_eq!(items.len(), 3);
545 assert!(matches!(
546 items[0],
547 SampleValue::Primitive(PrimitiveValue::I32(1))
548 ));
549 assert!(matches!(
550 items[2],
551 SampleValue::Primitive(PrimitiveValue::I32(3))
552 ));
553 }
554
555 #[test]
556 fn parse_sample_with_array_using_item_tag() {
557 let td = TypeDef::Struct(StructType {
560 name: "WithArr".into(),
561 members: alloc::vec![StructMember {
562 name: "coords".into(),
563 type_ref: TypeRef::Primitive(PrimitiveType::Long),
564 array_dimensions: alloc::vec![3],
565 ..Default::default()
566 }],
567 ..Default::default()
568 });
569 let xml = r#"<sample type_ref="WithArr"><coords><item>10</item><item>20</item><item>30</item></coords></sample>"#;
570 let v = parse_sample(xml, &td, &[]).expect("parse");
571 let SampleValue::Struct(m) = v else { panic!() };
572 let coords = m.get("coords").expect("coords");
573 let SampleValue::Array(items) = coords else {
574 panic!("expected Array, got {coords:?}")
575 };
576 assert_eq!(items.len(), 3);
577 }
578
579 #[test]
580 fn parse_sample_with_empty_sequence() {
581 let td = TypeDef::Struct(StructType {
582 name: "X".into(),
583 members: alloc::vec![StructMember {
584 name: "ids".into(),
585 type_ref: TypeRef::Primitive(PrimitiveType::Long),
586 sequence_max_length: Some(10),
587 ..Default::default()
588 }],
589 ..Default::default()
590 });
591 let xml = r#"<sample type_ref="X"><ids></ids></sample>"#;
592 let v = parse_sample(xml, &td, &[]).expect("parse empty");
593 let SampleValue::Struct(m) = v else { panic!() };
594 let ids = m.get("ids").expect("ids");
595 let SampleValue::Sequence(items) = ids else {
596 panic!("expected Sequence")
597 };
598 assert!(items.is_empty());
599 }
600
601 #[test]
602 fn serialize_sample_uses_item_tag_for_sequence() {
603 let value = SampleValue::Struct(BTreeMap::from([(
607 "ids".to_string(),
608 SampleValue::Sequence(alloc::vec![
609 SampleValue::Primitive(PrimitiveValue::I32(7)),
610 SampleValue::Primitive(PrimitiveValue::I32(8)),
611 ]),
612 )]));
613 let td = TypeDef::Struct(StructType {
614 name: "Wrap".into(),
615 members: alloc::vec![StructMember {
616 name: "ids".into(),
617 type_ref: TypeRef::Primitive(PrimitiveType::Long),
618 sequence_max_length: Some(10),
619 ..Default::default()
620 }],
621 ..Default::default()
622 });
623 let out = serialize_sample(&value, &td, &[]);
624 assert!(out.contains("<item>7</item>"));
625 assert!(out.contains("<item>8</item>"));
626 }
627
628 #[test]
629 fn parse_sample_with_union() {
630 use crate::xtypes_def::{UnionCase, UnionType};
632 let td = TypeDef::Union(UnionType {
633 name: "U".into(),
634 discriminator: TypeRef::Primitive(PrimitiveType::Long),
635 cases: alloc::vec![UnionCase {
636 discriminators: alloc::vec![UnionDiscriminator::Value("1".into())],
637 member: StructMember {
638 name: "a".into(),
639 type_ref: TypeRef::Primitive(PrimitiveType::Long),
640 ..Default::default()
641 },
642 }],
643 });
644 let xml = r#"<sample type_ref="U"><discriminator>1</discriminator><a>42</a></sample>"#;
645 let v = parse_sample(xml, &td, &[]).expect("parse union");
646 let SampleValue::Union {
647 discriminator,
648 value,
649 } = v
650 else {
651 panic!("expected Union")
652 };
653 assert_eq!(discriminator, "1");
654 assert!(matches!(
655 *value,
656 SampleValue::Primitive(PrimitiveValue::I32(42))
657 ));
658 }
659}