Skip to main content

zerodds_xml/
xsd_schema.rs

1// SPDX-License-Identifier: Apache-2.0
2// Copyright 2026 ZeroDDS Contributors
3//! W3C XML Schema (XSD) → DDS-XTypes-Bridge (XTypes 1.3 §7.3.3).
4//!
5//! Spec OMG XTypes 1.3 §7.3.3 erlaubt XSD als alternative Type-
6//! Representation neben IDL/XML/TypeObject. Dieses Modul mappt eine
7//! Untermenge von W3C XSD 1.0/1.1 auf das interne [`TypeLibrary`]-
8//! Datenmodell aus [`crate::xtypes_def`]; via
9//! [`crate::typeobject_bridge`] wird daraus ein
10//! [`zerodds_types::TypeObject`] berechnet.
11//!
12//! # Mapping (XTypes 1.3 §7.3.3 Tab.6.x)
13//!
14//! ```text
15//! XSD                                    | DDS-Type
16//! -------------------------------------- | -------------------------
17//! <xsd:schema>                           | TypeLibrary (Top-Level)
18//! <xsd:complexType name=X>               | <struct name=X>
19//!   <xsd:sequence>                       |
20//!     <xsd:element name=f type=t/>       | <member name=f type=t/>
21//! <xsd:simpleType name=E>                | <enum name=E>
22//!   <xsd:restriction base="xsd:string">  |
23//!     <xsd:enumeration value=A/>         | <enumerator name=A/>
24//! minOccurs=0 (Element)                  | optional=true
25//! maxOccurs=unbounded                    | sequence
26//! maxOccurs=N (N>1)                      | bounded sequence
27//! ```
28//!
29//! # Built-In Type Mapping
30//!
31//! ```text
32//! xsd:boolean | xsd:byte | xsd:short | xsd:int     | xsd:long
33//! -> boolean    octet      short       long          longlong
34//!
35//! xsd:unsignedByte | xsd:unsignedShort | xsd:unsignedInt | xsd:unsignedLong
36//! -> octet           ushort              ulong             ulonglong
37//!
38//! xsd:float | xsd:double  | xsd:string | xsd:base64Binary
39//! -> float    double        string       sequence<octet>
40//! ```
41//!
42//! # Bewusst nicht im Crate
43//!
44//! - Voller XSD-1.1-Validator (key/keyref, assertions).
45//! - `<xsd:choice>` als Union-Equivalent (Spec sieht das nicht
46//!   normativ vor; folgt evtl. Phase 2).
47//! - XSD `xsd:include`/`xsd:import` (Multi-File-Schemas).
48//! - Mixed Content / Attribute-as-Member.
49
50extern crate alloc;
51
52use alloc::format;
53use alloc::string::{String, ToString};
54use alloc::vec::Vec;
55
56use crate::errors::XmlError;
57use crate::parser::{XmlElement, parse_xml_tree};
58use crate::xtypes_def::{
59    EnumLiteral, EnumType, PrimitiveType, StructMember, StructType, TypeDef, TypeLibrary, TypeRef,
60};
61
62/// Mapped einen W3C-XSD-Built-In-Type-Namen (mit oder ohne `xsd:`-
63/// Prefix) auf einen DDS-`PrimitiveType` oder, falls es ein
64/// User-Type ist, `None`.
65fn map_builtin(local: &str) -> Option<PrimitiveType> {
66    let trimmed = match local.split_once(':') {
67        Some((_prefix, suffix)) => suffix,
68        None => local,
69    };
70    Some(match trimmed {
71        "boolean" => PrimitiveType::Boolean,
72        "byte" => PrimitiveType::Octet,
73        "unsignedByte" => PrimitiveType::Octet,
74        "short" => PrimitiveType::Short,
75        "unsignedShort" => PrimitiveType::UShort,
76        "int" | "integer" => PrimitiveType::Long,
77        "unsignedInt" => PrimitiveType::ULong,
78        "long" => PrimitiveType::LongLong,
79        "unsignedLong" => PrimitiveType::ULongLong,
80        "float" => PrimitiveType::Float,
81        "double" => PrimitiveType::Double,
82        "string" | "normalizedString" | "token" => PrimitiveType::String,
83        _ => return None,
84    })
85}
86
87/// Aufloesung eines `type=...`-Attributs in einen `TypeRef`. Built-In-
88/// XSD-Typen werden auf [`PrimitiveType`] gemapped, alle anderen
89/// (User-Types, andere Namespaces) werden als [`TypeRef::Named`]
90/// durchgereicht.
91fn resolve_type_ref(raw: &str) -> TypeRef {
92    if let Some(prim) = map_builtin(raw) {
93        TypeRef::Primitive(prim)
94    } else {
95        // strip prefix wenn local-name unique ist (User-Types ohne ns).
96        let local = match raw.split_once(':') {
97            Some((_p, s)) => s,
98            None => raw,
99        };
100        TypeRef::Named(local.to_string())
101    }
102}
103
104/// Parst ein XSD-Schema-Dokument und liefert eine [`TypeLibrary`].
105///
106/// # Errors
107/// * [`XmlError::InvalidXml`] — kein wohlgeformtes XML, fehlendes
108///   `<xsd:schema>`-Root, oder syntaktische Fehler in der XSD-Struktur.
109pub fn parse_xsd_schema(xml: &str) -> Result<TypeLibrary, XmlError> {
110    let doc = parse_xml_tree(xml)?;
111    if doc.root.name != "schema" {
112        return Err(XmlError::InvalidXml(format!(
113            "expected <xsd:schema> root, got <{}>",
114            doc.root.name
115        )));
116    }
117
118    let mut lib = TypeLibrary::default();
119    for child in &doc.root.children {
120        match child.name.as_str() {
121            "complexType" => lib.types.push(TypeDef::Struct(parse_complex_type(child)?)),
122            "simpleType" => {
123                if let Some(en) = parse_simple_type_as_enum(child)? {
124                    lib.types.push(TypeDef::Enum(en));
125                }
126                // simpleType ohne enumeration ist Type-Alias auf einen
127                // built-in — wir ignorieren das auf dieser Stufe.
128            }
129            "element" => {
130                // Top-Level `<xsd:element>` mit anonymem inline-
131                // complexType: wir behandeln das als Top-Level-Struct
132                // mit dem Element-Namen.
133                if let Some(ct) = child.child("complexType") {
134                    let mut s = parse_complex_type(ct)?;
135                    if s.name.is_empty() {
136                        s.name = child.attribute("name").unwrap_or_default().to_string();
137                    }
138                    if !s.name.is_empty() {
139                        lib.types.push(TypeDef::Struct(s));
140                    }
141                }
142            }
143            "include" | "import" | "annotation" | "notation" | "group" | "attributeGroup" => {
144                // Nicht im Scope; still ignorieren (XSD-Includes sind
145                // Annotations rein dokumentarisch).
146            }
147            _ => {
148                // Unbekanntes Top-Level-Element — strikt rejecten waere
149                // zu hart, also lax.
150            }
151        }
152    }
153    Ok(lib)
154}
155
156/// Parst `<xsd:complexType name="X"><xsd:sequence>...</xsd:sequence></xsd:complexType>`.
157fn parse_complex_type(node: &XmlElement) -> Result<StructType, XmlError> {
158    let name = node.attribute("name").unwrap_or_default().to_string();
159
160    let mut members = Vec::new();
161    // `<xsd:sequence>` ist die normalisierte Member-Reihenfolge. Spec
162    // erlaubt auch `<xsd:all>` (unordered) — wir behandeln es identisch.
163    if let Some(seq) = node.child("sequence").or_else(|| node.child("all")) {
164        for el in seq.children_named("element") {
165            members.push(parse_element_as_member(el)?);
166        }
167    }
168    // `<xsd:complexContent><xsd:extension base="..."><xsd:sequence>...`
169    // -> Inheritance.
170    let mut base_type: Option<String> = None;
171    if let Some(cc) = node.child("complexContent") {
172        if let Some(ext) = cc.child("extension") {
173            if let Some(b) = ext.attribute("base") {
174                let local = b.split_once(':').map(|(_p, s)| s).unwrap_or(b);
175                base_type = Some(local.to_string());
176            }
177            if let Some(seq) = ext.child("sequence").or_else(|| ext.child("all")) {
178                for el in seq.children_named("element") {
179                    members.push(parse_element_as_member(el)?);
180                }
181            }
182        }
183    }
184
185    Ok(StructType {
186        name,
187        extensibility: None,
188        base_type,
189        members,
190    })
191}
192
193/// Parst ein `<xsd:element name="..." type="..." minOccurs="..."
194/// maxOccurs="...">` als Struct-Member.
195fn parse_element_as_member(node: &XmlElement) -> Result<StructMember, XmlError> {
196    let name = node.attribute("name").unwrap_or_default().to_string();
197    let raw_type = node.attribute("type").unwrap_or("xsd:string");
198    let type_ref = resolve_type_ref(raw_type);
199
200    let min_occurs = node
201        .attribute("minOccurs")
202        .and_then(|v| v.parse::<u32>().ok())
203        .unwrap_or(1);
204    let max_occurs_raw = node.attribute("maxOccurs").unwrap_or("1");
205    let max_occurs: Option<u32> = if max_occurs_raw == "unbounded" {
206        Some(u32::MAX)
207    } else {
208        max_occurs_raw.parse::<u32>().ok()
209    };
210
211    let optional = min_occurs == 0 && max_occurs == Some(1);
212    let sequence_max_length = match max_occurs {
213        Some(u32::MAX) => Some(0),   // unbounded → 0 = "no bound" Marker
214        Some(n) if n > 1 => Some(n), // bounded sequence
215        _ => None,
216    };
217
218    Ok(StructMember {
219        name,
220        type_ref,
221        optional,
222        sequence_max_length,
223        ..Default::default()
224    })
225}
226
227/// Parst `<xsd:simpleType name="X"><xsd:restriction base="xsd:string">
228/// <xsd:enumeration value="A"/>...</xsd:restriction></xsd:simpleType>`
229/// und liefert `Some(EnumType)`. Wenn keine `<xsd:enumeration>`-
230/// Children vorhanden sind, ist das nur ein Type-Alias und wir liefern
231/// `None` (kein Enum).
232fn parse_simple_type_as_enum(node: &XmlElement) -> Result<Option<EnumType>, XmlError> {
233    let name = node.attribute("name").unwrap_or_default().to_string();
234    let restriction = match node.child("restriction") {
235        Some(r) => r,
236        None => return Ok(None),
237    };
238
239    let mut enumerators: Vec<EnumLiteral> = Vec::new();
240    for en in restriction.children_named("enumeration") {
241        if let Some(value) = en.attribute("value") {
242            enumerators.push(EnumLiteral {
243                name: value.to_string(),
244                value: None,
245            });
246        }
247    }
248    if enumerators.is_empty() {
249        return Ok(None);
250    }
251    Ok(Some(EnumType {
252        name,
253        bit_bound: None,
254        enumerators,
255    }))
256}
257
258#[cfg(test)]
259#[allow(clippy::expect_used, clippy::panic, clippy::unwrap_used)]
260mod tests {
261    use super::*;
262    use alloc::string::String;
263
264    fn lib_of(xml: &str) -> TypeLibrary {
265        parse_xsd_schema(xml).expect("parse")
266    }
267
268    #[test]
269    fn empty_schema_yields_empty_library() {
270        let xml = r#"<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema"/>"#;
271        let lib = lib_of(xml);
272        assert!(lib.types.is_empty());
273    }
274
275    #[test]
276    fn complex_type_maps_to_struct_with_primitive_members() {
277        // Spec §7.3.3 — XSD complexType + xsd:sequence → DDS struct.
278        let xml = r#"
279            <xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema">
280                <xsd:complexType name="Point">
281                    <xsd:sequence>
282                        <xsd:element name="x" type="xsd:int"/>
283                        <xsd:element name="y" type="xsd:int"/>
284                    </xsd:sequence>
285                </xsd:complexType>
286            </xsd:schema>
287        "#;
288        let lib = lib_of(xml);
289        assert_eq!(lib.types.len(), 1);
290        let TypeDef::Struct(s) = &lib.types[0] else {
291            panic!("expected struct, got {:?}", lib.types[0]);
292        };
293        assert_eq!(s.name, "Point");
294        assert_eq!(s.members.len(), 2);
295        assert_eq!(s.members[0].name, "x");
296        assert_eq!(
297            s.members[0].type_ref,
298            TypeRef::Primitive(PrimitiveType::Long)
299        );
300        assert_eq!(s.members[1].name, "y");
301    }
302
303    #[test]
304    fn xsd_long_maps_to_dds_longlong() {
305        // Spec §7.3.3 — xsd:long ist 64-bit → DDS longlong (§7.4.13).
306        let xml = r#"
307            <xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema">
308                <xsd:complexType name="Big">
309                    <xsd:sequence>
310                        <xsd:element name="v" type="xsd:long"/>
311                    </xsd:sequence>
312                </xsd:complexType>
313            </xsd:schema>
314        "#;
315        let lib = lib_of(xml);
316        let TypeDef::Struct(s) = &lib.types[0] else {
317            panic!()
318        };
319        assert_eq!(
320            s.members[0].type_ref,
321            TypeRef::Primitive(PrimitiveType::LongLong)
322        );
323    }
324
325    #[test]
326    fn unsigned_xsd_types_map_to_unsigned_dds_primitives() {
327        let xml = r#"
328            <xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema">
329                <xsd:complexType name="U">
330                    <xsd:sequence>
331                        <xsd:element name="a" type="xsd:unsignedByte"/>
332                        <xsd:element name="b" type="xsd:unsignedShort"/>
333                        <xsd:element name="c" type="xsd:unsignedInt"/>
334                        <xsd:element name="d" type="xsd:unsignedLong"/>
335                    </xsd:sequence>
336                </xsd:complexType>
337            </xsd:schema>
338        "#;
339        let lib = lib_of(xml);
340        let TypeDef::Struct(s) = &lib.types[0] else {
341            panic!()
342        };
343        assert_eq!(
344            s.members[0].type_ref,
345            TypeRef::Primitive(PrimitiveType::Octet)
346        );
347        assert_eq!(
348            s.members[1].type_ref,
349            TypeRef::Primitive(PrimitiveType::UShort)
350        );
351        assert_eq!(
352            s.members[2].type_ref,
353            TypeRef::Primitive(PrimitiveType::ULong)
354        );
355        assert_eq!(
356            s.members[3].type_ref,
357            TypeRef::Primitive(PrimitiveType::ULongLong)
358        );
359    }
360
361    #[test]
362    fn min_occurs_zero_yields_optional_member() {
363        let xml = r#"
364            <xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema">
365                <xsd:complexType name="Opt">
366                    <xsd:sequence>
367                        <xsd:element name="maybe" type="xsd:int" minOccurs="0"/>
368                    </xsd:sequence>
369                </xsd:complexType>
370            </xsd:schema>
371        "#;
372        let lib = lib_of(xml);
373        let TypeDef::Struct(s) = &lib.types[0] else {
374            panic!()
375        };
376        assert!(s.members[0].optional);
377    }
378
379    #[test]
380    fn max_occurs_unbounded_yields_sequence() {
381        let xml = r#"
382            <xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema">
383                <xsd:complexType name="WithSeq">
384                    <xsd:sequence>
385                        <xsd:element name="items" type="xsd:int" maxOccurs="unbounded"/>
386                    </xsd:sequence>
387                </xsd:complexType>
388            </xsd:schema>
389        "#;
390        let lib = lib_of(xml);
391        let TypeDef::Struct(s) = &lib.types[0] else {
392            panic!()
393        };
394        assert!(s.members[0].sequence_max_length.is_some());
395    }
396
397    #[test]
398    fn max_occurs_bounded_yields_bounded_sequence() {
399        let xml = r#"
400            <xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema">
401                <xsd:complexType name="WithBoundedSeq">
402                    <xsd:sequence>
403                        <xsd:element name="items" type="xsd:int" maxOccurs="5"/>
404                    </xsd:sequence>
405                </xsd:complexType>
406            </xsd:schema>
407        "#;
408        let lib = lib_of(xml);
409        let TypeDef::Struct(s) = &lib.types[0] else {
410            panic!()
411        };
412        assert_eq!(s.members[0].sequence_max_length, Some(5));
413    }
414
415    #[test]
416    fn simple_type_with_enumeration_maps_to_dds_enum() {
417        // Spec §7.3.3 — XSD restriction with enumeration → DDS enum.
418        let xml = r#"
419            <xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema">
420                <xsd:simpleType name="Color">
421                    <xsd:restriction base="xsd:string">
422                        <xsd:enumeration value="RED"/>
423                        <xsd:enumeration value="GREEN"/>
424                        <xsd:enumeration value="BLUE"/>
425                    </xsd:restriction>
426                </xsd:simpleType>
427            </xsd:schema>
428        "#;
429        let lib = lib_of(xml);
430        let TypeDef::Enum(e) = &lib.types[0] else {
431            panic!("expected enum, got {:?}", lib.types[0]);
432        };
433        assert_eq!(e.name, "Color");
434        assert_eq!(e.enumerators.len(), 3);
435        assert_eq!(e.enumerators[0].name, "RED");
436        assert_eq!(e.enumerators[1].name, "GREEN");
437        assert_eq!(e.enumerators[2].name, "BLUE");
438    }
439
440    #[test]
441    fn user_type_reference_yields_named_typeref() {
442        let xml = r#"
443            <xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema">
444                <xsd:complexType name="Origin">
445                    <xsd:sequence>
446                        <xsd:element name="lat" type="xsd:double"/>
447                    </xsd:sequence>
448                </xsd:complexType>
449                <xsd:complexType name="Trip">
450                    <xsd:sequence>
451                        <xsd:element name="from" type="Origin"/>
452                    </xsd:sequence>
453                </xsd:complexType>
454            </xsd:schema>
455        "#;
456        let lib = lib_of(xml);
457        assert_eq!(lib.types.len(), 2);
458        let TypeDef::Struct(trip) = &lib.types[1] else {
459            panic!()
460        };
461        assert_eq!(
462            trip.members[0].type_ref,
463            TypeRef::Named(String::from("Origin"))
464        );
465    }
466
467    #[test]
468    fn complex_content_extension_yields_inheritance() {
469        // Spec — `<xsd:complexContent><xsd:extension base="P">` ist
470        // XSD-Inheritance, das auf DDS-Struct-Inheritance gemapped wird.
471        let xml = r#"
472            <xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema">
473                <xsd:complexType name="Parent">
474                    <xsd:sequence>
475                        <xsd:element name="a" type="xsd:int"/>
476                    </xsd:sequence>
477                </xsd:complexType>
478                <xsd:complexType name="Child">
479                    <xsd:complexContent>
480                        <xsd:extension base="Parent">
481                            <xsd:sequence>
482                                <xsd:element name="b" type="xsd:int"/>
483                            </xsd:sequence>
484                        </xsd:extension>
485                    </xsd:complexContent>
486                </xsd:complexType>
487            </xsd:schema>
488        "#;
489        let lib = lib_of(xml);
490        let TypeDef::Struct(child) = &lib.types[1] else {
491            panic!()
492        };
493        assert_eq!(child.name, "Child");
494        assert_eq!(child.base_type.as_deref(), Some("Parent"));
495        assert_eq!(child.members.len(), 1);
496        assert_eq!(child.members[0].name, "b");
497    }
498
499    #[test]
500    fn non_schema_root_is_rejected() {
501        let xml = r#"<dds><types/></dds>"#;
502        let r = parse_xsd_schema(xml);
503        assert!(r.is_err(), "non-<xsd:schema> root muss rejected werden");
504    }
505
506    #[test]
507    fn xsd_to_typeobject_via_bridge_produces_minimal_struct() {
508        // End-to-End: XSD-Source → TypeLibrary →
509        // typeobject_bridge::bridge_library → MinimalTypeObject.
510        // Damit ist der Spec-Pfad XTypes 1.3 §7.3.3 voll abgedeckt
511        // (XSD → TypeObject auf Wire-Form).
512        use crate::typeobject_bridge::bridge_library;
513
514        let xml = r#"
515            <xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema">
516                <xsd:complexType name="Sensor">
517                    <xsd:sequence>
518                        <xsd:element name="id" type="xsd:int"/>
519                        <xsd:element name="name" type="xsd:string"/>
520                    </xsd:sequence>
521                </xsd:complexType>
522            </xsd:schema>
523        "#;
524        let lib = parse_xsd_schema(xml).expect("xsd-parse");
525        let map = bridge_library(&lib).expect("bridge");
526        assert!(
527            map.contains_key("Sensor"),
528            "TypeObject fuer Sensor fehlt: {:?}",
529            map.keys().collect::<Vec<_>>()
530        );
531    }
532}