1use crate::error::XmlError;
20use crate::scalar::{attribute_text, element_form, format_ref};
21use crate::{slots, XmlCodec, XmlLayout};
22use ifc_model::{Model, Value};
23use std::fmt::Write as _;
24
25const XSI_NAMESPACE: &str = "http://www.w3.org/2001/XMLSchema-instance";
26
27fn reject_non_finite_reals(model: &Model) -> Result<(), XmlError> {
28 for (id, entity) in model.iter() {
29 for (slot, value) in entity.attributes.iter().enumerate() {
30 if let Some(non_finite) = first_non_finite_real(value) {
31 let path = format!("/ifcXML/{}[@id='i{}']/a{}", entity.type_name, id.0, slot);
32 return Err(XmlError::InvalidScalar {
33 kind: "real".into(),
34 value: non_finite.to_string(),
35 }
36 .at(path));
37 }
38 }
39 }
40 Ok(())
41}
42
43fn first_non_finite_real(value: &Value) -> Option<f64> {
44 match value {
45 Value::Real(value) if !value.is_finite() => Some(*value),
46 Value::List(values) => values.iter().find_map(first_non_finite_real),
47 Value::Typed { value, .. } => first_non_finite_real(value),
48 _ => None,
49 }
50}
51
52pub fn write(codec: &XmlCodec, model: &Model) -> Result<Vec<u8>, XmlError> {
54 if codec.layout() == XmlLayout::Xsd {
55 return write_xsd(codec, model);
56 }
57 reject_non_finite_reals(model)?;
58 #[cfg(feature = "schema")]
59 let mut strict = codec.strict_schema().map(crate::typing::Layouts::new);
60 let mut out = String::with_capacity(model.len() * 96);
61 let schema_token = model.header().schema.first().cloned().unwrap_or_default();
62 let namespace = if let Some(profile) = codec.profile() {
63 if schema_token != profile.schema_token() {
64 return Err(XmlError::Profile {
65 expected: profile.schema_token(),
66 found: (!schema_token.is_empty()).then_some(schema_token),
67 });
68 }
69 profile.namespace()
70 } else {
71 "http://www.buildingsmart-tech.org/ifcXML/IFC4/final"
72 };
73
74 out.push_str("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n");
75 write!(
76 out,
77 "<ifcXML xmlns=\"{namespace}\" xmlns:xsi=\"{XSI_NAMESPACE}\""
78 )
79 .map_err(fmt_err)?;
80 write_attr(&mut out, "schema", &schema_token);
81 out.push_str(">\n");
82
83 write_header(&mut out, model);
84
85 for (id, entity) in model.iter() {
86 let names = slots::attribute_names(codec, &entity.type_name, entity.attributes.len());
87
88 write!(out, " <{}", entity.type_name).map_err(fmt_err)?;
89 write_attr(&mut out, "id", &format_ref(id));
90
91 #[cfg(feature = "schema")]
93 let layout = match strict.as_mut() {
94 Some(layouts) => layouts
97 .entity(&entity.type_name, false)
98 .ok()
99 .flatten()
100 .filter(|layout| layout.slots.len() >= entity.attributes.len()),
101 None => None,
102 };
103 let mut children: Vec<(usize, &Value)> = Vec::new();
104 for (i, value) in entity.attributes.iter().enumerate() {
105 let text = attribute_text(value);
106 #[cfg(feature = "schema")]
109 let text = text.filter(|text| {
110 layout.as_ref().is_none_or(|layout| {
111 let shape = &layout.slots[i].shape;
112 shape.levels.is_empty()
113 && crate::typing::scalar(&shape.leaf, text, crate::typing::Lexical::Native)
114 .is_ok_and(|typed| crate::scalar::same_scalar(&typed, value))
115 })
116 });
117 match text {
118 Some(text) => write_attr(&mut out, &names[i], &text),
119 None => children.push((i, value)),
120 }
121 }
122
123 if children.is_empty() {
124 out.push_str("/>\n");
125 } else {
126 out.push_str(">\n");
127 for (i, value) in children {
128 write_child(&mut out, &names[i], value, 2)?;
129 }
130 writeln!(out, " </{}>", entity.type_name).map_err(fmt_err)?;
131 }
132 }
133
134 out.push_str("</ifcXML>\n");
135 Ok(out.into_bytes())
136}
137
138#[cfg(feature = "schema")]
140fn write_xsd(codec: &XmlCodec, model: &Model) -> Result<Vec<u8>, XmlError> {
141 match (codec.schema(), codec.profile()) {
142 (Some(schema), Some(profile)) => crate::xsd::write(schema, profile, model),
143 _ => Err(XmlError::Unsupported {
144 construct: "the XSD layout without a schema and release profile".into(),
145 }),
146 }
147}
148
149#[cfg(not(feature = "schema"))]
150fn write_xsd(_: &XmlCodec, _: &Model) -> Result<Vec<u8>, XmlError> {
151 Err(XmlError::Unsupported {
152 construct: "the XSD layout without the `schema` feature".into(),
153 })
154}
155
156fn fmt_err(e: std::fmt::Error) -> XmlError {
157 XmlError::Write(e.to_string())
158}
159
160fn write_header(out: &mut String, model: &Model) {
162 let h = model.header();
163 out.push_str(" <header>\n");
164 push_element(out, "name", &h.name);
165 push_element(out, "time_stamp", &h.time_stamp);
166 push_element(out, "preprocessor_version", &h.preprocessor_version);
167 push_element(out, "originating_system", &h.originating_system);
168 push_element(out, "authorization", &h.authorization);
169 for a in &h.author {
170 push_element(out, "author", a);
171 }
172 for o in &h.organization {
173 push_element(out, "organization", o);
174 }
175 for d in &h.description {
176 push_element(out, "description", d);
177 }
178 out.push_str(" </header>\n");
179}
180
181fn push_element(out: &mut String, tag: &str, text: &str) {
182 if text.is_empty() {
183 return;
184 }
185 out.push_str(" <");
186 out.push_str(tag);
187 out.push('>');
188 escape_into(out, text);
189 out.push_str("</");
190 out.push_str(tag);
191 out.push_str(">\n");
192}
193
194fn write_child(out: &mut String, name: &str, value: &Value, depth: usize) -> Result<(), XmlError> {
196 let pad = " ".repeat(depth);
197 match value {
198 Value::Null => {
199 writeln!(out, "{pad}<{name} xsi:nil=\"true\"/>").map_err(fmt_err)?;
200 }
201 Value::Derived => {
202 writeln!(out, "{pad}<{name} derived=\"true\"/>").map_err(fmt_err)?;
203 }
204 Value::Typed { type_name, value } => {
205 write!(out, "{pad}<{name} kind=\"typed\"").map_err(fmt_err)?;
206 write_attr(out, "type", type_name);
207 out.push_str(">\n");
208 write_child(out, "value", value, depth + 1)?;
209 writeln!(out, "{pad}</{name}>").map_err(fmt_err)?;
210 }
211 Value::List(items) => {
212 writeln!(out, "{pad}<{name} kind=\"list\">").map_err(fmt_err)?;
213 for item in items {
214 write_child(out, "item", item, depth + 1)?;
215 }
216 writeln!(out, "{pad}</{name}>").map_err(fmt_err)?;
217 }
218 leaf => {
219 let (kind, text) = element_form(leaf)
223 .ok_or_else(|| XmlError::Write(format!("no element form for value {leaf:?}")))?;
224 write!(out, "{pad}<{name} kind=\"{kind}\">").map_err(fmt_err)?;
225 escape_into(out, &text);
226 writeln!(out, "</{name}>").map_err(fmt_err)?;
227 }
228 }
229 Ok(())
230}
231
232fn write_attr(out: &mut String, name: &str, value: &str) {
233 out.push(' ');
234 out.push_str(name);
235 out.push_str("=\"");
236 escape_into(out, value);
237 out.push('"');
238}
239
240pub(crate) fn escape_into(out: &mut String, text: &str) {
246 for c in text.chars() {
247 match c {
248 '\t' => out.push_str("	"),
249 '\n' => out.push_str(" "),
250 '\r' => out.push_str(" "),
251 '&' => out.push_str("&"),
252 '<' => out.push_str("<"),
253 '>' => out.push_str(">"),
254 '"' => out.push_str("""),
255 '\'' => out.push_str("'"),
256 c => out.push(c),
257 }
258 }
259}