ifc_xml/lib.rs
1//! `ifc-xml` — the ifcXML (ISO 10303-28) codec.
2//!
3//! # Why this crate exists
4//!
5//! It is the proof that serialization is genuinely pluggable. It implements
6//! the same [`ifc_model::Codec`] trait as `ifc-step`, over the same
7//! [`ifc_model::Model`], and the model needed **no change** to accommodate it.
8//! A third encoding (IFC-JSON) would be another crate beside these two.
9//!
10//! # The interesting difference from STEP
11//!
12//! STEP records are **positional**: `#5=IFCWALL('guid',#1,$)`. ifcXML is
13//! **named**: `<IfcWall id="i5" GlobalId="guid" .../>`. Crossing between them
14//! needs the schema to map slot 0 to `GlobalId`.
15//!
16//! That would make the schema a hard dependency of the codec, which would
17//! break round-tripping for files whose schema we do not have. So the schema
18//! is **optional**:
19//!
20//! - **with** a schema: conformant named attributes.
21//! - **without**: positional fallback names (`a0`, `a1`, ...).
22//!
23//! Both round-trip losslessly, and the fallback is clearly marked in the
24//! output rather than silently producing wrong names. Namespace conformance
25//! is separately explicit: [`XmlCodec::strict`] selects one exact
26//! [`XmlProfile`], while the default keeps the historical compatibility
27//! dialect.
28//!
29//! Neither is valid against the release XSD. The strict profile writes this
30//! crate's own layout under the XSD's target namespace and the release's
31//! schema token; it does not write the XSD configuration (upper-case STEP
32//! type names, `i<n>` ids, `kind` elements and a `schema` root attribute
33//! are its own). For output the XSD accepts, use [`XmlCodec::xsd`] (below).
34//! An opt-in test, `tests/xsd_output.rs`, validates both with `xmllint`
35//! against the fetched XSDs: strict output departs only as it documents,
36//! XSD-layout output has no validity error at all.
37//!
38//! # Reading with a schema is strict
39//!
40//! XML attribute values are untyped strings. With a schema, the reader types
41//! each value from its attribute's declaration ([`SchemaReading::Strict`],
42//! the default): `Name="1"` is the label `'1'`, never the integer `1`, and a
43//! name the entity does not declare is an [`XmlError::UnknownAttribute`]
44//! naming entity, element and attribute, never a value in the next free
45//! slot. [`SchemaReading::Lenient`] restores inference, which round-trips
46//! models the schema does not describe at the price of misreading such
47//! values.
48//!
49//! # The buildingSMART XSD configuration
50//!
51//! [`XmlCodec::xsd`] reads and writes the ifcXML configuration the release
52//! XSD declares (IFC4 ADD2 TC1, IFC4X3 ADD2): entities nested and defined in
53//! place, `ref`/`href` references, inverse attributes, `-wrapper` typed
54//! values, space-separated list attributes. It reads into the same
55//! [`ifc_model::Model`] as the document's STEP form, and writes that model
56//! back: every entity at the top level, references as `ref` with `xsi:nil`,
57//! the attributes the configuration leaves off a relationship through the
58//! inverse of the entity they name. Written documents validate against
59//! `IFC4.xsd` and read back to the same model. Both directions refuse with
60//! a typed error what they cannot carry exactly; a model the configuration
61//! cannot represent is [`XmlError::Unrepresentable`], never different
62//! output. Reader and writer share one derivation of each attribute's form
63//! from the schema; the rules are in the `xsd` module documentation, and a
64//! schema-backed test checks them, element by element, against both XSDs.
65//!
66//! ```
67//! # #[cfg(feature = "schema")] {
68//! use ifc_xml::{XmlCodec, XmlProfile};
69//! use std::sync::Arc;
70//!
71//! # let schema = ifc_schema::Schema::from_express(
72//! # "SCHEMA IFC4; ENTITY IfcPerson; FamilyName : OPTIONAL STRING; END_ENTITY; END_SCHEMA;",
73//! # );
74//! // `schema` is the IFC4 schema, e.g. `ifc_schema::ifc4()`.
75//! let codec = XmlCodec::xsd(Arc::new(schema), XmlProfile::Ifc4Add2Tc1);
76//! let xml = br#"<ifcXML xmlns="https://standards.buildingsmart.org/IFC/RELEASE/IFC4/ADD2_TC1/XML">
77//! <IfcPerson FamilyName="1"/>
78//! </ifcXML>"#;
79//! let model = ifc_xml::reader::read(&codec, xml).unwrap();
80//! let person = model.get(ifc_model::EntityId(1)).unwrap();
81//! assert_eq!(&*person.type_name, "IFCPERSON");
82//! assert_eq!(person.text(0), Some("1"));
83//! # }
84//! ```
85//!
86//! ```
87//! # #[cfg(feature = "schema")] {
88//! use ifc_model::{Codec, Entity, EntityId, Model, Value};
89//! use ifc_xml::{XmlCodec, XmlProfile};
90//! use std::sync::Arc;
91//!
92//! # let schema = ifc_schema::Schema::from_express(
93//! # "SCHEMA IFC4; ENTITY IfcPerson; FamilyName : OPTIONAL STRING; END_ENTITY; END_SCHEMA;",
94//! # );
95//! let codec = XmlCodec::xsd(Arc::new(schema), XmlProfile::Ifc4Add2Tc1);
96//! let mut model = Model::new();
97//! model.header_mut().schema = vec!["IFC4".into()];
98//! model.insert(EntityId(1), Entity::new("IFCPERSON", vec![Value::Text("1".into())]));
99//! let xml = String::from_utf8(codec.write_bytes(&model).unwrap()).unwrap();
100//! assert!(xml.contains(r#"<IfcPerson id="i1" FamilyName="1"/>"#));
101//! assert_eq!(codec.read_bytes(xml.as_bytes()).unwrap().get(EntityId(1)), model.get(EntityId(1)));
102//! # }
103//! ```
104//!
105//! ```
106//! use ifc_model::{Codec, Entity, EntityId, Model, Value};
107//! use ifc_xml::XmlCodec;
108//!
109//! let mut model = Model::new();
110//! model.insert(
111//! EntityId(1),
112//! Entity::new("IFCCOSTITEM", vec![Value::Text("Excavation".into())]),
113//! );
114//!
115//! let bytes = XmlCodec::default().write_bytes(&model).unwrap();
116//! let reparsed = XmlCodec::default().read_bytes(&bytes).unwrap();
117//! assert_eq!(&*reparsed.get(EntityId(1)).unwrap().type_name, "IFCCOSTITEM");
118//! ```
119
120mod codec;
121pub mod error;
122mod profile;
123pub mod reader;
124mod scalar;
125mod slots;
126#[cfg(feature = "schema")]
127mod typing;
128pub mod writer;
129#[cfg(feature = "schema")]
130mod xsd;
131
132pub use codec::{SchemaReading, XmlCodec, XmlLayout};
133pub use error::{XmlError, XmlPath};
134pub use profile::XmlProfile;