Skip to main content

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). An opt-in test, `tests/xsd_output.rs`, validates strict
34//! output from the fixture corpus with `xmllint` against the fetched XSDs
35//! and fails on any departure beyond those it documents.
36//!
37//! # Reading with a schema is strict
38//!
39//! XML attribute values are untyped strings. With a schema, the reader types
40//! each value from its attribute's declaration ([`SchemaReading::Strict`],
41//! the default): `Name="1"` is the label `'1'`, never the integer `1`, and a
42//! name the entity does not declare is an [`XmlError::UnknownAttribute`]
43//! naming entity, element and attribute, never a value in the next free
44//! slot. [`SchemaReading::Lenient`] restores inference, which round-trips
45//! models the schema does not describe at the price of misreading such
46//! values.
47//!
48//! # The buildingSMART XSD configuration
49//!
50//! [`XmlCodec::xsd`] reads the ifcXML configuration the release XSD
51//! declares (IFC4 ADD2 TC1, IFC4X3 ADD2): entities nested and defined in
52//! place, `ref`/`href` references, inverse attributes, `-wrapper` typed
53//! values, space-separated list attributes. It reads into the same
54//! [`ifc_model::Model`] as the document's STEP form, and refuses with a
55//! typed error what it cannot read exactly. It does not write. The rules
56//! are in the `xsd` module documentation; a schema-backed test checks them
57//! against both XSDs.
58//!
59//! ```
60//! # #[cfg(feature = "schema")] {
61//! use ifc_xml::{XmlCodec, XmlProfile};
62//! use std::sync::Arc;
63//!
64//! # let schema = ifc_schema::Schema::from_express(
65//! #     "SCHEMA IFC4; ENTITY IfcPerson; FamilyName : OPTIONAL STRING; END_ENTITY; END_SCHEMA;",
66//! # );
67//! // `schema` is the IFC4 schema, e.g. `ifc_schema::ifc4()`.
68//! let codec = XmlCodec::xsd(Arc::new(schema), XmlProfile::Ifc4Add2Tc1);
69//! let xml = br#"<ifcXML xmlns="https://standards.buildingsmart.org/IFC/RELEASE/IFC4/ADD2_TC1/XML">
70//!   <IfcPerson FamilyName="1"/>
71//! </ifcXML>"#;
72//! let model = ifc_xml::reader::read(&codec, xml).unwrap();
73//! let person = model.get(ifc_model::EntityId(1)).unwrap();
74//! assert_eq!(&*person.type_name, "IFCPERSON");
75//! assert_eq!(person.text(0), Some("1"));
76//! # }
77//! ```
78//!
79//! ```
80//! use ifc_model::{Codec, Entity, EntityId, Model, Value};
81//! use ifc_xml::XmlCodec;
82//!
83//! let mut model = Model::new();
84//! model.insert(
85//!     EntityId(1),
86//!     Entity::new("IFCCOSTITEM", vec![Value::Text("Excavation".into())]),
87//! );
88//!
89//! let bytes = XmlCodec::default().write_bytes(&model).unwrap();
90//! let reparsed = XmlCodec::default().read_bytes(&bytes).unwrap();
91//! assert_eq!(&*reparsed.get(EntityId(1)).unwrap().type_name, "IFCCOSTITEM");
92//! ```
93
94mod codec;
95pub mod error;
96mod profile;
97pub mod reader;
98mod scalar;
99mod slots;
100#[cfg(feature = "schema")]
101mod typing;
102pub mod writer;
103#[cfg(feature = "schema")]
104mod xsd;
105
106pub use codec::{SchemaReading, XmlCodec, XmlLayout};
107pub use error::{XmlError, XmlPath};
108pub use profile::XmlProfile;