Skip to main content

zerodds_xml/
xtypes_parser.rs

1// SPDX-License-Identifier: Apache-2.0
2// Copyright 2026 ZeroDDS Contributors
3//! DDS-XML 1.0 §7.3.3 Building Block "Types" — XML→AST-Parser.
4//!
5//! Liest `<types>`-Top-Level-Bloecke und produziert eine
6//! [`Vec<TypeLibrary>`]. Modul-Hierarchie wird als geschachtelte
7//! [`TypeDef::Module`]-Eintraege beibehalten.
8//!
9//! Spec-Quelle: OMG DDS-XML 1.0 §7.3.3 (Types Building Block).
10
11use alloc::format;
12use alloc::string::ToString;
13use alloc::vec::Vec;
14
15use crate::errors::XmlError;
16use crate::parser::{XmlElement, parse_xml_tree};
17use crate::types::{parse_long, parse_ulong};
18use crate::xtypes_def::{
19    BitField, BitValue, BitmaskType, BitsetType, EnumLiteral, EnumType, Extensibility, ModuleEntry,
20    PrimitiveType, StructMember, StructType, TypeDef, TypeLibrary, TypeRef, TypedefType, UnionCase,
21    UnionDiscriminator, UnionType,
22};
23
24/// Parst alle `<types>`-Top-Level-Elemente eines DDS-XML-Dokuments.
25///
26/// Der Top-Level-Wrapper `<dds>` ist NICHT erforderlich — `xml` darf direkt
27/// ein `<types>`-Wurzel-Element sein.
28///
29/// # Errors
30/// * [`XmlError::InvalidXml`] — kein wohlgeformtes XML.
31/// * [`XmlError::UnknownElement`] — unbekanntes Top-Level-Element.
32/// * [`XmlError::ValueOutOfRange`] — numerisches Attribut ausserhalb Range.
33pub fn parse_type_libraries(xml: &str) -> Result<Vec<TypeLibrary>, XmlError> {
34    let doc = parse_xml_tree(xml)?;
35    let mut out = Vec::new();
36    match doc.root.name.as_str() {
37        "dds" => {
38            for child in &doc.root.children {
39                if child.name == "types" {
40                    out.push(parse_types_element(child)?);
41                }
42            }
43        }
44        "types" => out.push(parse_types_element(&doc.root)?),
45        other => {
46            return Err(XmlError::InvalidXml(format!(
47                "expected <dds> or <types> root, got <{other}>"
48            )));
49        }
50    }
51    Ok(out)
52}
53
54/// Parst ein einzelnes `<types>`-Element zu einer `TypeLibrary`.
55///
56/// # Errors
57/// Wie [`parse_type_libraries`].
58pub fn parse_types_element(el: &XmlElement) -> Result<TypeLibrary, XmlError> {
59    let name = el.attribute("name").unwrap_or("").to_string();
60    let mut lib = TypeLibrary {
61        name,
62        types: Vec::new(),
63    };
64    for child in &el.children {
65        lib.types.push(parse_type_def(child)?);
66    }
67    Ok(lib)
68}
69
70/// zerodds-lint: recursion-depth = modul-schachtelungstiefe (indirekt via
71/// `parse_module`). DoS-Cap der XML-Foundation begrenzt Tiefe ≤ 16.
72fn parse_type_def(el: &XmlElement) -> Result<TypeDef, XmlError> {
73    match el.name.as_str() {
74        "module" => Ok(TypeDef::Module(parse_module(el)?)),
75        "struct" => Ok(TypeDef::Struct(parse_struct(el)?)),
76        "enum" => Ok(TypeDef::Enum(parse_enum(el)?)),
77        "union" => Ok(TypeDef::Union(parse_union(el)?)),
78        "typedef" => Ok(TypeDef::Typedef(parse_typedef(el)?)),
79        "bitmask" => Ok(TypeDef::Bitmask(parse_bitmask(el)?)),
80        "bitset" => Ok(TypeDef::Bitset(parse_bitset(el)?)),
81        "include" => Ok(TypeDef::Include(parse_include(el)?)),
82        "forward_dcl" | "forwardDcl" => Ok(TypeDef::ForwardDcl(parse_forward_dcl(el)?)),
83        "const" => Ok(TypeDef::Const(parse_const(el)?)),
84        other => Err(XmlError::UnknownElement(other.to_string())),
85    }
86}
87
88fn parse_include(el: &XmlElement) -> Result<crate::xtypes_def::IncludeEntry, XmlError> {
89    let file = require_attr(el, "file")?.to_string();
90    Ok(crate::xtypes_def::IncludeEntry { file })
91}
92
93fn parse_forward_dcl(el: &XmlElement) -> Result<crate::xtypes_def::ForwardDeclEntry, XmlError> {
94    let name = require_attr(el, "name")?.to_string();
95    // kind ist optional; default "STRUCT".
96    let kind = el.attribute("kind").unwrap_or("STRUCT").to_string();
97    Ok(crate::xtypes_def::ForwardDeclEntry { name, kind })
98}
99
100fn parse_const(el: &XmlElement) -> Result<crate::xtypes_def::ConstEntry, XmlError> {
101    let name = require_attr(el, "name")?.to_string();
102    let type_name = require_attr(el, "type")?.to_string();
103    let value = require_attr(el, "value")?.to_string();
104    Ok(crate::xtypes_def::ConstEntry {
105        name,
106        type_name,
107        value,
108    })
109}
110
111/// zerodds-lint: recursion-depth = modul-schachtelungstiefe (indirekt via
112/// `parse_type_def`). DoS-Cap der XML-Foundation begrenzt Tiefe ≤ 16.
113fn parse_module(el: &XmlElement) -> Result<ModuleEntry, XmlError> {
114    let name = require_attr(el, "name")?.to_string();
115    let mut types = Vec::new();
116    for child in &el.children {
117        types.push(parse_type_def(child)?);
118    }
119    Ok(ModuleEntry { name, types })
120}
121
122fn parse_struct(el: &XmlElement) -> Result<StructType, XmlError> {
123    let name = require_attr(el, "name")?.to_string();
124    let extensibility = el
125        .attribute("extensibility")
126        .map(parse_extensibility)
127        .transpose()?;
128    let base_type = el
129        .attribute("baseType")
130        .or_else(|| el.attribute("base_type"))
131        .map(ToString::to_string);
132    let mut members = Vec::new();
133    for child in &el.children {
134        if child.name == "member" {
135            members.push(parse_member(child)?);
136        }
137    }
138    Ok(StructType {
139        name,
140        extensibility,
141        base_type,
142        members,
143    })
144}
145
146fn parse_extensibility(s: &str) -> Result<Extensibility, XmlError> {
147    match s {
148        "final" | "FINAL" => Ok(Extensibility::Final),
149        "appendable" | "APPENDABLE" => Ok(Extensibility::Appendable),
150        "mutable" | "MUTABLE" => Ok(Extensibility::Mutable),
151        other => Err(XmlError::BadEnum(other.to_string())),
152    }
153}
154
155fn parse_member(el: &XmlElement) -> Result<StructMember, XmlError> {
156    let name = require_attr(el, "name")?.to_string();
157    let type_str = require_attr(el, "type")?;
158    let type_ref = parse_type_ref(type_str);
159    let key = parse_attr_bool(el, "key")?;
160    let optional = parse_attr_bool(el, "optional")?;
161    let must_understand =
162        parse_attr_bool(el, "mustUnderstand")? || parse_attr_bool(el, "must_understand")?;
163    let id = el.attribute("id").map(parse_ulong).transpose()?;
164    let string_max_length = el
165        .attribute("stringMaxLength")
166        .map(parse_ulong)
167        .transpose()?;
168    let sequence_max_length = el
169        .attribute("sequenceMaxLength")
170        .map(parse_ulong)
171        .transpose()?;
172    let array_dimensions = el
173        .attribute("arrayDimensions")
174        .map(parse_dimensions)
175        .transpose()?
176        .unwrap_or_default();
177    Ok(StructMember {
178        name,
179        type_ref,
180        key,
181        optional,
182        must_understand,
183        id,
184        string_max_length,
185        sequence_max_length,
186        array_dimensions,
187    })
188}
189
190fn parse_type_ref(s: &str) -> TypeRef {
191    if let Some(p) = PrimitiveType::from_xml(s) {
192        TypeRef::Primitive(p)
193    } else {
194        TypeRef::Named(s.to_string())
195    }
196}
197
198fn parse_attr_bool(el: &XmlElement, key: &str) -> Result<bool, XmlError> {
199    match el.attribute(key) {
200        None => Ok(false),
201        Some(v) => crate::types::parse_bool(v),
202    }
203}
204
205fn parse_dimensions(s: &str) -> Result<Vec<u32>, XmlError> {
206    let mut out = Vec::new();
207    for piece in s.split(',') {
208        let t = piece.trim();
209        if t.is_empty() {
210            continue;
211        }
212        let v = parse_ulong(t)?;
213        out.push(v);
214    }
215    Ok(out)
216}
217
218fn parse_enum(el: &XmlElement) -> Result<EnumType, XmlError> {
219    let name = require_attr(el, "name")?.to_string();
220    let bit_bound = el.attribute("bitBound").map(parse_ulong).transpose()?;
221    let mut enumerators = Vec::new();
222    for child in &el.children {
223        if child.name == "enumerator" {
224            let n = require_attr(child, "name")?.to_string();
225            let value = child.attribute("value").map(parse_long).transpose()?;
226            enumerators.push(EnumLiteral { name: n, value });
227        }
228    }
229    Ok(EnumType {
230        name,
231        bit_bound,
232        enumerators,
233    })
234}
235
236fn parse_union(el: &XmlElement) -> Result<UnionType, XmlError> {
237    let name = require_attr(el, "name")?.to_string();
238    let disc_str = require_attr(el, "discriminator")?;
239    let discriminator = parse_type_ref(disc_str);
240    let mut cases = Vec::new();
241    for child in el.children_named("case") {
242        cases.push(parse_union_case(child)?);
243    }
244    Ok(UnionType {
245        name,
246        discriminator,
247        cases,
248    })
249}
250
251fn parse_union_case(el: &XmlElement) -> Result<UnionCase, XmlError> {
252    let mut discriminators = Vec::new();
253    let mut member: Option<StructMember> = None;
254    for child in &el.children {
255        match child.name.as_str() {
256            "caseDiscriminator" => {
257                let v = require_attr(child, "value")?;
258                if v == "default" {
259                    discriminators.push(UnionDiscriminator::Default);
260                } else {
261                    discriminators.push(UnionDiscriminator::Value(v.to_string()));
262                }
263            }
264            "member" => {
265                member = Some(parse_member(child)?);
266            }
267            _ => {}
268        }
269    }
270    let member = member.ok_or_else(|| XmlError::MissingRequiredElement("member".to_string()))?;
271    Ok(UnionCase {
272        discriminators,
273        member,
274    })
275}
276
277fn parse_typedef(el: &XmlElement) -> Result<TypedefType, XmlError> {
278    let name = require_attr(el, "name")?.to_string();
279    let type_str = require_attr(el, "type")?;
280    let type_ref = parse_type_ref(type_str);
281    let array_dimensions = el
282        .attribute("arrayDimensions")
283        .map(parse_dimensions)
284        .transpose()?
285        .unwrap_or_default();
286    let sequence_max_length = el
287        .attribute("sequenceMaxLength")
288        .map(parse_ulong)
289        .transpose()?;
290    let string_max_length = el
291        .attribute("stringMaxLength")
292        .map(parse_ulong)
293        .transpose()?;
294    Ok(TypedefType {
295        name,
296        type_ref,
297        array_dimensions,
298        sequence_max_length,
299        string_max_length,
300    })
301}
302
303fn parse_bitmask(el: &XmlElement) -> Result<BitmaskType, XmlError> {
304    let name = require_attr(el, "name")?.to_string();
305    let bit_bound = el.attribute("bitBound").map(parse_ulong).transpose()?;
306    let mut bit_values = Vec::new();
307    for child in el.children_named("bit_value") {
308        let n = require_attr(child, "name")?.to_string();
309        let position = child.attribute("position").map(parse_ulong).transpose()?;
310        bit_values.push(BitValue { name: n, position });
311    }
312    Ok(BitmaskType {
313        name,
314        bit_bound,
315        bit_values,
316    })
317}
318
319fn parse_bitset(el: &XmlElement) -> Result<BitsetType, XmlError> {
320    let name = require_attr(el, "name")?.to_string();
321    let mut bit_fields = Vec::new();
322    for child in el.children_named("bitfield") {
323        let n = require_attr(child, "name")?.to_string();
324        let type_str = require_attr(child, "type")?;
325        let mask = require_attr(child, "mask")?.to_string();
326        bit_fields.push(BitField {
327            name: n,
328            type_ref: parse_type_ref(type_str),
329            mask,
330        });
331    }
332    Ok(BitsetType { name, bit_fields })
333}
334
335fn require_attr<'a>(el: &'a XmlElement, key: &str) -> Result<&'a str, XmlError> {
336    el.attribute(key)
337        .ok_or_else(|| XmlError::MissingRequiredElement(format!("@{key} on <{}>", el.name)))
338}
339
340#[cfg(test)]
341#[allow(clippy::expect_used, clippy::unwrap_used, clippy::panic)]
342mod tests {
343    use super::*;
344
345    #[test]
346    fn parse_simple_struct() {
347        let xml = r#"<types>
348            <struct name="State">
349                <member name="id" type="long" key="true"/>
350            </struct>
351        </types>"#;
352        let libs = parse_type_libraries(xml).expect("parse");
353        assert_eq!(libs.len(), 1);
354        let TypeDef::Struct(s) = &libs[0].types[0] else {
355            panic!("expected struct");
356        };
357        assert_eq!(s.name, "State");
358        assert_eq!(s.members.len(), 1);
359        assert!(s.members[0].key);
360    }
361
362    #[test]
363    fn parse_module_nested() {
364        let xml = r#"<types>
365            <module name="Outer">
366                <module name="Inner">
367                    <struct name="X"/>
368                </module>
369            </module>
370        </types>"#;
371        let libs = parse_type_libraries(xml).expect("parse");
372        let TypeDef::Module(m) = &libs[0].types[0] else {
373            panic!()
374        };
375        assert_eq!(m.name, "Outer");
376        let TypeDef::Module(inner) = &m.types[0] else {
377            panic!()
378        };
379        assert_eq!(inner.name, "Inner");
380    }
381
382    #[test]
383    fn dds_root_with_multiple_types_blocks() {
384        let xml = r#"<dds>
385            <types><struct name="A"/></types>
386            <types><struct name="B"/></types>
387        </dds>"#;
388        let libs = parse_type_libraries(xml).expect("parse");
389        assert_eq!(libs.len(), 2);
390    }
391
392    #[test]
393    fn unknown_root_rejected() {
394        let xml = r#"<other/>"#;
395        let err = parse_type_libraries(xml).expect_err("err");
396        assert!(matches!(err, XmlError::InvalidXml(_)));
397    }
398
399    // ---- §7.3.2 XML-TypeRepr Erweiterte Konstrukte ----
400
401    #[test]
402    fn parse_include_element() {
403        let xml = r#"<types>
404            <include file="shared/common.xml"/>
405            <struct name="Local"/>
406        </types>"#;
407        let libs = parse_type_libraries(xml).expect("parse");
408        assert_eq!(libs[0].types.len(), 2);
409        let TypeDef::Include(inc) = &libs[0].types[0] else {
410            panic!("expected Include");
411        };
412        assert_eq!(inc.file, "shared/common.xml");
413    }
414
415    #[test]
416    fn parse_forward_dcl_element() {
417        let xml = r#"<types>
418            <forward_dcl name="Node" kind="STRUCT"/>
419            <struct name="Node">
420                <member name="payload" type="long"/>
421            </struct>
422        </types>"#;
423        let libs = parse_type_libraries(xml).expect("parse");
424        let TypeDef::ForwardDcl(fwd) = &libs[0].types[0] else {
425            panic!("expected ForwardDcl");
426        };
427        assert_eq!(fwd.name, "Node");
428        assert_eq!(fwd.kind, "STRUCT");
429    }
430
431    #[test]
432    fn parse_forward_dcl_default_kind_struct() {
433        let xml = r#"<types>
434            <forward_dcl name="X"/>
435        </types>"#;
436        let libs = parse_type_libraries(xml).expect("parse");
437        let TypeDef::ForwardDcl(fwd) = &libs[0].types[0] else {
438            panic!("expected ForwardDcl");
439        };
440        assert_eq!(fwd.kind, "STRUCT");
441    }
442
443    #[test]
444    fn parse_const_element() {
445        let xml = r#"<types>
446            <const name="MaxSize" type="long" value="1024"/>
447            <const name="Greeting" type="string" value="hello"/>
448        </types>"#;
449        let libs = parse_type_libraries(xml).expect("parse");
450        assert_eq!(libs[0].types.len(), 2);
451        let TypeDef::Const(c1) = &libs[0].types[0] else {
452            panic!("expected Const");
453        };
454        assert_eq!(c1.name, "MaxSize");
455        assert_eq!(c1.type_name, "long");
456        assert_eq!(c1.value, "1024");
457        let TypeDef::Const(c2) = &libs[0].types[1] else {
458            panic!("expected Const");
459        };
460        assert_eq!(c2.value, "hello");
461    }
462
463    #[test]
464    fn parse_const_missing_value_attribute_rejected() {
465        let xml = r#"<types>
466            <const name="X" type="long"/>
467        </types>"#;
468        let res = parse_type_libraries(xml);
469        assert!(res.is_err());
470    }
471
472    #[test]
473    fn parse_include_missing_file_attribute_rejected() {
474        let xml = r#"<types>
475            <include/>
476        </types>"#;
477        let res = parse_type_libraries(xml);
478        assert!(res.is_err());
479    }
480}