1use crate::{
2 NamespacePrefix, NamespaceUri, QName, XbrlError,
3 xml::{self, ArcroleRef, RoleRef, SchemaRef, parse_qname},
4};
5use quick_xml::{
6 Reader,
7 events::{BytesStart, Event, attributes::Attributes},
8};
9use std::{
10 collections::HashMap,
11 fs::File,
12 io::{BufRead, BufReader},
13 path::{Path, PathBuf},
14};
15
16#[derive(Debug, PartialEq, Eq)]
18pub struct RawContext {
19 pub id: String,
21 pub entity: RawEntity,
23 pub period: RawPeriod,
25 pub scenario_dimensions: Vec<RawDimension>,
27}
28
29#[derive(Debug, PartialEq, Eq)]
31pub struct RawEntity {
32 pub identifier: String,
34 pub scheme: String,
38 pub segment_dimensions: Vec<RawDimension>,
40}
41
42#[derive(Debug, PartialEq, Eq)]
44pub enum RawPeriod {
45 Instant(String),
46 Duration {
47 start_date: String,
48 end_date: String,
49 },
50 Forever,
51}
52
53#[derive(Debug, PartialEq, Eq)]
55pub struct RawDimension {
56 pub dimension: QName,
58 pub member: QName,
60}
61
62#[derive(Debug, PartialEq, Eq)]
63pub struct RawUnit {
64 pub id: String,
66 pub numerator: Vec<QName>,
69 pub denominator: Vec<QName>,
72}
73
74#[derive(Debug, PartialEq, Eq)]
76pub enum RawFact {
77 Item(RawItemFact),
78 Tuple(RawTupleFact),
79}
80
81#[derive(Debug, PartialEq, Eq)]
82pub struct RawItemFact {
83 pub name: QName,
85 pub value: String,
87 pub context_ref: String,
89 pub unit_ref: Option<String>,
91 pub decimals: Option<String>,
93 pub precision: Option<String>,
95 pub id: Option<String>,
97 pub is_nil: bool,
99}
100
101#[derive(Debug, PartialEq, Eq)]
102pub struct RawTupleFact {
103 pub name: QName,
105 pub id: Option<String>,
107 pub is_nil: bool,
109 pub children: Vec<RawFact>,
111}
112
113#[derive(Debug, PartialEq, Eq)]
115pub struct Locator {
116 pub label: String,
118 pub href: String,
120}
121
122#[derive(Debug, PartialEq, Eq)]
123pub struct RawFootnoteLink {
124 pub role: String,
125 pub locators: Vec<Locator>,
126 pub arcs: Vec<FootnoteArc>,
127 pub footnotes: Vec<FootnoteResource>,
128}
129
130#[derive(Debug, PartialEq, Eq)]
131pub struct FootnoteArc {
132 pub from: String,
133 pub to: String,
134}
135
136#[derive(Debug, PartialEq, Eq)]
137pub struct FootnoteResource {
138 pub label: String,
139 pub lang: Option<String>,
140 pub text: String,
141}
142
143#[derive(Debug, PartialEq, Eq, Default)]
144pub struct RawInstance {
145 pub namespaces: HashMap<NamespacePrefix, NamespaceUri>,
147 pub schema_refs: Vec<SchemaRef>,
149 pub role_refs: Vec<RoleRef>,
154 pub arcrole_refs: Vec<ArcroleRef>,
159 pub contexts: Vec<RawContext>,
161 pub units: Vec<RawUnit>,
163 pub facts: Vec<RawFact>,
165 pub footnote_links: Vec<RawFootnoteLink>,
167}
168
169impl RawInstance {
170 #[allow(clippy::too_many_arguments)]
171 pub fn new(
172 namespaces: HashMap<NamespacePrefix, NamespaceUri>,
173 schema_refs: Vec<SchemaRef>,
174 role_refs: Vec<RoleRef>,
175 arcrole_refs: Vec<ArcroleRef>,
176 contexts: Vec<RawContext>,
177 units: Vec<RawUnit>,
178 facts: Vec<RawFact>,
179 footnote_links: Vec<RawFootnoteLink>,
180 ) -> Self {
181 Self {
182 namespaces,
183 schema_refs,
184 role_refs,
185 arcrole_refs,
186 contexts,
187 units,
188 facts,
189 footnote_links,
190 }
191 }
192}
193
194pub struct InstanceParser<R> {
196 path: Option<PathBuf>,
199 reader: Reader<R>,
201}
202
203impl InstanceParser<BufReader<File>> {
204 pub fn from_file(path: &Path) -> Result<Self, XbrlError> {
206 let file = File::open(path).map_err(|err| XbrlError::FileOpen {
207 path: path.to_path_buf(),
208 context: "opening file".to_string(),
209 source: err,
210 })?;
211 let reader = Reader::from_reader(BufReader::new(file));
212
213 Ok(Self {
214 path: Some(path.to_path_buf()),
215 reader,
216 })
217 }
218}
219
220impl<R: BufRead> InstanceParser<R> {
221 pub fn new(reader: Reader<R>) -> Self {
223 Self { path: None, reader }
224 }
225
226 pub fn from_reader(reader: R) -> Self {
228 let mut reader = Reader::from_reader(reader);
229 reader.config_mut().trim_text_start = true;
230 reader.config_mut().trim_text_end = true;
231 Self::new(reader)
232 }
233
234 pub fn parse_instance(&mut self) -> Result<RawInstance, XbrlError> {
237 let mut instance = RawInstance::default();
238 let mut has_instance_root = false;
239 let mut buf = Vec::new();
240
241 loop {
242 match self.reader.read_event_into(&mut buf) {
243 Ok(Event::Start(ref event)) => {
244 let event_name = event.name();
245 let local_name = event_name.local_name();
246 let attributes = event.attributes();
247
248 match local_name.as_ref() {
249 b"xbrl" => {
250 has_instance_root = true;
251 self.parse_instance_root(&mut instance, attributes)?;
252 }
253 b"schemaRef" => self.parse_schema_ref(&mut instance, attributes)?,
254 b"roleRef" => self.parse_role_ref(&mut instance, attributes)?,
255 b"arcroleRef" => self.parse_arcrole_ref(&mut instance, attributes)?,
256 b"context" => self.parse_context(&mut instance, attributes)?,
257 b"unit" => self.parse_unit(&mut instance, attributes)?,
258 b"footnoteLink" => self.parse_footnote_link(&mut instance, attributes)?,
259 _ if Self::is_fact_element(local_name.as_ref()) => {
260 self.parse_fact(&mut instance, event)?;
261 }
262 _ => {}
263 }
264 }
265 Ok(Event::Empty(ref event)) => {
266 let local_name = event.name().local_name();
267 let attributes = event.attributes();
268
269 match local_name.as_ref() {
270 b"xbrl" => {
271 has_instance_root = true;
272 self.parse_instance_root(&mut instance, attributes)?;
273 }
274 b"schemaRef" => self.parse_schema_ref(&mut instance, attributes)?,
275 b"roleRef" => self.parse_role_ref(&mut instance, attributes)?,
276 b"arcroleRef" => self.parse_arcrole_ref(&mut instance, attributes)?,
277 _ if Self::is_fact_element(local_name.as_ref()) => {
278 let fact = self.parse_empty_fact(event)?;
279 instance.facts.push(fact);
280 }
281 _ => {}
282 }
283 }
284 Ok(Event::End(ref event)) if event.name().local_name().as_ref() == b"xbrl" => {
285 break;
286 }
287 Ok(Event::End(_)) => {}
288 Ok(Event::Text(_)) => {}
289 Ok(Event::Eof) => break,
290 Err(err) => {
291 return Err(XbrlError::XmlParse {
292 path: self.path.clone(),
293 position: self.reader.buffer_position(),
294 element: Some("schema".to_string()),
295 source: err,
296 });
297 }
298 _ => {}
299 }
300 }
301
302 if !has_instance_root {
303 return Err(XbrlError::InvalidInstanceDocument {
304 path: self.path.clone(),
305 reason: "missing <xbrli:xbrl> root element".to_string(),
306 });
307 }
308
309 Ok(instance)
310 }
311
312 fn parse_instance_root(
314 &mut self,
315 instance: &mut RawInstance,
316 attributes: Attributes,
317 ) -> Result<(), XbrlError> {
318 for attribute in attributes {
319 let attribute = attribute.map_err(|err| XbrlError::XmlParse {
320 path: self.path.clone(),
321 position: self.reader.buffer_position(),
322 element: Some("xbrl".to_string()),
323 source: err.into(),
324 })?;
325 let key = attribute.key;
326
327 if let Some(prefix) = key.prefix()
328 && prefix.as_ref() == b"xmlns"
329 {
330 let local = key.local_name();
331 let namespace_prefix = str::from_utf8(local.as_ref())?;
332 let uri = attribute.decode_and_unescape_value(self.reader.decoder())?;
333 instance.namespaces.insert(
334 NamespacePrefix::from(namespace_prefix),
335 NamespaceUri::from(uri.into_owned()),
336 );
337 }
338 }
339
340 Ok(())
341 }
342
343 fn parse_schema_ref(
345 &mut self,
346 instance: &mut RawInstance,
347 attributes: Attributes,
348 ) -> Result<(), XbrlError> {
349 for attribute in attributes {
350 let attribute = attribute.map_err(|err| XbrlError::XmlParse {
351 path: self.path.clone(),
352 position: self.reader.buffer_position(),
353 element: Some("schemaRef".to_string()),
354 source: err.into(),
355 })?;
356
357 if attribute.key.local_name().as_ref() == b"href" {
358 let value = attribute.decode_and_unescape_value(self.reader.decoder())?;
359 instance.schema_refs.push(SchemaRef {
360 href: value.into_owned(),
361 });
362 return Ok(());
363 }
364 }
365
366 Err(XbrlError::InvalidInstanceDocument {
367 path: self.path.clone(),
368 reason: "missing xlink:href in link:schemaRef".to_string(),
369 })
370 }
371
372 fn parse_role_ref(
374 &mut self,
375 instance: &mut RawInstance,
376 attributes: Attributes,
377 ) -> Result<(), XbrlError> {
378 let mut role_uri = None;
379 let mut href = None;
380
381 for attribute in attributes {
382 let attribute = attribute.map_err(|err| XbrlError::XmlParse {
383 path: self.path.clone(),
384 position: self.reader.buffer_position(),
385 element: Some("roleRef".to_string()),
386 source: err.into(),
387 })?;
388 let local_name = attribute.key.local_name();
389 let value = attribute.decode_and_unescape_value(self.reader.decoder())?;
390
391 match local_name.as_ref() {
392 b"roleURI" => role_uri = Some(value.into_owned()),
393 b"href" => href = Some(value.into_owned()),
394 _ => {}
395 }
396 }
397
398 instance.role_refs.push(RoleRef {
399 role_uri: role_uri.ok_or_else(|| XbrlError::InvalidInstanceDocument {
400 path: self.path.clone(),
401 reason: "missing roleURI in link:roleRef".to_string(),
402 })?,
403 href: href.ok_or_else(|| XbrlError::InvalidInstanceDocument {
404 path: self.path.clone(),
405 reason: "missing xlink:href in link:roleRef".to_string(),
406 })?,
407 });
408
409 Ok(())
410 }
411
412 fn parse_arcrole_ref(
414 &mut self,
415 instance: &mut RawInstance,
416 attributes: Attributes,
417 ) -> Result<(), XbrlError> {
418 let mut arcrole_uri = None;
419 let mut href = None;
420
421 for attribute in attributes {
422 let attribute = attribute.map_err(|err| XbrlError::XmlParse {
423 path: self.path.clone(),
424 position: self.reader.buffer_position(),
425 element: Some("arcroleRef".to_string()),
426 source: err.into(),
427 })?;
428 let local_name = attribute.key.local_name();
429 let value = attribute.decode_and_unescape_value(self.reader.decoder())?;
430
431 match local_name.as_ref() {
432 b"arcroleURI" => arcrole_uri = Some(value.into_owned()),
433 b"href" => href = Some(value.into_owned()),
434 _ => {}
435 }
436 }
437
438 instance.arcrole_refs.push(ArcroleRef {
439 arcrole_uri: arcrole_uri.ok_or_else(|| XbrlError::InvalidInstanceDocument {
440 path: self.path.clone(),
441 reason: "missing arcroleURI in link:arcroleRef".to_string(),
442 })?,
443 href: href.ok_or_else(|| XbrlError::InvalidInstanceDocument {
444 path: self.path.clone(),
445 reason: "missing xlink:href in link:arcroleRef".to_string(),
446 })?,
447 });
448
449 Ok(())
450 }
451
452 fn parse_context(
458 &mut self,
459 instance: &mut RawInstance,
460 attributes: Attributes,
461 ) -> Result<(), XbrlError> {
462 let mut id = None;
463
464 for attribute in attributes {
465 let attribute = attribute.map_err(|err| XbrlError::XmlParse {
466 path: self.path.clone(),
467 position: self.reader.buffer_position(),
468 element: Some("context".to_string()),
469 source: err.into(),
470 })?;
471
472 if attribute.key.local_name().as_ref() == b"id" {
473 let value = attribute.decode_and_unescape_value(self.reader.decoder())?;
474 id = Some(value.into_owned());
475 }
476 }
477
478 let id = id.ok_or_else(|| XbrlError::InvalidInstanceDocument {
479 path: self.path.clone(),
480 reason: "missing id in xbrli:context".to_string(),
481 })?;
482
483 let mut entity = None;
484 let mut period = None;
485 let mut scenario_dimensions = Vec::new();
486 let mut buf = Vec::new();
487
488 loop {
489 match self.reader.read_event_into(&mut buf)? {
490 Event::Start(ref event) => match event.local_name().as_ref() {
491 b"entity" => {
492 entity = Some(self.parse_entity()?);
493 }
494 b"period" => {
495 period = Some(self.parse_period()?);
496 }
497 b"scenario" => {
498 self.parse_dimensional_container(&mut scenario_dimensions)?;
499 }
500 _ => {}
501 },
502 Event::End(ref event) if event.local_name().as_ref() == b"context" => break,
503 Event::Eof => break,
504 _ => {}
505 }
506 buf.clear();
507 }
508
509 instance.contexts.push(RawContext {
510 id,
511 entity: entity.ok_or_else(|| XbrlError::InvalidInstanceDocument {
512 path: self.path.clone(),
513 reason: "missing entity in xbrli:context".to_string(),
514 })?,
515 period: period.ok_or_else(|| XbrlError::InvalidInstanceDocument {
516 path: self.path.clone(),
517 reason: "missing period in xbrli:context".to_string(),
518 })?,
519 scenario_dimensions,
520 });
521
522 Ok(())
523 }
524
525 fn parse_entity(&mut self) -> Result<RawEntity, XbrlError> {
528 let mut identifier = None;
529 let mut scheme = None;
530 let mut segment_dimensions = Vec::new();
531 let mut buf = Vec::new();
532
533 loop {
534 match self.reader.read_event_into(&mut buf)? {
535 Event::Start(ref event) => match event.local_name().as_ref() {
536 b"identifier" => {
537 for attribute in event.attributes() {
538 let attribute = attribute.map_err(|err| XbrlError::XmlParse {
539 path: self.path.clone(),
540 position: self.reader.buffer_position(),
541 element: Some("identifier".to_string()),
542 source: err.into(),
543 })?;
544
545 if attribute.key.local_name().as_ref() == b"scheme" {
546 let value =
547 attribute.decode_and_unescape_value(self.reader.decoder())?;
548 scheme = Some(value.into_owned());
549 }
550 }
551 }
552 b"segment" => {
553 self.parse_dimensional_container(&mut segment_dimensions)?;
554 }
555 _ => {}
556 },
557 Event::Text(ref text) => {
558 if identifier.is_none() {
559 let value = text.xml_content().map_err(quick_xml::Error::from)?;
560 identifier = Some(value.into_owned());
561 }
562 }
563 Event::End(ref event) if event.local_name().as_ref() == b"entity" => break,
564 Event::Eof => break,
565 _ => {}
566 }
567 buf.clear();
568 }
569
570 Ok(RawEntity {
571 identifier: identifier.ok_or_else(|| XbrlError::InvalidInstanceDocument {
572 path: self.path.clone(),
573 reason: "missing identifier in xbrli:entity".to_string(),
574 })?,
575 scheme: scheme.ok_or_else(|| XbrlError::InvalidInstanceDocument {
576 path: self.path.clone(),
577 reason: "missing scheme in xbrli:identifier".to_string(),
578 })?,
579 segment_dimensions,
580 })
581 }
582
583 fn parse_period(&mut self) -> Result<RawPeriod, XbrlError> {
585 let mut instant = None;
586 let mut start_date = None;
587 let mut end_date = None;
588 let mut is_forever = false;
589 let mut current_tag: Option<String> = None;
590 let mut buf = Vec::new();
591
592 loop {
593 match self.reader.read_event_into(&mut buf)? {
594 Event::Start(ref event) | Event::Empty(ref event) => {
595 match event.local_name().as_ref() {
596 b"instant" => current_tag = Some("instant".to_string()),
597 b"startDate" => current_tag = Some("startDate".to_string()),
598 b"endDate" => current_tag = Some("endDate".to_string()),
599 b"forever" => is_forever = true,
600 _ => {}
601 }
602 }
603 Event::Text(ref text) => {
604 let value = text
605 .xml_content()
606 .map_err(quick_xml::Error::from)?
607 .into_owned();
608 match current_tag.as_deref() {
609 Some("instant") => instant = Some(value),
610 Some("startDate") => start_date = Some(value),
611 Some("endDate") => end_date = Some(value),
612 _ => {}
613 }
614 }
615 Event::End(ref event) => match event.local_name().as_ref() {
616 b"period" => break,
617 _ => current_tag = None,
618 },
619 Event::Eof => break,
620 _ => {}
621 }
622 buf.clear();
623 }
624
625 if is_forever {
626 Ok(RawPeriod::Forever)
627 } else if let Some(instant) = instant {
628 Ok(RawPeriod::Instant(instant))
629 } else if let (Some(start_date), Some(end_date)) = (start_date, end_date) {
630 Ok(RawPeriod::Duration {
631 start_date,
632 end_date,
633 })
634 } else {
635 Err(XbrlError::InvalidInstanceDocument {
636 path: self.path.clone(),
637 reason: "invalid period in xbrli:context".to_string(),
638 })
639 }
640 }
641
642 fn parse_dimensional_container(
644 &mut self,
645 dimensions: &mut Vec<RawDimension>,
646 ) -> Result<(), XbrlError> {
647 let mut buf = Vec::new();
648
649 loop {
650 match self.reader.read_event_into(&mut buf)? {
651 Event::Start(ref event) | Event::Empty(ref event) => {
652 if event.local_name().as_ref() == b"explicitMember" {
653 let mut dimension = None;
654
655 for attribute in event.attributes() {
656 let attribute = attribute.map_err(|err| XbrlError::XmlParse {
657 path: self.path.clone(),
658 position: self.reader.buffer_position(),
659 element: Some("explicitMember".to_string()),
660 source: err.into(),
661 })?;
662
663 if attribute.key.local_name().as_ref() == b"dimension" {
664 let value =
665 attribute.decode_and_unescape_value(self.reader.decoder())?;
666 dimension = Some(parse_qname(&value));
667 }
668 }
669
670 if let Some(dimension) = dimension {
671 let mut member_buf = Vec::new();
672
673 if let Event::Text(ref text) =
674 self.reader.read_event_into(&mut member_buf)?
675 {
676 let member = text
677 .xml_content()
678 .map_err(quick_xml::Error::from)?
679 .into_owned();
680 let member = parse_qname(&member);
681 dimensions.push(RawDimension { dimension, member });
682 }
683 }
684 }
685 }
686 Event::End(ref event)
687 if matches!(event.local_name().as_ref(), b"scenario" | b"segment") =>
688 {
689 break;
690 }
691 Event::Eof => break,
692 _ => {}
693 }
694 buf.clear();
695 }
696
697 Ok(())
698 }
699
700 fn parse_unit(
703 &mut self,
704 instance: &mut RawInstance,
705 attributes: Attributes,
706 ) -> Result<(), XbrlError> {
707 let mut id = None;
708
709 for attribute in attributes {
710 let attribute = attribute.map_err(|err| XbrlError::XmlParse {
711 path: self.path.clone(),
712 position: self.reader.buffer_position(),
713 element: Some("unit".to_string()),
714 source: err.into(),
715 })?;
716
717 if attribute.key.local_name().as_ref() == b"id" {
718 let value = attribute.decode_and_unescape_value(self.reader.decoder())?;
719 id = Some(value.into_owned());
720 }
721 }
722
723 let id = id.ok_or_else(|| XbrlError::InvalidInstanceDocument {
724 path: self.path.clone(),
725 reason: "missing id in xbrli:unit".to_string(),
726 })?;
727 let mut numerator = Vec::new();
728 let mut denominator = Vec::new();
729 let mut buf = Vec::new();
730
731 loop {
732 match self.reader.read_event_into(&mut buf)? {
733 Event::Start(ref event) => match event.local_name().as_ref() {
734 b"measure" => {
735 let mut text_buf = Vec::new();
736 if let Event::Text(ref text) = self.reader.read_event_into(&mut text_buf)? {
737 let value = text.xml_content().map_err(quick_xml::Error::from)?;
738 let qname = xml::parse_qname(&value);
739 numerator.push(qname);
740 }
741 }
742 b"divide" => {
743 self.parse_unit_divide(&mut numerator, &mut denominator)?;
744 }
745 _ => {}
746 },
747 Event::End(ref event) if event.local_name().as_ref() == b"unit" => break,
748 Event::Eof => break,
749 _ => {}
750 }
751 buf.clear();
752 }
753
754 instance.units.push(RawUnit {
755 id,
756 numerator,
757 denominator,
758 });
759
760 Ok(())
761 }
762
763 fn parse_unit_divide(
766 &mut self,
767 numerator: &mut Vec<QName>,
768 denominator: &mut Vec<QName>,
769 ) -> Result<(), XbrlError> {
770 let mut in_numerator = false;
771 let mut in_denominator = false;
772 let mut buf = Vec::new();
773
774 loop {
775 match self.reader.read_event_into(&mut buf)? {
776 Event::Start(ref event) => match event.local_name().as_ref() {
777 b"unitNumerator" => in_numerator = true,
778 b"unitDenominator" => in_denominator = true,
779 b"measure" => {
780 let mut text_buf = Vec::new();
781 if let Event::Text(ref text) = self.reader.read_event_into(&mut text_buf)? {
782 let value = text.xml_content().map_err(quick_xml::Error::from)?;
783 let qname = xml::parse_qname(&value);
784 if in_numerator {
785 numerator.push(qname);
786 } else if in_denominator {
787 denominator.push(qname);
788 }
789 }
790 }
791 _ => {}
792 },
793 Event::End(ref event) => match event.local_name().as_ref() {
794 b"unitNumerator" => in_numerator = false,
795 b"unitDenominator" => in_denominator = false,
796 b"divide" => break,
797 _ => {}
798 },
799 Event::Eof => break,
800 _ => {}
801 }
802 buf.clear();
803 }
804
805 Ok(())
806 }
807
808 fn is_fact_element(local_name: &[u8]) -> bool {
811 !matches!(
812 local_name,
813 b"xbrl"
814 | b"context"
815 | b"unit"
816 | b"schemaRef"
817 | b"roleRef"
818 | b"arcroleRef"
819 | b"identifier"
820 | b"entity"
821 | b"period"
822 | b"instant"
823 | b"startDate"
824 | b"endDate"
825 | b"scenario"
826 | b"segment"
827 | b"explicitMember"
828 | b"measure"
829 | b"footnoteLink"
830 | b"footnote"
831 | b"footnoteArc"
832 | b"loc"
833 | b"forever"
834 | b"unitNumerator"
835 | b"unitDenominator"
836 | b"divide"
837 )
838 }
839
840 fn parse_fact(
845 &mut self,
846 instance: &mut RawInstance,
847 event: &BytesStart,
848 ) -> Result<(), XbrlError> {
849 let fact = self.parse_fact_recursive(event)?;
850
851 if let Some(fact) = fact {
852 instance.facts.push(fact);
853 }
854
855 Ok(())
856 }
857
858 fn parse_fact_recursive(&mut self, event: &BytesStart) -> Result<Option<RawFact>, XbrlError> {
862 let name = parse_qname(std::str::from_utf8(event.name().as_ref())?);
863
864 let mut context_ref = None;
865 let mut unit_ref = None;
866 let mut decimals = None;
867 let mut precision = None;
868 let mut id = None;
869 let mut is_nil = false;
870
871 for attribute in event.attributes() {
872 let attribute = attribute.map_err(|err| XbrlError::XmlParse {
873 path: self.path.clone(),
874 position: self.reader.buffer_position(),
875 element: Some(name.to_string()),
876 source: err.into(),
877 })?;
878 let local_name = attribute.key.local_name();
879 let value = attribute.decode_and_unescape_value(self.reader.decoder())?;
880
881 match local_name.as_ref() {
882 b"contextRef" => context_ref = Some(value.into_owned()),
883 b"unitRef" => unit_ref = Some(value.into_owned()),
884 b"decimals" => decimals = Some(value.into_owned()),
885 b"precision" => precision = Some(value.into_owned()),
886 b"id" => id = Some(value.into_owned()),
887 b"nil" => is_nil = value.as_ref() == "true",
888 _ => {}
889 }
890 }
891
892 if let Some(context_ref) = context_ref {
893 let mut value = String::new();
894 let mut buf = Vec::new();
895
896 loop {
898 match self.reader.read_event_into(&mut buf)? {
899 Event::Text(ref text) => {
900 let decoded = text.xml_content().map_err(quick_xml::Error::from)?;
901 value.push_str(&decoded);
902 }
903 Event::End(ref end) if end.name().as_ref() == event.name().as_ref() => break,
904 Event::Eof => break,
905 _ => {}
906 }
907 buf.clear();
908 }
909
910 Ok(Some(RawFact::Item(RawItemFact {
911 name,
912 value,
913 context_ref,
914 unit_ref,
915 decimals,
916 precision,
917 id,
918 is_nil,
919 })))
920 } else {
921 let mut children = Vec::new();
922 let mut buf = Vec::new();
923
924 loop {
926 match self.reader.read_event_into(&mut buf)? {
927 Event::Start(ref child_event) => {
928 if Self::is_fact_element(child_event.name().local_name().as_ref())
929 && let Some(child) = self.parse_fact_recursive(child_event)?
930 {
931 children.push(child);
932 }
933 }
934 Event::Empty(ref child_event) => {
935 if Self::is_fact_element(child_event.name().local_name().as_ref()) {
936 children.push(self.parse_empty_fact(child_event)?);
937 }
938 }
939 Event::End(ref end) if end.name().as_ref() == event.name().as_ref() => break,
940 Event::Eof => break,
941 _ => {}
942 }
943 buf.clear();
944 }
945
946 Ok(Some(RawFact::Tuple(RawTupleFact {
947 name,
948 id,
949 is_nil,
950 children,
951 })))
952 }
953 }
954
955 fn parse_empty_fact(&mut self, event: &BytesStart) -> Result<RawFact, XbrlError> {
957 let name = parse_qname(std::str::from_utf8(event.name().as_ref())?);
958
959 let mut context_ref = None;
960 let mut unit_ref = None;
961 let mut decimals = None;
962 let mut precision = None;
963 let mut id = None;
964 let mut is_nil = false;
965
966 for attribute in event.attributes() {
967 let attribute = attribute.map_err(|err| XbrlError::XmlParse {
968 path: self.path.clone(),
969 position: self.reader.buffer_position(),
970 element: Some(name.to_string()),
971 source: err.into(),
972 })?;
973 let local_name = attribute.key.local_name();
974 let value = attribute.decode_and_unescape_value(self.reader.decoder())?;
975
976 match local_name.as_ref() {
977 b"contextRef" => context_ref = Some(value.into_owned()),
978 b"unitRef" => unit_ref = Some(value.into_owned()),
979 b"decimals" => decimals = Some(value.into_owned()),
980 b"precision" => precision = Some(value.into_owned()),
981 b"id" => id = Some(value.into_owned()),
982 b"nil" => is_nil = value.as_ref() == "true",
983 _ => {}
984 }
985 }
986
987 if let Some(context_ref) = context_ref {
988 Ok(RawFact::Item(RawItemFact {
989 name,
990 value: String::new(),
991 context_ref,
992 unit_ref,
993 decimals,
994 precision,
995 id,
996 is_nil,
997 }))
998 } else {
999 Ok(RawFact::Tuple(RawTupleFact {
1000 name,
1001 id,
1002 is_nil,
1003 children: Vec::new(),
1004 }))
1005 }
1006 }
1007
1008 fn parse_footnote_link(
1010 &mut self,
1011 instance: &mut RawInstance,
1012 attributes: Attributes,
1013 ) -> Result<(), XbrlError> {
1014 let mut role = String::new();
1015
1016 for attribute in attributes {
1017 let attribute = attribute.map_err(|err| XbrlError::XmlParse {
1018 path: self.path.clone(),
1019 position: self.reader.buffer_position(),
1020 element: Some("footnoteLink".to_string()),
1021 source: err.into(),
1022 })?;
1023
1024 if attribute.key.local_name().as_ref() == b"role" {
1025 let value = attribute.decode_and_unescape_value(self.reader.decoder())?;
1026 role = value.into_owned();
1027 }
1028 }
1029
1030 let mut locators = Vec::new();
1031 let mut arcs = Vec::new();
1032 let mut footnotes = Vec::new();
1033 let mut buf = Vec::new();
1034
1035 loop {
1036 match self.reader.read_event_into(&mut buf)? {
1037 Event::Start(ref event) | Event::Empty(ref event) => {
1038 match event.local_name().as_ref() {
1039 b"loc" => {
1040 let mut label = None;
1041 let mut href = None;
1042
1043 for attribute in event.attributes() {
1044 let attribute = attribute.map_err(|err| XbrlError::XmlParse {
1045 path: self.path.clone(),
1046 position: self.reader.buffer_position(),
1047 element: Some("loc".to_string()),
1048 source: err.into(),
1049 })?;
1050 let local_name = attribute.key.local_name();
1051 let value =
1052 attribute.decode_and_unescape_value(self.reader.decoder())?;
1053
1054 match local_name.as_ref() {
1055 b"label" => label = Some(value.into_owned()),
1056 b"href" => href = Some(value.into_owned()),
1057 _ => {}
1058 }
1059 }
1060
1061 if let (Some(label), Some(href)) = (label, href) {
1062 locators.push(Locator { label, href });
1063 }
1064 }
1065 b"footnoteArc" => {
1066 let mut from = None;
1067 let mut to = None;
1068
1069 for attribute in event.attributes() {
1070 let attribute = attribute.map_err(|err| XbrlError::XmlParse {
1071 path: self.path.clone(),
1072 position: self.reader.buffer_position(),
1073 element: Some("footnoteArc".to_string()),
1074 source: err.into(),
1075 })?;
1076 let local_name = attribute.key.local_name();
1077 let value =
1078 attribute.decode_and_unescape_value(self.reader.decoder())?;
1079
1080 match local_name.as_ref() {
1081 b"from" => from = Some(value.into_owned()),
1082 b"to" => to = Some(value.into_owned()),
1083 _ => {}
1084 }
1085 }
1086
1087 if let (Some(from), Some(to)) = (from, to) {
1088 arcs.push(FootnoteArc { from, to });
1089 }
1090 }
1091 b"footnote" => {
1092 let mut label = None;
1093 let mut lang = None;
1094
1095 for attribute in event.attributes() {
1096 let attribute = attribute.map_err(|err| XbrlError::XmlParse {
1097 path: self.path.clone(),
1098 position: self.reader.buffer_position(),
1099 element: Some("footnote".to_string()),
1100 source: err.into(),
1101 })?;
1102 let local_name = attribute.key.local_name();
1103 let value =
1104 attribute.decode_and_unescape_value(self.reader.decoder())?;
1105
1106 match local_name.as_ref() {
1107 b"label" => label = Some(value.into_owned()),
1108 b"lang" => lang = Some(value.into_owned()),
1109 _ => {}
1110 }
1111 }
1112
1113 let mut text = String::new();
1115 let mut text_buf = Vec::new();
1116 loop {
1117 match self.reader.read_event_into(&mut text_buf)? {
1118 Event::Text(ref t) => {
1119 let decoded =
1120 t.xml_content().map_err(quick_xml::Error::from)?;
1121 text.push_str(&decoded);
1122 }
1123 Event::End(ref e) if e.local_name().as_ref() == b"footnote" => {
1124 break;
1125 }
1126 Event::Eof => break,
1127 _ => {}
1128 }
1129 text_buf.clear();
1130 }
1131
1132 if let Some(label) = label {
1133 footnotes.push(FootnoteResource {
1134 label,
1135 lang,
1136 text: text.trim().to_string(),
1137 });
1138 }
1139 }
1140 _ => {}
1141 }
1142 }
1143 Event::End(ref event) if event.local_name().as_ref() == b"footnoteLink" => {
1144 break;
1145 }
1146 Event::Eof => break,
1147 _ => {}
1148 }
1149 buf.clear();
1150 }
1151
1152 instance.footnote_links.push(RawFootnoteLink {
1153 role,
1154 locators,
1155 arcs,
1156 footnotes,
1157 });
1158
1159 Ok(())
1160 }
1161}
1162
1163#[cfg(test)]
1164mod tests {
1165 use super::*;
1166 use assert_matches::assert_matches;
1167 use std::str::FromStr;
1168
1169 #[test]
1170 fn test_parse_instance_root() {
1171 let xml = r#"<xbrli:xbrl xmlns:xbrli="http://www.xbrl.org/2003/instance"
1172 xmlns:ifrs="http://xbrl.ifrs.org/taxonomy/2023">
1173 </xbrli:xbrl>"#;
1174 let mut parser = InstanceParser::from_reader(xml.as_bytes());
1175 let instance = parser.parse_instance().unwrap();
1176
1177 assert_eq!(instance.namespaces.len(), 2);
1178 assert_eq!(
1179 instance
1180 .namespaces
1181 .get(&NamespacePrefix::from("xbrli"))
1182 .unwrap(),
1183 &NamespaceUri::from("http://www.xbrl.org/2003/instance")
1184 );
1185 assert_eq!(
1186 instance
1187 .namespaces
1188 .get(&NamespacePrefix::from("ifrs"))
1189 .unwrap(),
1190 &NamespaceUri::from("http://xbrl.ifrs.org/taxonomy/2023")
1191 );
1192 }
1193
1194 #[test]
1195 fn test_parse_schema_ref() {
1196 let xml = r#"<xbrli:xbrl xmlns:xbrli="http://www.xbrl.org/2003/instance"
1197 xmlns:ifrs="http://xbrl.ifrs.org/taxonomy/2023">
1198 <link:schemaRef xlink:href="ifrs.xsd" />
1199 </xbrli:xbrl>"#;
1200 let mut parser = InstanceParser::from_reader(xml.as_bytes());
1201 let instance = parser.parse_instance().unwrap();
1202
1203 assert_eq!(instance.schema_refs.len(), 1);
1204 assert_eq!(instance.schema_refs[0].href, "ifrs.xsd");
1205 }
1206
1207 #[test]
1208 fn test_parse_role_ref() {
1209 let xml = r#"<xbrli:xbrl xmlns:xbrli="http://www.xbrl.org/2003/instance"
1210 xmlns:ifrs="http://xbrl.ifrs.org/taxonomy/2023">
1211 <link:roleRef roleURI="http://example.com/role" xlink:href="role.xml" />
1212 </xbrli:xbrl>"#;
1213 let mut parser = InstanceParser::from_reader(xml.as_bytes());
1214 let instance = parser.parse_instance().unwrap();
1215
1216 assert_eq!(instance.role_refs.len(), 1);
1217 assert_eq!(instance.role_refs[0].role_uri, "http://example.com/role");
1218 assert_eq!(instance.role_refs[0].href, "role.xml");
1219 }
1220
1221 #[test]
1222 fn test_parse_arcrole_ref() {
1223 let xml = r#"<xbrli:xbrl xmlns:xbrli="http://www.xbrl.org/2003/instance"
1224 xmlns:ifrs="http://xbrl.ifrs.org/taxonomy/2023">
1225 <link:arcroleRef arcroleURI="http://example.com/arcrole" xlink:href="arcrole.xml" />
1226 </xbrli:xbrl>"#;
1227 let mut parser = InstanceParser::from_reader(xml.as_bytes());
1228 let instance = parser.parse_instance().unwrap();
1229
1230 assert_eq!(instance.arcrole_refs.len(), 1);
1231 assert_eq!(
1232 instance.arcrole_refs[0].arcrole_uri,
1233 "http://example.com/arcrole"
1234 );
1235 assert_eq!(instance.arcrole_refs[0].href, "arcrole.xml");
1236 }
1237
1238 #[test]
1239 fn test_parse_context() {
1240 let xml = r#"<xbrli:xbrl
1241 xmlns:xbrli="http://www.xbrl.org/2003/instance"
1242 xmlns:xbrldi="http://xbrl.org/2006/xbrldi"
1243 xmlns:ifrs="http://xbrl.ifrs.org/taxonomy/2023">
1244 <context id="c1">
1245 <entity>
1246 <identifier scheme="http://example.com">ABC</identifier>
1247 <segment>
1248 <xbrldi:explicitMember dimension="ifrs:OperatingSegmentsAxis">
1249 ifrs:EuropeSegmentMember
1250 </xbrldi:explicitMember>
1251 </segment>
1252 </entity>
1253 <period>
1254 <instant>2024-12-31</instant>
1255 </period>
1256 <scenario>
1257 <xbrldi:explicitMember dimension="ifrs:ProductsAndServicesAxis">
1258 ifrs:SoftwareMember
1259 </xbrldi:explicitMember>
1260 </scenario>
1261 </context>
1262 </xbrli:xbrl>"#;
1263 let mut parser = InstanceParser::from_reader(xml.as_bytes());
1264 let instance = parser.parse_instance().unwrap();
1265
1266 assert_eq!(instance.contexts.len(), 1);
1267 let context = &instance.contexts[0];
1268 assert_eq!(
1269 context,
1270 &RawContext {
1271 id: "c1".to_string(),
1272 entity: RawEntity {
1273 identifier: "ABC".to_string(),
1274 scheme: "http://example.com".to_string(),
1275 segment_dimensions: vec![RawDimension {
1276 dimension: QName::from_str("ifrs:OperatingSegmentsAxis").unwrap(),
1277 member: QName::from_str("ifrs:EuropeSegmentMember").unwrap(),
1278 }],
1279 },
1280 period: RawPeriod::Instant("2024-12-31".to_string()),
1281 scenario_dimensions: vec![RawDimension {
1282 dimension: QName::from_str("ifrs:ProductsAndServicesAxis").unwrap(),
1283 member: QName::from_str("ifrs:SoftwareMember").unwrap(),
1284 }],
1285 }
1286 );
1287 }
1288
1289 #[test]
1290 fn test_parse_unit() {
1291 let xml = r#"<xbrli:xbrl xmlns:xbrli="http://www.xbrl.org/2003/instance"
1292 xmlns:ifrs="http://xbrl.ifrs.org/taxonomy/2023">
1293 <unit id="u1">
1294 <measure>iso4217:EUR</measure>
1295 </unit>
1296 </xbrli:xbrl>"#;
1297 let mut parser = InstanceParser::from_reader(xml.as_bytes());
1298 let instance = parser.parse_instance().unwrap();
1299
1300 assert_eq!(instance.units.len(), 1);
1301 let unit = &instance.units[0];
1302 assert_eq!(
1303 unit,
1304 &RawUnit {
1305 id: "u1".to_string(),
1306 numerator: vec![QName::from_str("iso4217:EUR").unwrap()],
1307 denominator: vec![],
1308 }
1309 );
1310 }
1311
1312 #[test]
1313 fn test_parse_unit_divide() {
1314 let xml = r#"<xbrli:xbrl xmlns:xbrli="http://www.xbrl.org/2003/instance"
1315 xmlns:ifrs="http://xbrl.ifrs.org/taxonomy/2023">
1316 <xbrli:unit id="USD_per_share">
1317 <xbrli:divide>
1318 <xbrli:unitNumerator>
1319 <xbrli:measure>iso4217:USD</xbrli:measure>
1320 </xbrli:unitNumerator>
1321 <xbrli:unitDenominator>
1322 <xbrli:measure>xbrli:shares</xbrli:measure>
1323 </xbrli:unitDenominator>
1324 </xbrli:divide>
1325 </xbrli:unit>
1326 </xbrli:xbrl>"#;
1327 let mut parser = InstanceParser::from_reader(xml.as_bytes());
1328 let instance = parser.parse_instance().unwrap();
1329
1330 assert_eq!(instance.units.len(), 1);
1331 let unit = &instance.units[0];
1332 assert_eq!(
1333 unit,
1334 &RawUnit {
1335 id: "USD_per_share".to_string(),
1336 numerator: vec![QName::from_str("iso4217:USD").unwrap()],
1337 denominator: vec![QName::from_str("xbrli:shares").unwrap()],
1338 }
1339 );
1340 }
1341
1342 #[test]
1343 fn test_parse_item_fact() {
1344 let xml = r#"<xbrli:xbrl xmlns:xbrli="http://www.xbrl.org/2003/instance"
1345 xmlns:ifrs="http://xbrl.ifrs.org/taxonomy/2023">
1346 <ifrs:Revenue contextRef="c1" unitRef="u1" decimals="-3">
1347 1200000
1348 </ifrs:Revenue>
1349 </xbrli:xbrl>"#;
1350 let mut parser = InstanceParser::from_reader(xml.as_bytes());
1351 let instance = parser.parse_instance().unwrap();
1352
1353 assert_eq!(instance.facts.len(), 1);
1354 let fact = &instance.facts[0];
1355 assert_matches!(fact, RawFact::Item(fact) => {
1356 assert_eq!(fact.name.to_string(), "ifrs:Revenue");
1357 assert_eq!(fact.value, "1200000");
1358 assert_eq!(fact.context_ref, "c1");
1359 assert_eq!(fact.unit_ref.as_deref(), Some("u1"));
1360 assert_eq!(fact.decimals.as_deref(), Some("-3"));
1361 assert!(!fact.is_nil);
1362 });
1363 }
1364
1365 #[test]
1366 fn test_parse_tuple_fact() {
1367 let xml = r#"<xbrli:xbrl xmlns:xbrli="http://www.xbrl.org/2003/instance"
1368 xmlns:t="http://example.com/taxonomy">
1369 <t:Address>
1370 <t:Street contextRef="c1">Main Street</t:Street>
1371 <t:City contextRef="c1">Berlin</t:City>
1372 </t:Address>
1373 </xbrli:xbrl>"#;
1374 let mut parser = InstanceParser::from_reader(xml.as_bytes());
1375 let instance = parser.parse_instance().unwrap();
1376
1377 assert_eq!(instance.facts.len(), 1);
1378 let fact = &instance.facts[0];
1379 assert_matches!(fact, RawFact::Tuple(tuple) => {
1380 assert_eq!(tuple.name.to_string(), "t:Address");
1381 assert!(!tuple.is_nil);
1382 assert_eq!(tuple.children.len(), 2);
1383
1384 assert_matches!(&tuple.children[0], RawFact::Item(item) => {
1385 assert_eq!(item.name.to_string(), "t:Street");
1386 assert_eq!(item.value, "Main Street");
1387 assert_eq!(item.context_ref, "c1");
1388 });
1389 assert_matches!(&tuple.children[1], RawFact::Item(item) => {
1390 assert_eq!(item.name.to_string(), "t:City");
1391 assert_eq!(item.value, "Berlin");
1392 assert_eq!(item.context_ref, "c1");
1393 });
1394 });
1395 }
1396
1397 #[test]
1398 fn test_parse_nested_tuple() {
1399 let xml = r#"<xbrli:xbrl xmlns:xbrli="http://www.xbrl.org/2003/instance"
1400 xmlns:t="http://example.com/taxonomy">
1401 <t:Outer>
1402 <t:Inner>
1403 <t:Value contextRef="c1">42</t:Value>
1404 </t:Inner>
1405 </t:Outer>
1406 </xbrli:xbrl>"#;
1407 let mut parser = InstanceParser::from_reader(xml.as_bytes());
1408 let instance = parser.parse_instance().unwrap();
1409
1410 assert_eq!(instance.facts.len(), 1);
1411 let fact = &instance.facts[0];
1412 assert_matches!(fact, RawFact::Tuple(outer) => {
1413 assert_eq!(outer.name.to_string(), "t:Outer");
1414 assert!(!outer.is_nil);
1415 assert_eq!(outer.children.len(), 1);
1416
1417 assert_matches!(&outer.children[0], RawFact::Tuple(inner) => {
1418 assert_eq!(inner.name.to_string(), "t:Inner");
1419 assert!(!inner.is_nil);
1420 assert_eq!(inner.children.len(), 1);
1421
1422 assert_matches!(&inner.children[0], RawFact::Item(item) => {
1423 assert_eq!(item.name.to_string(), "t:Value");
1424 assert_eq!(item.value, "42");
1425 assert_eq!(item.context_ref, "c1");
1426 });
1427 });
1428 });
1429 }
1430
1431 #[test]
1432 fn test_parse_nil_item_fact() {
1433 let xml = r#"<xbrli:xbrl xmlns:xbrli="http://www.xbrl.org/2003/instance"
1434 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
1435 xmlns:ifrs="http://xbrl.ifrs.org/taxonomy/2023">
1436 <ifrs:Revenue contextRef="c1" xsi:nil="true" />
1437 </xbrli:xbrl>"#;
1438 let mut parser = InstanceParser::from_reader(xml.as_bytes());
1439 let instance = parser.parse_instance().unwrap();
1440
1441 assert_eq!(instance.facts.len(), 1);
1442 match &instance.facts[0] {
1443 RawFact::Item(fact) => {
1444 assert_eq!(fact.name.to_string(), "ifrs:Revenue");
1445 assert!(fact.is_nil);
1446 assert_eq!(fact.value, "");
1447 assert_eq!(fact.context_ref, "c1");
1448 }
1449 RawFact::Tuple(_) => panic!("expected item fact"),
1450 }
1451 }
1452
1453 #[test]
1454 fn test_parse_empty_tuple() {
1455 let xml = r#"<xbrli:xbrl xmlns:xbrli="http://www.xbrl.org/2003/instance"
1456 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
1457 xmlns:t="http://example.com/taxonomy">
1458 <t:Address xsi:nil="true" />
1459 </xbrli:xbrl>"#;
1460 let mut parser = InstanceParser::from_reader(xml.as_bytes());
1461 let instance = parser.parse_instance().unwrap();
1462
1463 assert_eq!(instance.facts.len(), 1);
1464 match &instance.facts[0] {
1465 RawFact::Tuple(tuple) => {
1466 assert_eq!(tuple.name.to_string(), "t:Address");
1467 assert!(tuple.is_nil);
1468 assert!(tuple.children.is_empty());
1469 }
1470 RawFact::Item(_) => panic!("expected tuple fact"),
1471 }
1472 }
1473
1474 #[test]
1475 fn test_parse_footnote_link() {
1476 let xml = r##"<xbrli:xbrl xmlns:xbrli="http://www.xbrl.org/2003/instance"
1477 xmlns:ifrs="http://xbrl.ifrs.org/taxonomy/2023">
1478 <link:footnoteLink role="http://example.com/footnote">
1479 <link:loc xlink:label="loc1" xlink:href="#c1" />
1480 <link:footnote xlink:label="fn1" xml:lang="en">
1481 This is a footnote.
1482 </link:footnote>
1483 <link:footnoteArc xlink:from="loc1" xlink:to="fn1" />
1484 </link:footnoteLink>
1485 </xbrli:xbrl>"##;
1486 let mut parser = InstanceParser::from_reader(xml.as_bytes());
1487 let instance = parser.parse_instance().unwrap();
1488
1489 assert_eq!(instance.footnote_links.len(), 1);
1490 let footnote_link = &instance.footnote_links[0];
1491 assert_eq!(
1492 footnote_link,
1493 &RawFootnoteLink {
1494 role: "http://example.com/footnote".to_string(),
1495 locators: vec![Locator {
1496 label: "loc1".to_string(),
1497 href: "#c1".to_string(),
1498 }],
1499 arcs: vec![FootnoteArc {
1500 from: "loc1".to_string(),
1501 to: "fn1".to_string(),
1502 }],
1503 footnotes: vec![FootnoteResource {
1504 label: "fn1".to_string(),
1505 lang: Some("en".to_string()),
1506 text: "This is a footnote.".to_string(),
1507 }],
1508 }
1509 );
1510 }
1511
1512 #[test]
1513 fn test_parse_instance() {
1514 let xml = r#"<xbrli:xbrl xmlns:xbrli="http://www.xbrl.org/2003/instance"
1515 xmlns:ifrs="http://xbrl.ifrs.org/taxonomy/2023">
1516 <link:schemaRef xlink:href="ifrs.xsd" />
1517 <context id="c1">
1518 <entity>
1519 <identifier scheme="http://example.com">ABC</identifier>
1520 </entity>
1521 <period>
1522 <instant>2024-12-31</instant>
1523 </period>
1524 </context>
1525 <unit id="u1">
1526 <measure>iso4217:EUR</measure>
1527 </unit>
1528 <ifrs:Revenue contextRef="c1" unitRef="u1" decimals="-3">
1529 1200000
1530 </ifrs:Revenue>
1531 </xbrli:xbrl>"#;
1532 let mut parser = InstanceParser::from_reader(xml.as_bytes());
1533 let instance = parser.parse_instance().unwrap();
1534
1535 assert_eq!(instance.contexts.len(), 1);
1536 assert_eq!(instance.units.len(), 1);
1537 assert_eq!(instance.facts.len(), 1);
1538
1539 assert_eq!(
1540 instance,
1541 RawInstance {
1542 namespaces: {
1543 let mut namespaces = HashMap::new();
1544 namespaces.insert("xbrli".into(), "http://www.xbrl.org/2003/instance".into());
1545 namespaces.insert("ifrs".into(), "http://xbrl.ifrs.org/taxonomy/2023".into());
1546 namespaces
1547 },
1548 schema_refs: vec![SchemaRef {
1549 href: "ifrs.xsd".to_string(),
1550 }],
1551 role_refs: vec![],
1552 arcrole_refs: vec![],
1553 contexts: vec![RawContext {
1554 id: "c1".to_string(),
1555 entity: RawEntity {
1556 identifier: "ABC".to_string(),
1557 scheme: "http://example.com".to_string(),
1558 segment_dimensions: vec![],
1559 },
1560 period: RawPeriod::Instant("2024-12-31".to_string()),
1561 scenario_dimensions: vec![],
1562 }],
1563 units: vec![RawUnit {
1564 id: "u1".to_string(),
1565 numerator: vec![QName::from_str("iso4217:EUR").unwrap()],
1566 denominator: vec![],
1567 }],
1568 facts: vec![RawFact::Item(RawItemFact {
1569 name: QName::from_str("ifrs:Revenue").unwrap(),
1570 value: "1200000".to_string(),
1571 context_ref: "c1".to_string(),
1572 unit_ref: Some("u1".to_string()),
1573 decimals: Some("-3".to_string()),
1574 precision: None,
1575 id: None,
1576 is_nil: false,
1577 })],
1578 footnote_links: vec![],
1579 }
1580 );
1581 }
1582}