1use crate::{
4 Context, Fact, InstanceDocument, ItemFact, NamespacePrefix, NamespaceUri, Period, QName,
5 TupleFact, error::Result, instance::Unit,
6};
7use quick_xml::{
8 Writer,
9 events::{BytesDecl, BytesEnd, BytesStart, BytesText, Event},
10};
11use std::{collections::HashMap, io};
12
13pub struct InstanceWriter<W> {
15 writer: Writer<W>,
17 is_xbrl_root: bool,
19}
20
21impl<W: io::Write> InstanceWriter<W> {
22 pub fn new(writer: Writer<W>, is_xbrl_root: bool) -> Self {
23 Self {
24 writer,
25 is_xbrl_root,
26 }
27 }
28
29 pub fn into_inner(self) -> W {
31 self.writer.into_inner()
32 }
33
34 pub fn write(&mut self, instance: &InstanceDocument) -> Result<()> {
39 if self.is_xbrl_root {
41 self.writer
42 .write_event(Event::Decl(BytesDecl::new("1.0", Some("utf-8"), None)))?;
43 }
44
45 let prefix_to_uri = instance.namespaces();
46 let uri_to_prefix: HashMap<NamespaceUri, NamespacePrefix> = prefix_to_uri
47 .iter()
48 .map(|(prefix, uri)| (uri.clone(), prefix.clone()))
49 .collect();
50 let mut namespaces: Vec<_> = prefix_to_uri.iter().collect();
51 namespaces.sort_by_key(|(prefix, _)| *prefix);
52
53 let mut root = BytesStart::new("xbrli:xbrl");
55
56 for (prefix, uri) in &namespaces {
57 let attr_name = format!("xmlns:{prefix}");
58 root.push_attribute((attr_name.as_str(), uri.as_str()));
59 }
60
61 self.writer.write_event(Event::Start(root))?;
62
63 for href in instance.schema_refs() {
65 let mut elem = BytesStart::new("link:schemaRef");
66 elem.push_attribute(("xlink:type", "simple"));
67 elem.push_attribute(("xlink:href", href.as_str()));
68 self.writer.write_event(Event::Empty(elem))?;
69 }
70
71 for role_uri in instance.role_refs() {
73 let mut elem = BytesStart::new("link:roleRef");
74 elem.push_attribute(("roleURI", role_uri.as_str()));
75 elem.push_attribute(("xlink:type", "simple"));
76 elem.push_attribute(("xlink:href", ""));
77 self.writer.write_event(Event::Empty(elem))?;
78 }
79
80 for arcrole_uri in instance.arcrole_refs() {
82 let mut elem = BytesStart::new("link:arcroleRef");
83 elem.push_attribute(("arcroleURI", arcrole_uri.as_str()));
84 elem.push_attribute(("xlink:type", "simple"));
85 elem.push_attribute(("xlink:href", ""));
86 self.writer.write_event(Event::Empty(elem))?;
87 }
88
89 let mut ctx_sorted: Vec<_> = instance.contexts().iter().collect();
91 ctx_sorted.sort_by_key(|(id, _)| *id);
92 for (_, context) in &ctx_sorted {
93 write_context(&mut self.writer, context, &uri_to_prefix)?;
94 }
95
96 let mut unit_sorted: Vec<_> = instance.units().iter().collect();
98 unit_sorted.sort_by_key(|(id, _)| *id);
99 for (_, unit) in &unit_sorted {
100 write_unit(&mut self.writer, unit, &uri_to_prefix)?;
101 }
102
103 for fact in instance.facts() {
104 write_fact(&mut self.writer, fact, &uri_to_prefix)?;
105 }
106
107 self.writer
109 .write_event(Event::End(BytesEnd::new("xbrli:xbrl")))?;
110
111 Ok(())
112 }
113}
114
115fn write_context<W: std::io::Write>(
116 writer: &mut Writer<W>,
117 context: &Context,
118 uri_to_prefix: &HashMap<NamespaceUri, NamespacePrefix>,
119) -> Result<()> {
120 let mut element = BytesStart::new("xbrli:context");
121 element.push_attribute(("id", context.id.as_str()));
122 writer.write_event(Event::Start(element))?;
123
124 writer.write_event(Event::Start(BytesStart::new("xbrli:entity")))?;
126 let mut identifier = BytesStart::new("xbrli:identifier");
127 identifier.push_attribute(("scheme", context.entity.scheme.as_str()));
128 writer.write_event(Event::Start(identifier))?;
129 writer.write_event(Event::Text(BytesText::new(&context.entity.value)))?;
130 writer.write_event(Event::End(BytesEnd::new("xbrli:identifier")))?;
131 writer.write_event(Event::End(BytesEnd::new("xbrli:entity")))?;
132
133 writer.write_event(Event::Start(BytesStart::new("xbrli:period")))?;
135 match &context.period {
136 Period::Instant { date } => {
137 writer.write_event(Event::Start(BytesStart::new("xbrli:instant")))?;
138 writer.write_event(Event::Text(BytesText::new(date)))?;
139 writer.write_event(Event::End(BytesEnd::new("xbrli:instant")))?;
140 }
141 Period::Duration { start, end } => {
142 writer.write_event(Event::Start(BytesStart::new("xbrli:startDate")))?;
143 writer.write_event(Event::Text(BytesText::new(start)))?;
144 writer.write_event(Event::End(BytesEnd::new("xbrli:startDate")))?;
145 writer.write_event(Event::Start(BytesStart::new("xbrli:endDate")))?;
146 writer.write_event(Event::Text(BytesText::new(end)))?;
147 writer.write_event(Event::End(BytesEnd::new("xbrli:endDate")))?;
148 }
149 Period::Forever => {
150 writer.write_event(Event::Empty(BytesStart::new("xbrli:forever")))?;
151 }
152 }
153 writer.write_event(Event::End(BytesEnd::new("xbrli:period")))?;
154
155 if !context.dimensions.is_empty() {
157 writer.write_event(Event::Start(BytesStart::new("xbrli:scenario")))?;
158 let mut dim_sorted: Vec<_> = context.dimensions.iter().collect();
159 dim_sorted.sort_by_key(|(dim, _)| *dim);
160 for (dimension, member) in &dim_sorted {
161 let mut explicit = BytesStart::new("xbrldi:explicitMember");
162 let dimension = QName {
163 prefix: uri_to_prefix.get(&dimension.namespace_uri).cloned(),
164 local_name: dimension.local_name.clone(),
165 }
166 .to_string();
167 let member = QName {
168 prefix: uri_to_prefix.get(&member.namespace_uri).cloned(),
169 local_name: member.local_name.clone(),
170 }
171 .to_string();
172
173 explicit.push_attribute(("dimension", dimension.as_str()));
174
175 if member.is_empty() {
176 writer.write_event(Event::Empty(explicit))?;
177 } else {
178 writer.write_event(Event::Start(explicit))?;
179 writer.write_event(Event::Text(BytesText::new(&member)))?;
180 writer.write_event(Event::End(BytesEnd::new("xbrldi:explicitMember")))?;
181 }
182 }
183 writer.write_event(Event::End(BytesEnd::new("xbrli:scenario")))?;
184 }
185
186 writer.write_event(Event::End(BytesEnd::new("xbrli:context")))?;
187
188 Ok(())
189}
190
191fn write_unit<W: io::Write>(
192 writer: &mut Writer<W>,
193 unit: &Unit,
194 uri_to_prefix: &HashMap<NamespaceUri, NamespacePrefix>,
195) -> Result<()> {
196 let mut elem = BytesStart::new("xbrli:unit");
197 elem.push_attribute(("id", unit.id.as_str()));
198 writer.write_event(Event::Start(elem))?;
199
200 if unit.has_denominator() {
201 writer.write_event(Event::Start(BytesStart::new("xbrli:divide")))?;
202 writer.write_event(Event::Start(BytesStart::new("xbrli:numerator")))?;
203
204 for measure in &unit.numerator {
205 let qname = QName {
206 prefix: uri_to_prefix.get(&measure.namespace_uri).cloned(),
207 local_name: measure.local_name.clone(),
208 };
209 writer.write_event(Event::Start(BytesStart::new("xbrli:measure")))?;
210 writer.write_event(Event::Text(BytesText::new(&qname.to_string())))?;
211 writer.write_event(Event::End(BytesEnd::new("xbrli:measure")))?;
212 }
213
214 writer.write_event(Event::End(BytesEnd::new("xbrli:numerator")))?;
215 writer.write_event(Event::Start(BytesStart::new("xbrli:denominator")))?;
216
217 for measure in &unit.denominator {
218 let qname = QName {
219 prefix: uri_to_prefix.get(&measure.namespace_uri).cloned(),
220 local_name: measure.local_name.clone(),
221 };
222 writer.write_event(Event::Start(BytesStart::new("xbrli:measure")))?;
223 writer.write_event(Event::Text(BytesText::new(&qname.to_string())))?;
224 writer.write_event(Event::End(BytesEnd::new("xbrli:measure")))?;
225 }
226
227 writer.write_event(Event::End(BytesEnd::new("xbrli:denominator")))?;
228 writer.write_event(Event::End(BytesEnd::new("xbrli:divide")))?;
229 } else {
230 for measure in &unit.numerator {
231 let qname = QName {
232 prefix: uri_to_prefix.get(&measure.namespace_uri).cloned(),
233 local_name: measure.local_name.clone(),
234 };
235 writer.write_event(Event::Start(BytesStart::new("xbrli:measure")))?;
236 writer.write_event(Event::Text(BytesText::new(&qname.to_string())))?;
237 writer.write_event(Event::End(BytesEnd::new("xbrli:measure")))?;
238 }
239 }
240
241 writer.write_event(Event::End(BytesEnd::new("xbrli:unit")))?;
242
243 Ok(())
244}
245
246fn write_fact<W: io::Write>(
247 writer: &mut Writer<W>,
248 fact: &Fact,
249 namespaces: &HashMap<NamespaceUri, NamespacePrefix>,
250) -> Result<()> {
251 match fact {
252 Fact::Item(item) => write_item_fact(writer, item, namespaces),
253 Fact::Tuple(tuple) => write_tuple_fact(writer, tuple, namespaces),
254 }
255}
256
257fn write_tuple_fact<W: io::Write>(
258 writer: &mut Writer<W>,
259 fact: &TupleFact,
260 namespaces: &HashMap<NamespaceUri, NamespacePrefix>,
261) -> Result<()> {
262 let concept_name = fact.concept_name();
263 let prefix = namespaces.get(&concept_name.namespace_uri);
264 let local_name = &concept_name.local_name;
265 let concept_name = QName {
266 prefix: prefix.cloned(),
267 local_name: local_name.clone(),
268 }
269 .to_string();
270 let mut element = BytesStart::new(&concept_name);
271
272 if let Some(id) = fact.id() {
273 element.push_attribute(("id", id));
274 }
275
276 if fact.is_nil() {
277 element.push_attribute(("xsi:nil", "true"));
278 writer.write_event(Event::Empty(element))?;
279 return Ok(());
280 }
281
282 if fact.children().is_empty() {
283 writer.write_event(Event::Empty(element))?;
284 return Ok(());
285 }
286
287 writer.write_event(Event::Start(element))?;
288 for child in fact.children() {
289 write_fact(writer, child, namespaces)?;
290 }
291 writer.write_event(Event::End(BytesEnd::new(concept_name)))?;
292 Ok(())
293}
294
295fn write_item_fact<W: std::io::Write>(
296 writer: &mut Writer<W>,
297 fact: &ItemFact,
298 namespaces: &HashMap<NamespaceUri, NamespacePrefix>,
299) -> Result<()> {
300 let concept_name = fact.concept_name();
301 let prefix = namespaces.get(&concept_name.namespace_uri);
302 let local_name = &concept_name.local_name;
303 let concept_name = QName {
304 prefix: prefix.cloned(),
305 local_name: local_name.clone(),
306 }
307 .to_string();
308 let mut element = BytesStart::new(&concept_name);
309
310 if let Some(id) = fact.id() {
311 element.push_attribute(("id", id));
312 }
313
314 element.push_attribute(("contextRef", fact.context_ref()));
315
316 if let Some(unit_ref) = fact.unit_ref() {
317 element.push_attribute(("unitRef", unit_ref));
318 }
319
320 if !fact.is_nil() {
321 if let Some(decimals) = fact.decimals() {
322 element.push_attribute(("decimals", decimals.to_string().as_str()));
323 }
324
325 if let Some(precision) = fact.precision() {
326 element.push_attribute(("precision", precision.to_string().as_str()));
327 }
328 }
329
330 if fact.is_nil() {
331 element.push_attribute(("xsi:nil", "true"));
332 writer.write_event(Event::Empty(element))?;
333 } else {
334 writer.write_event(Event::Start(element))?;
335 writer.write_event(Event::Text(BytesText::new(fact.value())))?;
336 writer.write_event(Event::End(BytesEnd::new(&concept_name)))?;
337 }
338
339 Ok(())
340}
341
342#[cfg(test)]
343mod tests {
344 use super::*;
345 use quick_xml::Writer;
346
347 fn write_instance(instance: &InstanceDocument, is_xbrl_root: bool) -> String {
348 let mut output = Vec::new();
349 let mut writer = InstanceWriter::new(Writer::new(&mut output), is_xbrl_root);
350 writer.write(instance).unwrap();
351 String::from_utf8(output).unwrap()
352 }
353
354 #[test]
355 fn test_write_instance_xbrl_root() {
356 let instance = InstanceDocument::default();
357 let output = write_instance(&instance, true);
358 assert!(output.starts_with("<?xml version=\"1.0\" encoding=\"utf-8\"?>"));
359 }
360
361 #[test]
362 fn test_write_instance_non_xbrl_root() {
363 let instance = InstanceDocument::default();
364 let output = write_instance(&instance, false);
365 assert!(!output.starts_with("<?xml version=\"1.0\" encoding=\"utf-8\"?>"));
366 assert!(output.starts_with("<xbrli:xbrl"));
367 }
368}