Skip to main content

openbim_dt/
codec.rs

1//! Lossless XML codec for the owned ISO 23387 types.
2//!
3//! Every owned type has `to_element` and `from_element`:
4//!
5//! * `to_element` builds a `dt:`-qualified element named after the type's
6//!   global element (`dt:ObjectType`, `dt:Unit`, ...). It emits no `xmlns`
7//!   declarations: the embedding document owns them. Use
8//!   [`Document::standalone`](crate::Document::standalone) for a self-contained
9//!   document. Children are written in the order the schema declares them
10//!   (from the generated tables), so the output passes the order checks in
11//!   [`Document::validate_schema`](crate::Document::validate_schema).
12//! * `from_element` reads the element's *content* and ignores its local
13//!   name, because standards such as ISO 7817-3 embed ISO 23387 content under
14//!   their own unqualified element (`<ObjectType>` with `dt:`-qualified
15//!   children). The element itself must be ISO 23387 qualified or
16//!   unqualified; any other namespace is refused.
17//!
18//! # Contract
19//!
20//! `T::from_element(&value.to_element()) == Ok(value)` for every value. The
21//! reverse is a semantic projection: comments, processing instructions and
22//! child order within a repeating choice are not retained. Use the wire tree
23//! ([`Element`]) when those must survive.
24//!
25//! Decoding fails closed with a structured [`CodecError`] rather than dropping
26//! anything. In particular, a child the owned model does not represent,
27//! including a foreign-namespace extension, is reported as
28//! [`CodecErrorKind::UnknownChild`] instead of being skipped, and an
29//! attribute it cannot hold as [`CodecErrorKind::UnknownAttribute`].
30//!
31//! Values that are well typed but violate a schema minimum (for example a
32//! [`Subject`] with neither `HasPartRef` nor `IsSubtypeOfRef`) still encode;
33//! run schema validation on the result to detect that.
34
35use std::{error::Error, fmt, str::FromStr};
36
37use crate::{
38    schema::DEFINITIONS, AnyUri, Attribute, Base, Base64Binary, Concept, DataTemplate, DataType,
39    DataTypeConstraint, DataTypeName, DataValue, DateTime, Decimal, Dimension, Element,
40    GroupOfProperties, Guid, Language, MultiLanguageText, Node, NonNegativeInteger, ObjectType,
41    Property, QuantityKind, Rational, Reference, ReferenceDocument, Scale, Subject, Unit,
42    ValueError, ValueList, NAMESPACE,
43};
44
45const PREFIX: &str = "dt";
46
47/// Why decoding an owned value from an element failed.
48#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
49#[non_exhaustive]
50pub enum CodecErrorKind {
51    /// A required attribute is absent.
52    MissingAttribute,
53    /// A required child element is absent.
54    MissingChild,
55    /// A child occurs more often than the owned model can hold.
56    TooManyChildren,
57    /// A child the owned model does not represent (including extensions).
58    UnknownChild,
59    /// An attribute the owned model does not represent, including a schema
60    /// attribute in the wrong namespace (e.g. an unqualified `GUID`).
61    UnknownAttribute,
62    /// An attribute or text value is outside its lexical space.
63    InvalidValue,
64    /// Element content where the schema declares simple content, or the reverse.
65    UnexpectedContent,
66}
67
68/// A structured decoding failure: what went wrong and where.
69#[derive(Debug, Clone, PartialEq, Eq)]
70pub struct CodecError {
71    kind: CodecErrorKind,
72    path: String,
73    detail: String,
74}
75
76impl CodecError {
77    fn new(kind: CodecErrorKind, path: impl Into<String>, detail: impl Into<String>) -> Self {
78        Self {
79            kind,
80            path: path.into(),
81            detail: detail.into(),
82        }
83    }
84
85    /// Stable failure category.
86    #[must_use]
87    pub const fn kind(&self) -> CodecErrorKind {
88        self.kind
89    }
90
91    /// Location relative to the decoded element: `Name[2]`, `@GUID`,
92    /// `DataType/PossibleValues[1]/ValueList[1]`. Empty for the element itself.
93    #[must_use]
94    pub fn path(&self) -> &str {
95        &self.path
96    }
97
98    /// Human-readable detail. Not stable; match on [`CodecError::kind`].
99    #[must_use]
100    pub fn detail(&self) -> &str {
101        &self.detail
102    }
103
104    fn nested(mut self, parent: &str) -> Self {
105        self.path = if self.path.is_empty() {
106            parent.to_owned()
107        } else {
108            format!("{parent}/{}", self.path)
109        };
110        self
111    }
112}
113
114impl fmt::Display for CodecError {
115    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
116        write!(
117            formatter,
118            "{:?} at `{}`: {}",
119            self.kind, self.path, self.detail
120        )
121    }
122}
123
124impl Error for CodecError {}
125
126// ---------------------------------------------------------------------------
127// Schema-driven ordering
128// ---------------------------------------------------------------------------
129
130/// Declared child order for a type definition, from the generated tables.
131fn declared_children(handle: &str) -> &'static [crate::schema::ChildRule] {
132    DEFINITIONS
133        .iter()
134        .find(|definition| definition.handle == handle)
135        .map_or(&[], |definition| definition.children)
136}
137
138/// Accumulates children by name, then emits them in schema order.
139struct ChildWriter {
140    handle: &'static str,
141    children: Vec<(&'static str, Element)>,
142}
143
144impl ChildWriter {
145    const fn new(handle: &'static str) -> Self {
146        Self {
147            handle,
148            children: Vec::new(),
149        }
150    }
151
152    fn push(&mut self, name: &'static str, element: Element) {
153        self.children.push((name, element));
154    }
155
156    fn texts(&mut self, name: &'static str, values: &[MultiLanguageText]) {
157        for value in values {
158            self.push(name, multilingual(name, value));
159        }
160    }
161
162    fn references(&mut self, name: &'static str, values: &[Reference]) {
163        for value in values {
164            self.push(name, reference(name, value));
165        }
166    }
167
168    fn simple<'a>(&mut self, name: &'static str, values: impl IntoIterator<Item = &'a str>) {
169        for value in values {
170            self.push(name, dt_element(name).with_text(value));
171        }
172    }
173
174    /// Emits children grouped in declared order, preserving insertion order
175    /// within each name. Panics in debug builds if a pushed name is not
176    /// declared for this handle: that would be a codec bug, and the drift
177    /// tests exercise every name.
178    fn finish(self, mut element: Element) -> Element {
179        let order = declared_children(self.handle);
180        debug_assert!(
181            self.children
182                .iter()
183                .all(|(name, _)| order.iter().any(|rule| rule.name == *name)),
184            "codec wrote a child not declared on {}",
185            self.handle
186        );
187        let mut children = self.children;
188        for rule in order {
189            let (matching, rest): (Vec<_>, Vec<_>) = children
190                .into_iter()
191                .partition(|(name, _)| *name == rule.name);
192            children = rest;
193            for (_, child) in matching {
194                element = element.with_child(child);
195            }
196        }
197        element
198    }
199}
200
201fn dt_element(local_name: &str) -> Element {
202    Element::new(Some(NAMESPACE), Some(PREFIX), local_name)
203}
204
205fn dt_attribute(local_name: &str, value: &str) -> Attribute {
206    Attribute::new(Some(PREFIX), local_name, Some(NAMESPACE), value)
207}
208
209fn plain_attribute(local_name: &str, value: &str) -> Attribute {
210    Attribute::new(None, local_name, None, value)
211}
212
213fn multilingual(name: &str, value: &MultiLanguageText) -> Element {
214    dt_element(name)
215        .with_attribute(plain_attribute("language", value.language()))
216        .with_text(value.text())
217}
218
219fn reference(name: &str, value: &Reference) -> Element {
220    let mut element = dt_element(name);
221    if let Some(uri) = value.uri() {
222        element = element.with_attribute(dt_attribute("referenceURI", uri));
223    }
224    if let Some(guid) = value.guid() {
225        element = element.with_attribute(dt_attribute("GUID", guid.as_str()));
226    }
227    element
228}
229
230// ---------------------------------------------------------------------------
231// Encoding
232// ---------------------------------------------------------------------------
233
234fn concept_attributes(element: Element, concept: &Concept) -> Element {
235    let mut element = element;
236    if let Some(about) = concept.about_value() {
237        element = element.with_attribute(dt_attribute("about", about.as_str()));
238    }
239    element
240        .with_attribute(dt_attribute("GUID", concept.guid().as_str()))
241        .with_attribute(plain_attribute(
242            "dateOfCreation",
243            concept.date_of_creation_value().as_str(),
244        ))
245}
246
247fn write_concept(children: &mut ChildWriter, concept: &Concept) {
248    children.texts("Name", concept.names());
249    children.texts("Definition", concept.definitions());
250    children.references("ReferenceDocumentRef", concept.reference_document_refs());
251    children.texts("Description", concept.descriptions());
252    children.texts("Example", concept.examples());
253    children.references("SimilarToRef", concept.similar_to_refs());
254    children.simple(
255        "LanguageOfCreator",
256        concept.languages_of_creator().iter().map(Language::as_str),
257    );
258    children.simple(
259        "CountryOfOrigin",
260        concept.countries_of_origin().iter().map(String::as_str),
261    );
262    children.simple(
263        "VisualRepresentation",
264        concept
265            .visual_representations()
266            .iter()
267            .map(Base64Binary::as_str),
268    );
269    children.simple(
270        "MajorVersion",
271        concept
272            .major_versions()
273            .iter()
274            .map(NonNegativeInteger::as_str),
275    );
276    children.simple(
277        "MinorVersion",
278        concept
279            .minor_versions()
280            .iter()
281            .map(NonNegativeInteger::as_str),
282    );
283    children.simple("Status", concept.statuses().iter().map(String::as_str));
284    children.references("ReplacedObjectsRef", concept.replaced_objects_refs());
285    children.simple(
286        "DeprecationExplanation",
287        concept
288            .deprecation_explanations()
289            .iter()
290            .map(String::as_str),
291    );
292    children.references("DictionaryRef", concept.dictionary_refs());
293}
294
295fn write_subject(children: &mut ChildWriter, subject: &Subject) {
296    write_concept(children, subject.concept());
297    children.references("HasPartRef", subject.has_part_refs());
298    children.references("IsSubtypeOfRef", subject.is_subtype_of_refs());
299}
300
301fn encode(
302    local_name: &'static str,
303    handle: &'static str,
304    concept: &Concept,
305    fill: impl FnOnce(&mut ChildWriter),
306) -> Element {
307    let mut children = ChildWriter::new(handle);
308    fill(&mut children);
309    children.finish(concept_attributes(dt_element(local_name), concept))
310}
311
312fn data_type_element(value: &DataType) -> Element {
313    let mut element = dt_element("DataType");
314    if let Some(name) = value.name() {
315        element = element.with_attribute(plain_attribute("name", name.as_str()));
316    }
317    let mut children = ChildWriter::new("DataTypeType");
318    for constraint in value.constraints() {
319        let (name, bound) = match constraint {
320            DataTypeConstraint::MinInclusive(bound) => ("MinInclusive", bound),
321            DataTypeConstraint::MinExclusive(bound) => ("MinExclusive", bound),
322            DataTypeConstraint::MaxInclusive(bound) => ("MaxInclusive", bound),
323            DataTypeConstraint::MaxExclusive(bound) => ("MaxExclusive", bound),
324        };
325        children.push(
326            name,
327            dt_element(name).with_attribute(plain_attribute("value", bound)),
328        );
329    }
330    for format in value.data_formats() {
331        children.push(
332            "DataFormat",
333            dt_element("DataFormat").with_attribute(plain_attribute("value", format)),
334        );
335    }
336    for list in value.possible_values() {
337        let mut values = dt_element("ValueList")
338            .with_attribute(plain_attribute("language", list.language().as_str()));
339        for item in list.values() {
340            let mut entry = dt_element("Value");
341            if let Some(order) = item.order() {
342                entry = entry.with_attribute(plain_attribute("order", &order.to_string()));
343            }
344            values = values.with_child(entry.with_text(item.value()));
345        }
346        children.push(
347            "PossibleValues",
348            dt_element("PossibleValues").with_child(values),
349        );
350    }
351    children.finish(element)
352}
353
354impl Concept {
355    /// Encodes this concept as a `dt:Subject`-free `ConceptType` element named
356    /// `local_name`. Concrete types use their own `to_element`.
357    #[must_use]
358    pub fn to_element_named(&self, local_name: &'static str) -> Element {
359        encode(local_name, "ConceptType", self, |children| {
360            write_concept(children, self);
361        })
362    }
363}
364
365impl Subject {
366    /// Encodes as a global `dt:Subject` element.
367    #[must_use]
368    pub fn to_element(&self) -> Element {
369        encode("Subject", "SubjectType", self.concept(), |children| {
370            write_subject(children, self);
371        })
372    }
373}
374
375impl ObjectType {
376    /// Encodes as a global `dt:ObjectType` element.
377    #[must_use]
378    pub fn to_element(&self) -> Element {
379        let subject = self.subject();
380        encode(
381            "ObjectType",
382            "ObjectTypeType",
383            subject.concept(),
384            |children| {
385                write_subject(children, subject);
386            },
387        )
388    }
389}
390
391impl GroupOfProperties {
392    /// Encodes as a global `dt:GroupOfProperties` element.
393    #[must_use]
394    pub fn to_element(&self) -> Element {
395        let subject = self.subject();
396        encode(
397            "GroupOfProperties",
398            "GroupOfPropertiesType",
399            subject.concept(),
400            |children| {
401                write_subject(children, subject);
402                children.references("HasPropertyRef", self.property_refs());
403            },
404        )
405    }
406}
407
408impl DataTemplate {
409    /// Encodes as a global `dt:DataTemplate` element.
410    #[must_use]
411    pub fn to_element(&self) -> Element {
412        let subject = self.subject();
413        encode(
414            "DataTemplate",
415            "DataTemplateType",
416            subject.concept(),
417            |children| {
418                write_subject(children, subject);
419                children.references("HasObjectTypeRef", self.object_type_refs());
420                children.references("HasPropertyRef", self.property_refs());
421                children.references("HasGroupOfPropertiesRef", self.group_of_properties_refs());
422            },
423        )
424    }
425}
426
427impl Property {
428    /// Encodes as a global `dt:Property` element.
429    #[must_use]
430    pub fn to_element(&self) -> Element {
431        encode("Property", "PropertyType", self.concept(), |children| {
432            write_concept(children, self.concept());
433            children.push("DataType", data_type_element(self.data_type()));
434            children.simple("Symbol", self.symbols().iter().map(String::as_str));
435            children.references("DimensionRef", self.dimension_refs());
436            children.references("UnitRef", self.unit_refs());
437            children.references("QuantityKindRef", self.quantity_kind_refs());
438            children.references("IsDependentOnRef", self.dependency_refs());
439            children.references(
440                "IsSpecializationOfRef",
441                self.specialization_ref().map_or(&[], std::slice::from_ref),
442            );
443        })
444    }
445}
446
447impl QuantityKind {
448    /// Encodes as a `dt:QuantityKind` element (library member, not a global root).
449    #[must_use]
450    pub fn to_element(&self) -> Element {
451        encode(
452            "QuantityKind",
453            "QuantityKindType",
454            self.concept(),
455            |children| {
456                write_concept(children, self.concept());
457                children.references("DimensionRef", std::slice::from_ref(self.dimension_ref()));
458            },
459        )
460    }
461}
462
463/// Exponent element names in the order of [`Dimension::exponents`].
464pub(crate) const DIMENSION_EXPONENTS: [&str; 7] = [
465    "DimensionExponentForAmountOfSubstance",
466    "DimensionExponentForElectricCurrent",
467    "DimensionExponentForLength",
468    "DimensionExponentForLuminousIntensity",
469    "DimensionExponentForMass",
470    "DimensionExponentForThermodynamicTemperature",
471    "DimensionExponentForTime",
472];
473
474impl Dimension {
475    /// Encodes as a `dt:Dimension` element (library member, not a global root).
476    #[must_use]
477    pub fn to_element(&self) -> Element {
478        encode("Dimension", "DimensionType", self.concept(), |children| {
479            write_concept(children, self.concept());
480            for (name, exponent) in DIMENSION_EXPONENTS.into_iter().zip(self.exponents()) {
481                children.simple(name, [exponent.as_str()]);
482            }
483        })
484    }
485}
486
487impl Unit {
488    /// Encodes as a `dt:Unit` element (library member, not a global root).
489    #[must_use]
490    pub fn to_element(&self) -> Element {
491        encode("Unit", "UnitType", self.concept(), |children| {
492            write_concept(children, self.concept());
493            children.texts("Symbol", self.symbols());
494            children.references("DimensionRef", std::slice::from_ref(self.dimension_ref()));
495            children.simple("Scale", [self.scale().as_str()]);
496            children.simple("Base", [self.base().as_str()]);
497            children.simple("Coefficient", [self.coefficient().as_str()]);
498            children.simple("Offset", [self.offset().as_str()]);
499        })
500    }
501}
502
503impl ReferenceDocument {
504    /// Encodes as a `dt:ReferenceDocument` element (library member).
505    #[must_use]
506    pub fn to_element(&self) -> Element {
507        encode(
508            "ReferenceDocument",
509            "ReferenceDocumentType",
510            self.concept(),
511            |children| {
512                write_concept(children, self.concept());
513                children.simple(
514                    "DateOfPublication",
515                    self.date_of_publication().map(DateTime::as_str),
516                );
517                children.simple("Author", self.author());
518                children.simple("ISBN", self.isbn());
519                children.simple("Language", self.languages().iter().map(Language::as_str));
520                children.simple("Publisher", self.publisher());
521                children.simple("URI", self.uri());
522            },
523        )
524    }
525}
526
527// ---------------------------------------------------------------------------
528// Decoding
529// ---------------------------------------------------------------------------
530
531/// Children of one element, indexed for positional error paths and checked
532/// off as the decoder consumes them. Anything left over is an unknown child.
533struct Reader<'a> {
534    element: &'a Element,
535    consumed: Vec<bool>,
536}
537
538impl<'a> Reader<'a> {
539    fn new(element: &'a Element) -> Result<Self, CodecError> {
540        for node in element.nodes() {
541            if let Node::Text(text) | Node::CData(text) = node {
542                if !text.chars().all(|c| matches!(c, ' ' | '\t' | '\r' | '\n')) {
543                    return Err(CodecError::new(
544                        CodecErrorKind::UnexpectedContent,
545                        "",
546                        "character data in element-only content",
547                    ));
548                }
549            }
550        }
551        let count = element.children().count();
552        Ok(Self {
553            element,
554            consumed: vec![false; count],
555        })
556    }
557
558    /// Every dt child named `name`, with its 1-based path index.
559    fn take(&mut self, name: &str) -> Vec<(String, &'a Element)> {
560        let mut index = 0;
561        let mut found = Vec::new();
562        for (position, child) in self.element.children().enumerate() {
563            if child.namespace_uri() == Some(NAMESPACE) && child.local_name() == name {
564                index += 1;
565                self.consumed[position] = true;
566                found.push((format!("{name}[{index}]"), child));
567            }
568        }
569        found
570    }
571
572    fn texts(&mut self, name: &str) -> Result<Vec<MultiLanguageText>, CodecError> {
573        self.take(name)
574            .into_iter()
575            .map(|(path, child)| read_multilingual(child).map_err(|error| error.nested(&path)))
576            .collect()
577    }
578
579    fn references(&mut self, name: &str) -> Result<Vec<Reference>, CodecError> {
580        self.take(name)
581            .into_iter()
582            .map(|(path, child)| read_reference(child).map_err(|error| error.nested(&path)))
583            .collect()
584    }
585
586    fn simple<T>(
587        &mut self,
588        name: &str,
589        parse: impl Fn(&str) -> Result<T, ValueError>,
590    ) -> Result<Vec<T>, CodecError> {
591        self.take(name)
592            .into_iter()
593            .map(|(path, child)| {
594                refuse_unrepresented_attributes(child, &[]).map_err(|error| error.nested(&path))?;
595                let text = simple_text(child).map_err(|error| error.nested(&path))?;
596                parse(&text).map_err(|error| invalid(&path, &error))
597            })
598            .collect()
599    }
600
601    fn strings(&mut self, name: &str) -> Result<Vec<String>, CodecError> {
602        self.simple(name, |text| Ok(text.to_owned()))
603    }
604
605    fn at_most_one<T>(name: &str, mut values: Vec<T>) -> Result<Option<T>, CodecError> {
606        if values.len() > 1 {
607            return Err(CodecError::new(
608                CodecErrorKind::TooManyChildren,
609                format!("{name}[2]"),
610                format!(
611                    "`{name}` occurs {} times; at most one is declared",
612                    values.len()
613                ),
614            ));
615        }
616        Ok(values.pop())
617    }
618
619    fn exactly_one<T>(name: &str, values: Vec<T>) -> Result<T, CodecError> {
620        Self::at_most_one(name, values)?.ok_or_else(|| {
621            CodecError::new(
622                CodecErrorKind::MissingChild,
623                "",
624                format!("required child `{name}` is missing"),
625            )
626        })
627    }
628
629    /// Fails on the first child nothing consumed.
630    fn finish(self) -> Result<(), CodecError> {
631        let unknown = self
632            .element
633            .children()
634            .zip(&self.consumed)
635            .find(|(_, consumed)| !**consumed);
636        match unknown {
637            None => Ok(()),
638            Some((child, _)) => Err(CodecError::new(
639                CodecErrorKind::UnknownChild,
640                child.qname(),
641                format!(
642                    "`{}` ({}) is not represented by the owned model",
643                    child.local_name(),
644                    child.namespace_uri().unwrap_or("no namespace")
645                ),
646            )),
647        }
648    }
649}
650
651/// An attribute identity: namespace and local name.
652type AttributeName = (Option<&'static str>, &'static str);
653
654/// `ConceptType` attributes. `about` and `GUID` are global attribute
655/// declarations, hence namespace-qualified; `dateOfCreation` is local and
656/// unqualified.
657const CONCEPT_ATTRIBUTES: &[AttributeName] = &[
658    (Some(NAMESPACE), "about"),
659    (Some(NAMESPACE), "GUID"),
660    (None, "dateOfCreation"),
661];
662const REFERENCE_ATTRIBUTES: &[AttributeName] =
663    &[(Some(NAMESPACE), "GUID"), (Some(NAMESPACE), "referenceURI")];
664const LANGUAGE_ATTRIBUTE: &[AttributeName] = &[(None, "language")];
665const VALUE_ATTRIBUTE: &[AttributeName] = &[(None, "value")];
666
667/// Refuses any attribute the owned model cannot hold, so decoding never
668/// silently drops content. Namespace declarations are syntax, not content.
669fn refuse_unrepresented_attributes(
670    element: &Element,
671    allowed: &[AttributeName],
672) -> Result<(), CodecError> {
673    for attribute in element.attributes() {
674        if attribute.qname() == "xmlns" || attribute.prefix() == Some("xmlns") {
675            continue;
676        }
677        let namespace = attribute.namespace_uri();
678        let local_name = attribute.local_name();
679        if !allowed
680            .iter()
681            .any(|(ns, name)| *ns == namespace && *name == local_name)
682        {
683            return Err(CodecError::new(
684                CodecErrorKind::UnknownAttribute,
685                format!("@{}", attribute.qname()),
686                format!(
687                    "attribute `{local_name}` ({}) is not represented by the owned model",
688                    namespace.unwrap_or("no namespace")
689                ),
690            ));
691        }
692    }
693    Ok(())
694}
695
696/// Refuses child elements and non-whitespace text in empty content.
697fn empty_content(element: &Element) -> Result<(), CodecError> {
698    let text = simple_text(element)?;
699    if text.chars().all(|c| matches!(c, ' ' | '\t' | '\r' | '\n')) {
700        Ok(())
701    } else {
702        Err(CodecError::new(
703            CodecErrorKind::UnexpectedContent,
704            "",
705            "character data where empty content is declared",
706        ))
707    }
708}
709
710fn invalid(path: &str, error: &ValueError) -> CodecError {
711    CodecError::new(CodecErrorKind::InvalidValue, path, error.to_string())
712}
713
714/// Text content of a simple-content element; child elements are refused.
715fn simple_text(element: &Element) -> Result<String, CodecError> {
716    if element.children().next().is_some() {
717        return Err(CodecError::new(
718            CodecErrorKind::UnexpectedContent,
719            "",
720            "element content where simple content is declared",
721        ));
722    }
723    Ok(element.direct_text())
724}
725
726fn read_multilingual(element: &Element) -> Result<MultiLanguageText, CodecError> {
727    refuse_unrepresented_attributes(element, LANGUAGE_ATTRIBUTE)?;
728    let language = element.attribute_ns(None, "language").ok_or_else(|| {
729        CodecError::new(
730            CodecErrorKind::MissingAttribute,
731            "@language",
732            "required attribute `language` is missing",
733        )
734    })?;
735    let text = simple_text(element)?;
736    MultiLanguageText::new(language, text).map_err(|error| invalid("@language", &error))
737}
738
739fn read_reference(element: &Element) -> Result<Reference, CodecError> {
740    refuse_unrepresented_attributes(element, REFERENCE_ATTRIBUTES)?;
741    empty_content(element)?;
742    let guid = element
743        .attribute_ns(Some(NAMESPACE), "GUID")
744        .map(Guid::from_str)
745        .transpose()
746        .map_err(|error| invalid("@GUID", &error))?;
747    let uri = element
748        .attribute_ns(Some(NAMESPACE), "referenceURI")
749        .map(AnyUri::from_str)
750        .transpose()
751        .map_err(|error| invalid("@referenceURI", &error))?;
752    Ok(Reference::new(guid, uri))
753}
754
755fn required_attribute<'a>(
756    element: &'a Element,
757    namespace: Option<&str>,
758    name: &str,
759) -> Result<&'a str, CodecError> {
760    element.attribute_ns(namespace, name).ok_or_else(|| {
761        CodecError::new(
762            CodecErrorKind::MissingAttribute,
763            format!("@{name}"),
764            format!("required attribute `{name}` is missing"),
765        )
766    })
767}
768
769fn read_concept(element: &Element, children: &mut Reader<'_>) -> Result<Concept, CodecError> {
770    refuse_unrepresented_attributes(element, CONCEPT_ATTRIBUTES)?;
771    let guid = Guid::from_str(required_attribute(element, Some(NAMESPACE), "GUID")?)
772        .map_err(|error| invalid("@GUID", &error))?;
773    let date = DateTime::from_str(required_attribute(element, None, "dateOfCreation")?)
774        .map_err(|error| invalid("@dateOfCreation", &error))?;
775    let mut concept = Concept::from_identity(guid, date);
776    if let Some(about) = element.attribute_ns(Some(NAMESPACE), "about") {
777        concept.set_about(Some(
778            AnyUri::from_str(about).map_err(|error| invalid("@about", &error))?,
779        ));
780    }
781    for value in children.texts("Name")? {
782        concept.add_name(value);
783    }
784    for value in children.texts("Definition")? {
785        concept.add_definition(value);
786    }
787    for value in children.references("ReferenceDocumentRef")? {
788        concept.add_reference_document_ref(value);
789    }
790    for value in children.texts("Description")? {
791        concept.add_description(value);
792    }
793    for value in children.texts("Example")? {
794        concept.add_example(value);
795    }
796    for value in children.references("SimilarToRef")? {
797        concept.add_similar_to_ref(value);
798    }
799    for value in children.simple("LanguageOfCreator", Language::from_str)? {
800        concept.add_language_of_creator(value);
801    }
802    for value in children.strings("CountryOfOrigin")? {
803        concept.add_country_of_origin(value);
804    }
805    for value in children.simple("VisualRepresentation", Base64Binary::from_str)? {
806        concept.add_visual_representation(value);
807    }
808    for value in children.simple("MajorVersion", NonNegativeInteger::from_str)? {
809        concept.add_major_version(value);
810    }
811    for value in children.simple("MinorVersion", NonNegativeInteger::from_str)? {
812        concept.add_minor_version(value);
813    }
814    for value in children.strings("Status")? {
815        concept.add_status(value);
816    }
817    for value in children.references("ReplacedObjectsRef")? {
818        concept.add_replaced_objects_ref(value);
819    }
820    for value in children.strings("DeprecationExplanation")? {
821        concept.add_deprecation_explanation(value);
822    }
823    for value in children.references("DictionaryRef")? {
824        concept.add_dictionary_ref(value);
825    }
826    Ok(concept)
827}
828
829fn read_subject(element: &Element, children: &mut Reader<'_>) -> Result<Subject, CodecError> {
830    let mut subject = Subject::new(read_concept(element, children)?);
831    for value in children.references("HasPartRef")? {
832        subject.add_has_part_ref(value);
833    }
834    for value in children.references("IsSubtypeOfRef")? {
835        subject.add_is_subtype_of_ref(value);
836    }
837    Ok(subject)
838}
839
840fn read_data_type(element: &Element) -> Result<DataType, CodecError> {
841    refuse_unrepresented_attributes(element, &[(None, "name")])?;
842    let mut children = Reader::new(element)?;
843    let mut value = DataType::new(element.attribute_ns(None, "name").map(DataTypeName::from));
844    // Constraints are one choice group; read them in document order so a
845    // mixed Min/Max sequence round-trips in its original order.
846    for (position, child) in element.children().enumerate() {
847        if child.namespace_uri() != Some(NAMESPACE) {
848            continue;
849        }
850        let make: fn(String) -> DataTypeConstraint = match child.local_name() {
851            "MinInclusive" => DataTypeConstraint::MinInclusive,
852            "MinExclusive" => DataTypeConstraint::MinExclusive,
853            "MaxInclusive" => DataTypeConstraint::MaxInclusive,
854            "MaxExclusive" => DataTypeConstraint::MaxExclusive,
855            _ => continue,
856        };
857        children.consumed[position] = true;
858        let path = format!("{}[{}]", child.local_name(), position + 1);
859        refuse_unrepresented_attributes(child, VALUE_ATTRIBUTE)
860            .map_err(|error| error.nested(&path))?;
861        let bound = child.attribute_ns(None, "value").ok_or_else(|| {
862            CodecError::new(
863                CodecErrorKind::MissingAttribute,
864                format!("{path}/@value"),
865                "a bound without `value` cannot be represented",
866            )
867        })?;
868        simple_text(child).map_err(|error| error.nested(&path))?;
869        empty_content(child).map_err(|error| error.nested(&path))?;
870        value.add_constraint(make(bound.to_owned()));
871    }
872    for (path, child) in children.take("DataFormat") {
873        refuse_unrepresented_attributes(child, VALUE_ATTRIBUTE)
874            .map_err(|error| error.nested(&path))?;
875        empty_content(child).map_err(|error| error.nested(&path))?;
876        let format = child.attribute_ns(None, "value").ok_or_else(|| {
877            CodecError::new(
878                CodecErrorKind::MissingAttribute,
879                format!("{path}/@value"),
880                "a data format without `value` cannot be represented",
881            )
882        })?;
883        value.add_data_format(format);
884    }
885    for (path, possible) in children.take("PossibleValues") {
886        refuse_unrepresented_attributes(possible, &[]).map_err(|error| error.nested(&path))?;
887        let mut lists = Reader::new(possible).map_err(|error| error.nested(&path))?;
888        for (list_path, list) in lists.take("ValueList") {
889            let nested = format!("{path}/{list_path}");
890            value
891                .add_possible_values(read_value_list(list).map_err(|error| error.nested(&nested))?);
892        }
893        lists.finish().map_err(|error| error.nested(&path))?;
894    }
895    children.finish()?;
896    Ok(value)
897}
898
899fn read_value_list(element: &Element) -> Result<ValueList, CodecError> {
900    refuse_unrepresented_attributes(element, LANGUAGE_ATTRIBUTE)?;
901    let language = Language::from_str(required_attribute(element, None, "language")?)
902        .map_err(|error| invalid("@language", &error))?;
903    let mut children = Reader::new(element)?;
904    let mut values = Vec::new();
905    for (path, child) in children.take("Value") {
906        refuse_unrepresented_attributes(child, &[(None, "order")])
907            .map_err(|error| error.nested(&path))?;
908        let order = child
909            .attribute_ns(None, "order")
910            .map(|order| {
911                order.trim().parse::<i32>().map_err(|_| {
912                    CodecError::new(
913                        CodecErrorKind::InvalidValue,
914                        format!("{path}/@order"),
915                        format!("`{order}` is not an xs:int"),
916                    )
917                })
918            })
919            .transpose()?;
920        let text = simple_text(child).map_err(|error| error.nested(&path))?;
921        values.push(DataValue::new(text, order));
922    }
923    children.finish()?;
924    let mut values = values.into_iter();
925    let first = values.next().ok_or_else(|| {
926        CodecError::new(
927            CodecErrorKind::MissingChild,
928            "",
929            "a value list requires at least one `Value`",
930        )
931    })?;
932    let mut list = ValueList::new(language, first);
933    for value in values {
934        list.add_value(value);
935    }
936    Ok(list)
937}
938
939/// Decodes one owned value. The element's local name is deliberately not
940/// checked: the same type appears under several names (a `Unit` is also a
941/// `Library` child, LOIN embeds `ObjectType` content under its own element),
942/// so the caller chooses. Its namespace must be ISO 23387, or it has none
943/// when a host standard such as LOIN wraps DT content in a local element.
944fn decode<T>(
945    element: &Element,
946    read: impl FnOnce(&mut Reader<'_>) -> Result<T, CodecError>,
947) -> Result<T, CodecError> {
948    if !matches!(element.namespace_uri(), None | Some(NAMESPACE)) {
949        return Err(CodecError::new(
950            CodecErrorKind::UnexpectedContent,
951            "",
952            format!(
953                "`{}` is in namespace {}, not ISO 23387",
954                element.local_name(),
955                element.namespace_uri().unwrap_or_default()
956            ),
957        ));
958    }
959    let mut children = Reader::new(element)?;
960    let value = read(&mut children)?;
961    children.finish()?;
962    Ok(value)
963}
964
965impl Concept {
966    /// Decodes plain `ConceptType` content (any element name).
967    pub fn from_element(element: &Element) -> Result<Self, CodecError> {
968        decode(element, |children| read_concept(element, children))
969    }
970}
971
972impl Subject {
973    /// Decodes `SubjectType` content (any element name).
974    pub fn from_element(element: &Element) -> Result<Self, CodecError> {
975        decode(element, |children| read_subject(element, children))
976    }
977}
978
979impl ObjectType {
980    /// Decodes `ObjectTypeType` content (any element name).
981    pub fn from_element(element: &Element) -> Result<Self, CodecError> {
982        decode(element, |children| {
983            read_subject(element, children).map(Self::new)
984        })
985    }
986}
987
988impl GroupOfProperties {
989    /// Decodes `GroupOfPropertiesType` content (any element name).
990    pub fn from_element(element: &Element) -> Result<Self, CodecError> {
991        decode(element, |children| {
992            let subject = read_subject(element, children)?;
993            let mut refs = children.references("HasPropertyRef")?.into_iter();
994            // The owned type requires one property ref, matching the schema's
995            // minimum of one for its choice group.
996            let first = refs.next().ok_or_else(|| {
997                CodecError::new(
998                    CodecErrorKind::MissingChild,
999                    "",
1000                    "required child `HasPropertyRef` is missing",
1001                )
1002            })?;
1003            let mut group = Self::new(subject, first);
1004            for value in refs {
1005                group.add_property_ref(value);
1006            }
1007            Ok(group)
1008        })
1009    }
1010}
1011
1012impl DataTemplate {
1013    /// Decodes `DataTemplateType` content (any element name).
1014    pub fn from_element(element: &Element) -> Result<Self, CodecError> {
1015        decode(element, |children| {
1016            let mut template = Self::new(read_subject(element, children)?);
1017            for value in children.references("HasObjectTypeRef")? {
1018                template.add_object_type_ref(value);
1019            }
1020            for value in children.references("HasPropertyRef")? {
1021                template.add_property_ref(value);
1022            }
1023            for value in children.references("HasGroupOfPropertiesRef")? {
1024                template.add_group_of_properties_ref(value);
1025            }
1026            Ok(template)
1027        })
1028    }
1029}
1030
1031impl Property {
1032    /// Decodes `PropertyType` content (any element name).
1033    pub fn from_element(element: &Element) -> Result<Self, CodecError> {
1034        decode(element, |children| {
1035            let concept = read_concept(element, children)?;
1036            let data_type = Reader::exactly_one("DataType", children.take("DataType"))?;
1037            let data_type =
1038                read_data_type(data_type.1).map_err(|error| error.nested(&data_type.0))?;
1039            let mut property = Self::new(concept, data_type);
1040            for value in children.strings("Symbol")? {
1041                property.add_symbol(value);
1042            }
1043            for value in children.references("DimensionRef")? {
1044                property.add_dimension_ref(value);
1045            }
1046            for value in children.references("UnitRef")? {
1047                property.add_unit_ref(value);
1048            }
1049            for value in children.references("QuantityKindRef")? {
1050                property.add_quantity_kind_ref(value);
1051            }
1052            for value in children.references("IsDependentOnRef")? {
1053                property.add_dependency_ref(value);
1054            }
1055            property.set_specialization_ref(Reader::at_most_one(
1056                "IsSpecializationOfRef",
1057                children.references("IsSpecializationOfRef")?,
1058            )?);
1059            Ok(property)
1060        })
1061    }
1062}
1063
1064impl QuantityKind {
1065    /// Decodes `QuantityKindType` content (any element name).
1066    pub fn from_element(element: &Element) -> Result<Self, CodecError> {
1067        decode(element, |children| {
1068            let concept = read_concept(element, children)?;
1069            let dimension =
1070                Reader::exactly_one("DimensionRef", children.references("DimensionRef")?)?;
1071            Ok(Self::new(concept, dimension))
1072        })
1073    }
1074}
1075
1076impl Dimension {
1077    /// Decodes `DimensionType` content (any element name).
1078    pub fn from_element(element: &Element) -> Result<Self, CodecError> {
1079        decode(element, |children| {
1080            let concept = read_concept(element, children)?;
1081            let mut exponents = Vec::with_capacity(DIMENSION_EXPONENTS.len());
1082            for name in DIMENSION_EXPONENTS {
1083                exponents.push(Reader::exactly_one(
1084                    name,
1085                    children.simple(name, Decimal::from_str)?,
1086                )?);
1087            }
1088            let exponents: [Decimal; 7] = exponents
1089                .try_into()
1090                .unwrap_or_else(|_| unreachable!("seven names yield seven exponents"));
1091            Ok(Self::new(concept, exponents))
1092        })
1093    }
1094}
1095
1096impl Unit {
1097    /// Decodes `UnitType` content (any element name).
1098    pub fn from_element(element: &Element) -> Result<Self, CodecError> {
1099        decode(element, |children| {
1100            let concept = read_concept(element, children)?;
1101            let symbols = children.texts("Symbol")?;
1102            let dimension =
1103                Reader::exactly_one("DimensionRef", children.references("DimensionRef")?)?;
1104            let scale = Reader::exactly_one("Scale", children.strings("Scale")?)?;
1105            let base = Reader::exactly_one("Base", children.strings("Base")?)?;
1106            let coefficient = Reader::exactly_one(
1107                "Coefficient",
1108                children.simple("Coefficient", Rational::from_str)?,
1109            )?;
1110            let offset =
1111                Reader::exactly_one("Offset", children.simple("Offset", Rational::from_str)?)?;
1112            let mut unit = Self::new(
1113                concept,
1114                dimension,
1115                Scale::from(scale.as_str()),
1116                Base::from(base.as_str()),
1117                coefficient,
1118                offset,
1119            );
1120            for symbol in symbols {
1121                unit.add_symbol(symbol);
1122            }
1123            Ok(unit)
1124        })
1125    }
1126}
1127
1128impl ReferenceDocument {
1129    /// Decodes `ReferenceDocumentType` content (any element name).
1130    pub fn from_element(element: &Element) -> Result<Self, CodecError> {
1131        decode(element, |children| {
1132            let concept = read_concept(element, children)?;
1133            let mut languages = children.simple("Language", Language::from_str)?.into_iter();
1134            let first = languages.next().ok_or_else(|| {
1135                CodecError::new(
1136                    CodecErrorKind::MissingChild,
1137                    "",
1138                    "required child `Language` is missing",
1139                )
1140            })?;
1141            let mut document = Self::new(concept, first);
1142            for language in languages {
1143                document.add_language(language);
1144            }
1145            document.set_date_of_publication(Reader::at_most_one(
1146                "DateOfPublication",
1147                children.simple("DateOfPublication", DateTime::from_str)?,
1148            )?);
1149            document.set_author(Reader::at_most_one("Author", children.strings("Author")?)?);
1150            document.set_isbn(Reader::at_most_one("ISBN", children.strings("ISBN")?)?);
1151            document.set_publisher(Reader::at_most_one(
1152                "Publisher",
1153                children.strings("Publisher")?,
1154            )?);
1155            document.set_uri(Reader::at_most_one(
1156                "URI",
1157                children.simple("URI", AnyUri::from_str)?,
1158            )?);
1159            Ok(document)
1160        })
1161    }
1162}
1163
1164#[cfg(test)]
1165mod drift {
1166    //! Drift guard: the codec must write every child element the generated
1167    //! schema tables declare for each owned type, so a schema regeneration
1168    //! that adds a child fails here instead of being silently dropped.
1169
1170    use super::*;
1171    use crate::schema::DEFINITIONS;
1172
1173    fn declared(handle: &str) -> Vec<&'static str> {
1174        let definition = DEFINITIONS
1175            .iter()
1176            .find(|definition| definition.handle == handle)
1177            .unwrap_or_else(|| panic!("schema tables no longer define {handle}"));
1178        definition.children.iter().map(|rule| rule.name).collect()
1179    }
1180
1181    fn written(element: &Element) -> Vec<String> {
1182        let mut names: Vec<String> = element
1183            .children()
1184            .map(|child| child.local_name().to_owned())
1185            .collect();
1186        names.dedup();
1187        names
1188    }
1189
1190    fn assert_writes_every_declared_child(handle: &str, element: &Element) {
1191        let written = written(element);
1192        let missing: Vec<_> = declared(handle)
1193            .into_iter()
1194            .filter(|name| !written.iter().any(|w| w == name))
1195            .collect();
1196        assert!(
1197            missing.is_empty(),
1198            "{handle}: codec never writes {missing:?}"
1199        );
1200    }
1201
1202    // The same maximal fixtures the integration tests round-trip.
1203    use crate::{
1204        Base, Base64Binary, DataTypeConstraint, DataTypeName, DataValue, Decimal, Language,
1205        NonNegativeInteger, Rational, Scale, ValueList,
1206    };
1207    include!("../tests/support/maximal.rs");
1208
1209    #[test]
1210    fn codec_writes_every_child_the_schema_declares() {
1211        let cases = [
1212            (
1213                "ObjectTypeType",
1214                ObjectType::new(maximal_subject(1)).to_element(),
1215            ),
1216            ("PropertyType", maximal_property().to_element()),
1217            ("GroupOfPropertiesType", maximal_group().to_element()),
1218            ("DataTemplateType", maximal_template().to_element()),
1219            ("QuantityKindType", maximal_quantity_kind().to_element()),
1220            ("DimensionType", maximal_dimension().to_element()),
1221            ("UnitType", maximal_unit().to_element()),
1222            (
1223                "ReferenceDocumentType",
1224                maximal_reference_document().to_element(),
1225            ),
1226        ];
1227        for (handle, element) in &cases {
1228            assert_writes_every_declared_child(handle, element);
1229        }
1230    }
1231}