1use crate::error::XmlError;
16use crate::scalar::{attribute_text, element_form, format_ref};
17use crate::{slots, XmlCodec};
18use ifc_model::{Model, Value};
19use std::fmt::Write as _;
20
21const XSI_NAMESPACE: &str = "http://www.w3.org/2001/XMLSchema-instance";
22
23fn reject_non_finite_reals(model: &Model) -> Result<(), XmlError> {
24 for (id, entity) in model.iter() {
25 for (slot, value) in entity.attributes.iter().enumerate() {
26 if let Some(non_finite) = first_non_finite_real(value) {
27 let path = format!("/ifcXML/{}[@id='i{}']/a{}", entity.type_name, id.0, slot);
28 return Err(XmlError::InvalidScalar {
29 kind: "real".into(),
30 value: non_finite.to_string(),
31 }
32 .at(path));
33 }
34 }
35 }
36 Ok(())
37}
38
39fn first_non_finite_real(value: &Value) -> Option<f64> {
40 match value {
41 Value::Real(value) if !value.is_finite() => Some(*value),
42 Value::List(values) => values.iter().find_map(first_non_finite_real),
43 Value::Typed { value, .. } => first_non_finite_real(value),
44 _ => None,
45 }
46}
47
48pub fn write(codec: &XmlCodec, model: &Model) -> Result<Vec<u8>, XmlError> {
50 reject_non_finite_reals(model)?;
51 let mut out = String::with_capacity(model.len() * 96);
52 let schema_token = model.header().schema.first().cloned().unwrap_or_default();
53 let namespace = if let Some(profile) = codec.profile() {
54 if schema_token != profile.schema_token() {
55 return Err(XmlError::Profile {
56 expected: profile.schema_token(),
57 found: (!schema_token.is_empty()).then_some(schema_token),
58 });
59 }
60 profile.namespace()
61 } else {
62 "http://www.buildingsmart-tech.org/ifcXML/IFC4/final"
63 };
64
65 out.push_str("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n");
66 write!(
67 out,
68 "<ifcXML xmlns=\"{namespace}\" xmlns:xsi=\"{XSI_NAMESPACE}\""
69 )
70 .map_err(fmt_err)?;
71 write_attr(&mut out, "schema", &schema_token);
72 out.push_str(">\n");
73
74 write_header(&mut out, model);
75
76 for (id, entity) in model.iter() {
77 let names = slots::attribute_names(codec, &entity.type_name, entity.attributes.len());
78
79 write!(out, " <{}", entity.type_name).map_err(fmt_err)?;
80 write_attr(&mut out, "id", &format_ref(id));
81
82 let mut children: Vec<(usize, &Value)> = Vec::new();
84 for (i, value) in entity.attributes.iter().enumerate() {
85 match attribute_text(value) {
86 Some(text) => write_attr(&mut out, &names[i], &text),
87 None => children.push((i, value)),
88 }
89 }
90
91 if children.is_empty() {
92 out.push_str("/>\n");
93 } else {
94 out.push_str(">\n");
95 for (i, value) in children {
96 write_child(&mut out, &names[i], value, 2)?;
97 }
98 writeln!(out, " </{}>", entity.type_name).map_err(fmt_err)?;
99 }
100 }
101
102 out.push_str("</ifcXML>\n");
103 Ok(out.into_bytes())
104}
105
106fn fmt_err(e: std::fmt::Error) -> XmlError {
107 XmlError::Write(e.to_string())
108}
109
110fn write_header(out: &mut String, model: &Model) {
112 let h = model.header();
113 out.push_str(" <header>\n");
114 push_element(out, "name", &h.name);
115 push_element(out, "time_stamp", &h.time_stamp);
116 push_element(out, "preprocessor_version", &h.preprocessor_version);
117 push_element(out, "originating_system", &h.originating_system);
118 push_element(out, "authorization", &h.authorization);
119 for a in &h.author {
120 push_element(out, "author", a);
121 }
122 for o in &h.organization {
123 push_element(out, "organization", o);
124 }
125 for d in &h.description {
126 push_element(out, "description", d);
127 }
128 out.push_str(" </header>\n");
129}
130
131fn push_element(out: &mut String, tag: &str, text: &str) {
132 if text.is_empty() {
133 return;
134 }
135 out.push_str(" <");
136 out.push_str(tag);
137 out.push('>');
138 escape_into(out, text);
139 out.push_str("</");
140 out.push_str(tag);
141 out.push_str(">\n");
142}
143
144fn write_child(out: &mut String, name: &str, value: &Value, depth: usize) -> Result<(), XmlError> {
146 let pad = " ".repeat(depth);
147 match value {
148 Value::Null => {
149 writeln!(out, "{pad}<{name} xsi:nil=\"true\"/>").map_err(fmt_err)?;
150 }
151 Value::Derived => {
152 writeln!(out, "{pad}<{name} derived=\"true\"/>").map_err(fmt_err)?;
153 }
154 Value::Typed { type_name, value } => {
155 write!(out, "{pad}<{name} kind=\"typed\"").map_err(fmt_err)?;
156 write_attr(out, "type", type_name);
157 out.push_str(">\n");
158 write_child(out, "value", value, depth + 1)?;
159 writeln!(out, "{pad}</{name}>").map_err(fmt_err)?;
160 }
161 Value::List(items) => {
162 writeln!(out, "{pad}<{name} kind=\"list\">").map_err(fmt_err)?;
163 for item in items {
164 write_child(out, "item", item, depth + 1)?;
165 }
166 writeln!(out, "{pad}</{name}>").map_err(fmt_err)?;
167 }
168 leaf => {
169 let (kind, text) = element_form(leaf)
173 .ok_or_else(|| XmlError::Write(format!("no element form for value {leaf:?}")))?;
174 write!(out, "{pad}<{name} kind=\"{kind}\">").map_err(fmt_err)?;
175 escape_into(out, &text);
176 writeln!(out, "</{name}>").map_err(fmt_err)?;
177 }
178 }
179 Ok(())
180}
181
182fn write_attr(out: &mut String, name: &str, value: &str) {
183 out.push(' ');
184 out.push_str(name);
185 out.push_str("=\"");
186 escape_into(out, value);
187 out.push('"');
188}
189
190fn escape_into(out: &mut String, text: &str) {
196 for c in text.chars() {
197 match c {
198 '\t' => out.push_str("	"),
199 '\n' => out.push_str(" "),
200 '\r' => out.push_str(" "),
201 '&' => out.push_str("&"),
202 '<' => out.push_str("<"),
203 '>' => out.push_str(">"),
204 '"' => out.push_str("""),
205 '\'' => out.push_str("'"),
206 c => out.push(c),
207 }
208 }
209}