1use crate::{RoleUri, XbrlError, xml::ArcroleUri};
2use quick_xml::{
3 Reader,
4 events::{BytesStart, Event},
5};
6use rust_decimal::Decimal;
7use std::{
8 fs::File,
9 io::{BufRead, BufReader},
10 path::{Path, PathBuf},
11 str,
12};
13
14#[derive(Debug, PartialEq, Eq)]
16pub struct Locator {
17 pub label: String,
19 pub href: String,
21}
22
23#[derive(Debug, PartialEq, Eq)]
25pub struct LabelResource {
26 pub label: String,
28 pub role: Option<String>,
30 pub lang: String,
32 pub text: String,
34}
35
36#[derive(Debug, PartialEq, Eq)]
45pub struct ReferenceResource {
46 pub label: String,
48 pub role: Option<String>,
50}
51
52#[derive(Debug, Clone, PartialEq)]
54pub struct RawPresentationArc {
55 pub from: String,
57 pub to: String,
59 pub order: Option<Decimal>,
61 pub preferred_label: Option<RoleUri>,
63 pub arcrole: ArcroleUri,
65}
66
67#[derive(Debug, Clone, PartialEq)]
69pub struct RawCalculationArc {
70 pub from: String,
72 pub to: String,
74 pub order: Option<Decimal>,
76 pub weight: Decimal,
78 pub arcrole: ArcroleUri,
80}
81
82#[derive(Debug, Clone, PartialEq)]
84pub struct RawDefinitionArc {
85 pub from: String,
87 pub to: String,
89 pub order: Option<Decimal>,
91 pub arcrole: ArcroleUri,
93}
94
95#[derive(Debug, PartialEq, Eq)]
97pub struct RawLabelArc {
98 pub from: String,
101 pub to: String,
104}
105
106#[derive(Debug, PartialEq, Eq)]
108pub struct RawReferenceArc {
109 pub from: String,
112 pub to: String,
115}
116
117#[derive(Debug, PartialEq)]
119pub struct PresentationLink {
120 pub role: String,
123 pub locators: Vec<Locator>,
126 pub arcs: Vec<RawPresentationArc>,
129}
130
131#[derive(Debug, PartialEq)]
133pub struct CalculationLink {
134 pub role: String,
137 pub locators: Vec<Locator>,
140 pub arcs: Vec<RawCalculationArc>,
143}
144
145#[derive(Debug, PartialEq)]
147pub struct DefinitionLink {
148 pub role: String,
151 pub locators: Vec<Locator>,
154 pub arcs: Vec<RawDefinitionArc>,
157}
158
159#[derive(Debug, PartialEq, Eq)]
161pub struct ReferenceLink {
162 pub role: String,
165 pub locators: Vec<Locator>,
168 pub arcs: Vec<RawReferenceArc>,
171 pub references: Vec<ReferenceResource>,
174}
175
176#[derive(Debug, PartialEq, Eq)]
192pub struct LabelLink {
193 pub role: String,
196 pub locators: Vec<Locator>,
199 pub arcs: Vec<RawLabelArc>,
202 pub labels: Vec<LabelResource>,
205}
206
207#[derive(Debug, PartialEq, Default)]
209pub struct RawLinkbases {
210 pub presentation_links: Vec<PresentationLink>,
213 pub calculation_links: Vec<CalculationLink>,
216 pub definition_links: Vec<DefinitionLink>,
219 pub label_links: Vec<LabelLink>,
222 pub reference_links: Vec<ReferenceLink>,
225}
226
227impl RawLinkbases {
228 pub fn new(
230 presentation_links: Vec<PresentationLink>,
231 calculation_links: Vec<CalculationLink>,
232 definition_links: Vec<DefinitionLink>,
233 label_links: Vec<LabelLink>,
234 reference_links: Vec<ReferenceLink>,
235 ) -> Self {
236 Self {
237 presentation_links,
238 calculation_links,
239 definition_links,
240 label_links,
241 reference_links,
242 }
243 }
244}
245
246fn parse_decimal(value: &str) -> Result<Decimal, XbrlError> {
248 value.parse::<Decimal>().map_err(|_| XbrlError::ParseError {
249 expected: "floating point number",
250 value: value.to_string(),
251 })
252}
253
254pub struct LinkbaseParser<R> {
256 path: Option<PathBuf>,
259 reader: Reader<R>,
261}
262
263impl LinkbaseParser<BufReader<File>> {
264 pub fn from_file(path: &Path) -> Result<Self, XbrlError> {
266 let file = File::open(path).map_err(|err| XbrlError::FileOpen {
267 path: path.to_path_buf(),
268 context: "opening file".to_string(),
269 source: err,
270 })?;
271 let mut reader = Reader::from_reader(BufReader::new(file));
272
273 reader.config_mut().trim_text_start = true;
274 reader.config_mut().trim_text_end = true;
275
276 Ok(Self {
277 path: Some(path.to_path_buf()),
278 reader,
279 })
280 }
281}
282
283impl<R: BufRead> LinkbaseParser<R> {
284 pub fn new(reader: Reader<R>) -> Self {
286 Self { path: None, reader }
287 }
288
289 pub fn from_reader(reader: R) -> Self {
291 let mut reader = Reader::from_reader(reader);
292
293 reader.config_mut().trim_text_start = true;
294 reader.config_mut().trim_text_end = true;
295
296 Self { path: None, reader }
297 }
298
299 pub fn parse_linkbase(&mut self, linkbase: &mut RawLinkbases) -> Result<(), XbrlError> {
302 let mut buf = Vec::new();
303 let mut has_linkbase_root = false;
304
305 loop {
306 match self.reader.read_event_into(&mut buf)? {
307 quick_xml::events::Event::Start(event) => match event.local_name().as_ref() {
308 b"linkbase" => {
309 has_linkbase_root = true;
310 }
311 b"presentationLink" => {
312 let link = self.parse_presentation_link(event)?;
313 linkbase.presentation_links.push(link);
314 }
315
316 b"calculationLink" => {
317 let link = self.parse_calculation_link(event)?;
318 linkbase.calculation_links.push(link);
319 }
320
321 b"definitionLink" => {
322 let link = self.parse_definition_link(event)?;
323 linkbase.definition_links.push(link);
324 }
325
326 b"labelLink" => {
327 let link = self.parse_label_link(event)?;
328 linkbase.label_links.push(link);
329 }
330
331 b"referenceLink" => {
332 let link = self.parse_reference_link(event)?;
333 linkbase.reference_links.push(link);
334 }
335
336 _ => {}
337 },
338 Event::Eof => break,
339
340 _ => {}
341 }
342
343 buf.clear();
344 }
345
346 if !has_linkbase_root {
347 return Err(XbrlError::InvalidLinkbaseDocument {
348 path: self.path.clone(),
349 reason: "missing <linkbase> root element".to_string(),
350 });
351 }
352
353 Ok(())
354 }
355
356 fn parse_presentation_link(
358 &mut self,
359 start: BytesStart,
360 ) -> Result<PresentationLink, XbrlError> {
361 let mut role = None;
363 for attribute in start.attributes() {
364 let attribute = attribute.map_err(|err| XbrlError::XmlParse {
365 path: self.path.clone(),
366 position: self.reader.buffer_position(),
367 element: Some("presentationLink".to_string()),
368 source: err.into(),
369 })?;
370
371 if attribute.key.as_ref() == b"xlink:role" {
372 let value = attribute.decode_and_unescape_value(self.reader.decoder())?;
373 role = Some(value.to_string());
374 }
375 }
376 let role = role.ok_or_else(|| XbrlError::ParseError {
377 expected: "xlink:role on presentationLink",
378 value: "".to_string(),
379 })?;
380
381 let mut locators = Vec::new();
382 let mut arcs = Vec::new();
383 let mut buf = Vec::new();
384
385 loop {
386 match self.reader.read_event_into(&mut buf)? {
387 Event::Start(event) | Event::Empty(event) => match event.local_name().as_ref() {
388 b"loc" => {
389 let locator = self.parse_locator(&event)?;
390 locators.push(locator);
391 }
392 b"presentationArc" => {
393 let mut from = None;
395 let mut to = None;
396 let mut order = None;
397 let mut preferred_label = None;
398 let mut arcrole = None;
399
400 for attribute in event.attributes() {
401 let attribute = attribute.map_err(|err| XbrlError::XmlParse {
402 path: self.path.clone(),
403 position: self.reader.buffer_position(),
404 element: Some("presentationLink".to_string()),
405 source: err.into(),
406 })?;
407
408 match attribute.key.as_ref() {
409 b"xlink:from" => {
410 let value = attribute
411 .decode_and_unescape_value(self.reader.decoder())?;
412 from = Some(value.to_string())
413 }
414 b"xlink:to" => {
415 let value = attribute
416 .decode_and_unescape_value(self.reader.decoder())?;
417 to = Some(value.to_string())
418 }
419 b"order" => {
420 let value = attribute
421 .decode_and_unescape_value(self.reader.decoder())?;
422 order = Some(parse_decimal(&value)?);
423 }
424 b"preferredLabel" => {
425 let value = attribute
426 .decode_and_unescape_value(self.reader.decoder())?;
427 preferred_label = Some(value.to_string())
428 }
429 b"xlink:arcrole" => {
430 let value = attribute
431 .decode_and_unescape_value(self.reader.decoder())?;
432 arcrole = Some(value.to_string())
433 }
434 _ => {}
435 }
436 }
437
438 let arc = RawPresentationArc {
439 from: from.ok_or_else(|| XbrlError::ParseError {
440 expected: "xlink:from on presentationArc",
441 value: "".to_string(),
442 })?,
443 to: to.ok_or_else(|| XbrlError::ParseError {
444 expected: "xlink:to on presentationArc",
445 value: "".to_string(),
446 })?,
447 arcrole: arcrole
448 .ok_or_else(|| XbrlError::ParseError {
449 expected: "xlink:arcrole on presentationArc",
450 value: "".to_string(),
451 })?
452 .into(),
453 order,
454 preferred_label: preferred_label.map(RoleUri::from),
455 };
456 arcs.push(arc);
457 }
458
459 _ => {}
460 },
461 Event::End(event) => {
462 if event.name() == start.name() {
463 break;
464 }
465 }
466 Event::Eof => {
467 return Err(XbrlError::ParseError {
468 expected: "presentationLink end tag",
469 value: "".to_string(),
470 });
471 }
472
473 _ => {}
474 }
475
476 buf.clear();
477 }
478
479 Ok(PresentationLink {
480 role,
481 locators,
482 arcs,
483 })
484 }
485
486 fn parse_calculation_link(&mut self, start: BytesStart) -> Result<CalculationLink, XbrlError> {
488 let mut role = None;
490 for attribute in start.attributes() {
491 let attribute = attribute.map_err(|err| XbrlError::XmlParse {
492 path: self.path.clone(),
493 position: self.reader.buffer_position(),
494 element: Some("calculationLink".to_string()),
495 source: err.into(),
496 })?;
497
498 if attribute.key.as_ref() == b"xlink:role" {
499 let value = attribute.decode_and_unescape_value(self.reader.decoder())?;
500 role = Some(value.to_string());
501 }
502 }
503 let role = role.ok_or_else(|| XbrlError::ParseError {
504 expected: "xlink:role on calculationLink",
505 value: "".to_string(),
506 })?;
507
508 let mut locators = Vec::new();
509 let mut arcs = Vec::new();
510 let mut buf = Vec::new();
511
512 loop {
513 match self.reader.read_event_into(&mut buf)? {
514 Event::Start(event) | Event::Empty(event) => match event.local_name().as_ref() {
515 b"loc" => {
516 let locator = self.parse_locator(&event)?;
517 locators.push(locator);
518 }
519 b"calculationArc" => {
520 let mut from = None;
522 let mut to = None;
523 let mut order = None;
524 let mut weight = None;
525 let mut arcrole = None;
526
527 for attribute in event.attributes() {
528 let attribute = attribute.map_err(|err| XbrlError::XmlParse {
529 path: self.path.clone(),
530 position: self.reader.buffer_position(),
531 element: Some("calculationLink".to_string()),
532 source: err.into(),
533 })?;
534
535 match attribute.key.as_ref() {
536 b"xlink:from" => {
537 let value = attribute
538 .decode_and_unescape_value(self.reader.decoder())?;
539 from = Some(value.to_string())
540 }
541 b"xlink:to" => {
542 let value = attribute
543 .decode_and_unescape_value(self.reader.decoder())?;
544 to = Some(value.to_string())
545 }
546 b"xlink:arcrole" => {
547 let value = attribute
548 .decode_and_unescape_value(self.reader.decoder())?;
549 arcrole = Some(value.to_string())
550 }
551 b"order" => {
552 let value = attribute
553 .decode_and_unescape_value(self.reader.decoder())?;
554 order = Some(parse_decimal(&value)?);
555 }
556 b"weight" => {
557 let value = attribute
558 .decode_and_unescape_value(self.reader.decoder())?;
559 weight = Some(parse_decimal(&value)?);
560 }
561 _ => {}
562 }
563 }
564
565 arcs.push(RawCalculationArc {
566 from: from.ok_or_else(|| XbrlError::ParseError {
567 expected: "xlink:from on calculationArc",
568 value: "".to_string(),
569 })?,
570 to: to.ok_or_else(|| XbrlError::ParseError {
571 expected: "xlink:to on calculationArc",
572 value: "".to_string(),
573 })?,
574 arcrole: arcrole
575 .ok_or_else(|| XbrlError::ParseError {
576 expected: "xlink:arcrole on calculationArc",
577 value: "".to_string(),
578 })?
579 .into(),
580 order,
581 weight: weight.ok_or_else(|| XbrlError::ParseError {
582 expected: "weight on calculationArc",
583 value: "".to_string(),
584 })?,
585 });
586 }
587 _ => {}
588 },
589 Event::End(event) => {
590 if event.name() == start.name() {
591 break;
592 }
593 }
594 Event::Eof => {
595 return Err(XbrlError::ParseError {
596 expected: "calculationLink end tag",
597 value: "".to_string(),
598 });
599 }
600 _ => {}
601 }
602
603 buf.clear();
604 }
605
606 Ok(CalculationLink {
607 role,
608 locators,
609 arcs,
610 })
611 }
612
613 fn parse_definition_link(&mut self, start: BytesStart) -> Result<DefinitionLink, XbrlError> {
615 let mut role = None;
617 for attribute in start.attributes() {
618 let attribute = attribute.map_err(|err| XbrlError::XmlParse {
619 path: self.path.clone(),
620 position: self.reader.buffer_position(),
621 element: Some("definitionLink".to_string()),
622 source: err.into(),
623 })?;
624
625 if attribute.key.as_ref() == b"xlink:role" {
626 let value = attribute.decode_and_unescape_value(self.reader.decoder())?;
627 role = Some(value.to_string());
628 }
629 }
630 let role = role.ok_or_else(|| XbrlError::ParseError {
631 expected: "xlink:role on definitionLink",
632 value: "".to_string(),
633 })?;
634
635 let mut locators = Vec::new();
636 let mut arcs = Vec::new();
637 let mut buf = Vec::new();
638
639 loop {
640 match self.reader.read_event_into(&mut buf)? {
641 Event::Start(event) | Event::Empty(event) => match event.local_name().as_ref() {
642 b"loc" => {
643 let locator = self.parse_locator(&event)?;
644 locators.push(locator);
645 }
646 b"definitionArc" => {
647 let mut from = None;
648 let mut to = None;
649 let mut arcrole = None;
650 let mut order = None;
651
652 for attribute in event.attributes() {
653 let attribute = attribute.map_err(|err| XbrlError::XmlParse {
654 path: self.path.clone(),
655 position: self.reader.buffer_position(),
656 element: Some("definitionArc".to_string()),
657 source: err.into(),
658 })?;
659
660 match attribute.key.as_ref() {
661 b"xlink:from" => {
662 let value = attribute
663 .decode_and_unescape_value(self.reader.decoder())?;
664 from = Some(value.to_string());
665 }
666 b"xlink:to" => {
667 let value = attribute
668 .decode_and_unescape_value(self.reader.decoder())?;
669 to = Some(value.to_string());
670 }
671 b"xlink:arcrole" => {
672 let value = attribute
673 .decode_and_unescape_value(self.reader.decoder())?;
674 arcrole = Some(value.to_string());
675 }
676 b"order" => {
677 let value = attribute
678 .decode_and_unescape_value(self.reader.decoder())?;
679 order = Some(parse_decimal(&value)?);
680 }
681 _ => {}
682 }
683 }
684
685 arcs.push(RawDefinitionArc {
686 from: from.ok_or_else(|| XbrlError::ParseError {
687 expected: "xlink:from on definitionArc",
688 value: "".to_string(),
689 })?,
690 to: to.ok_or_else(|| XbrlError::ParseError {
691 expected: "xlink:to on definitionArc",
692 value: "".to_string(),
693 })?,
694 arcrole: arcrole
695 .ok_or_else(|| XbrlError::ParseError {
696 expected: "xlink:arcrole on definitionArc",
697 value: "".to_string(),
698 })?
699 .into(),
700 order,
701 });
702 }
703 _ => {}
704 },
705 Event::End(event) => {
706 if event.name() == start.name() {
707 break;
708 }
709 }
710 Event::Eof => {
711 return Err(XbrlError::ParseError {
712 expected: "definitionLink end tag",
713 value: "".to_string(),
714 });
715 }
716 _ => {}
717 }
718
719 buf.clear();
720 }
721
722 Ok(DefinitionLink {
723 role,
724 locators,
725 arcs,
726 })
727 }
728
729 fn parse_label_link(&mut self, start: BytesStart) -> Result<LabelLink, XbrlError> {
731 let mut role = String::new();
733
734 for attribute in start.attributes() {
735 let attribute = attribute.map_err(|err| XbrlError::XmlParse {
736 path: self.path.clone(),
737 position: self.reader.buffer_position(),
738 element: Some("labelLink".to_string()),
739 source: err.into(),
740 })?;
741
742 if attribute.key.as_ref() == b"xlink:role" {
743 let value = attribute.decode_and_unescape_value(self.reader.decoder())?;
744 role = value.to_string();
745 }
746 }
747
748 let mut locators = Vec::new();
749 let mut arcs = Vec::new();
750 let mut labels = Vec::new();
751 let mut buf = Vec::new();
752
753 loop {
754 match self.reader.read_event_into(&mut buf)? {
755 Event::Start(event) | Event::Empty(event) => match event.local_name().as_ref() {
756 b"loc" => {
757 let locator = self.parse_locator(&event)?;
758 locators.push(locator);
759 }
760 b"labelArc" => {
761 let mut from = None;
762 let mut to = None;
763
764 for attribute in event.attributes() {
765 let attribute = attribute.map_err(|err| XbrlError::XmlParse {
766 path: self.path.clone(),
767 position: self.reader.buffer_position(),
768 element: Some("labelArc".to_string()),
769 source: err.into(),
770 })?;
771
772 match attribute.key.as_ref() {
773 b"xlink:from" => {
774 let value = attribute
775 .decode_and_unescape_value(self.reader.decoder())?;
776 from = Some(value.to_string());
777 }
778 b"xlink:to" => {
779 let value = attribute
780 .decode_and_unescape_value(self.reader.decoder())?;
781 to = Some(value.to_string());
782 }
783 _ => {}
784 }
785 }
786
787 arcs.push(RawLabelArc {
788 from: from.ok_or_else(|| XbrlError::ParseError {
789 expected: "xlink:from on labelArc",
790 value: "".to_string(),
791 })?,
792 to: to.ok_or_else(|| XbrlError::ParseError {
793 expected: "xlink:to on labelArc",
794 value: "".to_string(),
795 })?,
796 });
797 }
798 b"label" => {
799 let mut label = None;
800 let mut label_role = None;
801 let mut label_lang = None;
802
803 for attribute in event.attributes() {
804 let attribute = attribute.map_err(|err| XbrlError::XmlParse {
805 path: self.path.clone(),
806 position: self.reader.buffer_position(),
807 element: Some("label".to_string()),
808 source: err.into(),
809 })?;
810
811 match attribute.key.as_ref() {
812 b"xlink:label" => {
813 let value = attribute
814 .decode_and_unescape_value(self.reader.decoder())?;
815 label = Some(value.to_string());
816 }
817 b"xlink:role" => {
818 let value = attribute
819 .decode_and_unescape_value(self.reader.decoder())?;
820 label_role = Some(value.to_string());
821 }
822 b"xml:lang" => {
823 let value = attribute
824 .decode_and_unescape_value(self.reader.decoder())?;
825 label_lang = Some(value.to_string());
826 }
827 _ => {}
828 }
829 }
830
831 let mut text_buf = Vec::new();
832 let bytes_text = self
833 .reader
834 .read_text_into(event.to_end().name(), &mut text_buf)?;
835 let text = str::from_utf8(bytes_text.as_ref())
836 .map_err(XbrlError::Utf8)?
837 .trim()
838 .to_string();
839
840 labels.push(LabelResource {
841 label: label.ok_or_else(|| XbrlError::ParseError {
842 expected: "xlink:label on label",
843 value: "".to_string(),
844 })?,
845 role: label_role,
846 lang: label_lang.unwrap_or_default(),
847 text,
848 });
849 }
850 _ => {}
851 },
852 Event::End(event) => {
853 if event.name() == start.name() {
854 break;
855 }
856 }
857 Event::Eof => {
858 return Err(XbrlError::ParseError {
859 expected: "labelLink end tag",
860 value: "".to_string(),
861 });
862 }
863 _ => {}
864 }
865
866 buf.clear();
867 }
868
869 Ok(LabelLink {
870 role,
871 locators,
872 arcs,
873 labels,
874 })
875 }
876
877 fn parse_reference_link(&mut self, start: BytesStart) -> Result<ReferenceLink, XbrlError> {
879 let mut role = String::new();
881
882 for attribute in start.attributes() {
883 let attribute = attribute.map_err(|err| XbrlError::XmlParse {
884 path: self.path.clone(),
885 position: self.reader.buffer_position(),
886 element: Some("referenceLink".to_string()),
887 source: err.into(),
888 })?;
889
890 if attribute.key.as_ref() == b"xlink:role" {
891 let value = attribute.decode_and_unescape_value(self.reader.decoder())?;
892 role = value.to_string();
893 }
894 }
895
896 let mut locators = Vec::new();
897 let mut arcs = Vec::new();
898 let mut references = Vec::new();
899 let mut buf = Vec::new();
900
901 loop {
902 match self.reader.read_event_into(&mut buf)? {
903 Event::Empty(event) | Event::Start(event) => match event.local_name().as_ref() {
904 b"loc" => {
905 let locator = self.parse_locator(&event)?;
906 locators.push(locator);
907 }
908 b"referenceArc" => {
909 let mut from = None;
910 let mut to = None;
911
912 for attribute in event.attributes() {
913 let attribute = attribute.map_err(|err| XbrlError::XmlParse {
914 path: self.path.clone(),
915 position: self.reader.buffer_position(),
916 element: Some("referenceArc".to_string()),
917 source: err.into(),
918 })?;
919
920 match attribute.key.as_ref() {
921 b"xlink:from" => {
922 let value = attribute
923 .decode_and_unescape_value(self.reader.decoder())?;
924 from = Some(value.to_string());
925 }
926 b"xlink:to" => {
927 let value = attribute
928 .decode_and_unescape_value(self.reader.decoder())?;
929 to = Some(value.to_string());
930 }
931 _ => {}
932 }
933 }
934
935 arcs.push(RawReferenceArc {
936 from: from.ok_or_else(|| XbrlError::ParseError {
937 expected: "xlink:from on referenceArc",
938 value: "".to_string(),
939 })?,
940 to: to.ok_or_else(|| XbrlError::ParseError {
941 expected: "xlink:to on referenceArc",
942 value: "".to_string(),
943 })?,
944 });
945 }
946 b"reference" => {
947 let mut label = None;
948 let mut ref_role = None;
949
950 for attribute in event.attributes() {
951 let attribute = attribute.map_err(|err| XbrlError::XmlParse {
952 path: self.path.clone(),
953 position: self.reader.buffer_position(),
954 element: Some("reference".to_string()),
955 source: err.into(),
956 })?;
957
958 match attribute.key.as_ref() {
959 b"xlink:label" => {
960 let value = attribute
961 .decode_and_unescape_value(self.reader.decoder())?;
962 label = Some(value.to_string());
963 }
964 b"xlink:role" => {
965 let value = attribute
966 .decode_and_unescape_value(self.reader.decoder())?;
967 ref_role = Some(value.to_string());
968 }
969 _ => {}
970 }
971 }
972
973 references.push(ReferenceResource {
974 label: label.ok_or_else(|| XbrlError::ParseError {
975 expected: "xlink:label on reference",
976 value: "".to_string(),
977 })?,
978 role: ref_role,
979 });
980 }
981 _ => {}
982 },
983 Event::End(event) => {
984 if event.name() == start.name() {
985 break;
986 }
987 }
988 Event::Eof => {
989 return Err(XbrlError::ParseError {
990 expected: "referenceLink end tag",
991 value: "".to_string(),
992 });
993 }
994 _ => {}
995 }
996
997 buf.clear();
998 }
999
1000 Ok(ReferenceLink {
1001 role,
1002 locators,
1003 arcs,
1004 references,
1005 })
1006 }
1007
1008 fn parse_locator(&mut self, event: &BytesStart) -> Result<Locator, XbrlError> {
1011 let mut label = None;
1012 let mut href = None;
1013
1014 for attribute in event.attributes() {
1015 let attribute = attribute.map_err(|err| XbrlError::XmlParse {
1016 path: self.path.clone(),
1017 position: self.reader.buffer_position(),
1018 element: Some("presentationLink".to_string()),
1019 source: err.into(),
1020 })?;
1021
1022 match attribute.key.as_ref() {
1023 b"xlink:label" => {
1024 let value = attribute.decode_and_unescape_value(self.reader.decoder())?;
1025 label = Some(value.to_string())
1026 }
1027 b"xlink:href" => {
1028 let value = attribute.decode_and_unescape_value(self.reader.decoder())?;
1029 href = Some(value.to_string())
1030 }
1031 _ => {}
1032 }
1033 }
1034 let locator = Locator {
1035 label: label.ok_or_else(|| XbrlError::ParseError {
1036 expected: "xlink:label on loc",
1037 value: "".to_string(),
1038 })?,
1039 href: href.ok_or_else(|| XbrlError::ParseError {
1040 expected: "xlink:href on loc",
1041 value: "".to_string(),
1042 })?,
1043 };
1044
1045 Ok(locator)
1046 }
1047}
1048
1049#[cfg(test)]
1050mod tests {
1051 use super::*;
1052 use assert_matches::assert_matches;
1053
1054 #[test]
1055 fn test_parse_missing_linkbase_root() {
1056 let xml = r#"<presentationLink
1057 xmlns:link="http://www.xbrl.org/2003/linkbase"
1058 xlink:type="extended"
1059 xlink:role="http://example.com/role/balanceSheet">
1060 </presentationLink>"#;
1061 let mut parser = LinkbaseParser::from_reader(xml.as_bytes());
1062 let mut linkbases = RawLinkbases::default();
1063
1064 let result = parser.parse_linkbase(&mut linkbases);
1065
1066 assert_matches!(result, Err(XbrlError::InvalidLinkbaseDocument { reason, .. }) if reason == "missing <linkbase> root element");
1067 }
1068
1069 #[test]
1070 fn test_parse_linkbase_presentation_link() {
1071 let xml = r#"<link:linkbase xmlns:link="http://www.xbrl.org/2003/linkbase">
1072 <link:presentationLink
1073 xlink:type="extended"
1074 xlink:role="http://example.com/role/balanceSheet">
1075 <link:loc
1076 xlink:type="locator"
1077 xlink:href="taxonomy.xsd#Assets"
1078 xlink:label="loc_assets" />
1079 <link:loc
1080 xlink:type="locator"
1081 xlink:href="taxonomy.xsd#Cash"
1082 xlink:label="loc_cash" />
1083 <link:presentationArc
1084 xlink:type="arc"
1085 xlink:arcrole="http://www.xbrl.org/2003/arcrole/parent-child"
1086 xlink:from="loc_assets"
1087 xlink:to="loc_cash"
1088 order="1" />
1089 </link:presentationLink>
1090 </link:linkbase>"#;
1091 let mut parser = LinkbaseParser::from_reader(xml.as_bytes());
1092 let mut linkbases = RawLinkbases::default();
1093 parser.parse_linkbase(&mut linkbases).unwrap();
1094
1095 assert_eq!(linkbases.presentation_links.len(), 1);
1096 assert_eq!(linkbases.calculation_links.len(), 0);
1097 assert_eq!(linkbases.definition_links.len(), 0);
1098 assert_eq!(linkbases.label_links.len(), 0);
1099 assert_eq!(linkbases.reference_links.len(), 0);
1100
1101 let presentation_link = &linkbases.presentation_links[0];
1102 assert_eq!(
1103 presentation_link,
1104 &PresentationLink {
1105 role: "http://example.com/role/balanceSheet".to_string(),
1106 locators: vec![
1107 Locator {
1108 label: "loc_assets".to_string(),
1109 href: "taxonomy.xsd#Assets".to_string(),
1110 },
1111 Locator {
1112 label: "loc_cash".to_string(),
1113 href: "taxonomy.xsd#Cash".to_string(),
1114 },
1115 ],
1116 arcs: vec![RawPresentationArc {
1117 from: "loc_assets".to_string(),
1118 to: "loc_cash".to_string(),
1119 order: Some(Decimal::new(1, 0)),
1120 preferred_label: None,
1121 arcrole: "http://www.xbrl.org/2003/arcrole/parent-child".into(),
1122 }],
1123 }
1124 );
1125 }
1126
1127 #[test]
1128 fn test_parse_linkbase_calculation_link() {
1129 let xml = r#"<link:linkbase xmlns:link="http://www.xbrl.org/2003/linkbase">
1130 <link:calculationLink xlink:type="extended" xlink:role="">
1131 <link:calculationArc
1132 xlink:type="arc"
1133 xlink:arcrole="http://www.xbrl.org/2003/arcrole/summation-item"
1134 xlink:from="loc_assets"
1135 xlink:to="loc_cash"
1136 weight="1"
1137 order="1" />
1138 </link:calculationLink>
1139 </link:linkbase>"#;
1140 let mut parser = LinkbaseParser::from_reader(xml.as_bytes());
1141 let mut linkbases = RawLinkbases::default();
1142 parser.parse_linkbase(&mut linkbases).unwrap();
1143
1144 assert_eq!(linkbases.presentation_links.len(), 0);
1145 assert_eq!(linkbases.calculation_links.len(), 1);
1146 assert_eq!(linkbases.definition_links.len(), 0);
1147 assert_eq!(linkbases.label_links.len(), 0);
1148 assert_eq!(linkbases.reference_links.len(), 0);
1149 let calculation_link = &linkbases.calculation_links[0];
1150 assert_eq!(
1151 calculation_link,
1152 &CalculationLink {
1153 role: "".to_string(),
1154 locators: vec![],
1155 arcs: vec![RawCalculationArc {
1156 from: "loc_assets".to_string(),
1157 to: "loc_cash".to_string(),
1158 order: Some(Decimal::new(1, 0)),
1159 weight: Decimal::new(1, 0),
1160 arcrole: "http://www.xbrl.org/2003/arcrole/summation-item".into(),
1161 }],
1162 }
1163 );
1164 }
1165
1166 #[test]
1167 fn test_parse_linkbase_definition_link() {
1168 let xml = r#"<link:linkbase xmlns:link="http://www.xbrl.org/2003/linkbase">
1169 <link:definitionLink xlink:type="extended" xlink:role="">
1170 <link:definitionArc
1171 xlink:type="arc"
1172 xlink:arcrole="http://xbrl.org/int/dim/arcrole/domain-member"
1173 xlink:from="loc_domain"
1174 xlink:to="loc_member" />
1175 </link:definitionLink>
1176 </link:linkbase>"#;
1177 let mut parser = LinkbaseParser::from_reader(xml.as_bytes());
1178 let mut linkbases = RawLinkbases::default();
1179 parser.parse_linkbase(&mut linkbases).unwrap();
1180
1181 assert_eq!(linkbases.presentation_links.len(), 0);
1182 assert_eq!(linkbases.calculation_links.len(), 0);
1183 assert_eq!(linkbases.definition_links.len(), 1);
1184 assert_eq!(linkbases.label_links.len(), 0);
1185 assert_eq!(linkbases.reference_links.len(), 0);
1186 let definition_link = &linkbases.definition_links[0];
1187 assert_eq!(
1188 definition_link,
1189 &DefinitionLink {
1190 role: "".to_string(),
1191 locators: vec![],
1192 arcs: vec![RawDefinitionArc {
1193 from: "loc_domain".to_string(),
1194 to: "loc_member".to_string(),
1195 arcrole: "http://xbrl.org/int/dim/arcrole/domain-member".into(),
1196 order: None,
1197 }],
1198 }
1199 );
1200 }
1201
1202 #[test]
1203 fn test_parse_linkbase_label_link() {
1204 let xml = r#"<link:linkbase xmlns:link="http://www.xbrl.org/2003/linkbase">
1205 <link:labelLink>
1206 <link:loc
1207 xlink:type="locator"
1208 xlink:href="taxonomy.xsd#Assets"
1209 xlink:label="loc_assets" />
1210 <link:label
1211 xlink:type="resource"
1212 xlink:label="lab_assets"
1213 xlink:role="http://www.xbrl.org/2003/role/label"
1214 xml:lang="en">
1215 Assets
1216 </link:label>
1217 <link:labelArc
1218 xlink:type="arc"
1219 xlink:from="loc_assets"
1220 xlink:to="lab_assets" />
1221 </link:labelLink>
1222 </link:linkbase>"#;
1223 let mut parser = LinkbaseParser::from_reader(xml.as_bytes());
1224 let mut linkbases = RawLinkbases::default();
1225 parser.parse_linkbase(&mut linkbases).unwrap();
1226
1227 assert_eq!(linkbases.presentation_links.len(), 0);
1228 assert_eq!(linkbases.calculation_links.len(), 0);
1229 assert_eq!(linkbases.definition_links.len(), 0);
1230 assert_eq!(linkbases.label_links.len(), 1);
1231 assert_eq!(linkbases.reference_links.len(), 0);
1232 let label_link = &linkbases.label_links[0];
1233 assert_eq!(
1234 label_link,
1235 &LabelLink {
1236 role: "".to_string(),
1237 locators: vec![Locator {
1238 label: "loc_assets".to_string(),
1239 href: "taxonomy.xsd#Assets".to_string(),
1240 }],
1241 arcs: vec![RawLabelArc {
1242 from: "loc_assets".to_string(),
1243 to: "lab_assets".to_string(),
1244 }],
1245 labels: vec![LabelResource {
1246 label: "lab_assets".to_string(),
1247 role: Some("http://www.xbrl.org/2003/role/label".to_string()),
1248 lang: "en".to_string(),
1249 text: "Assets".to_string(),
1250 }],
1251 }
1252 );
1253 }
1254
1255 #[test]
1256 fn test_parse_linkbase_reference_link() {
1257 let xml = r#"<link:linkbase xmlns:link="http://www.xbrl.org/2003/linkbase"
1258 xmlns:xlink="http://www.w3.org/1999/xlink"
1259 xmlns:my="http://example.com/my-taxonomy">
1260 <!-- Extended link for references -->
1261 <link:referenceLink xlink:type="extended" xlink:role="http://www.xbrl.org/2003/role/reference">
1262 <!-- Locators point to concepts in the taxonomy -->
1263 <link:loc xlink:type="locator" xlink:label="loc_assets" xlink:href="my-taxonomy.xsd#Assets" />
1264 <link:loc xlink:type="locator" xlink:label="loc_cash" xlink:href="my-taxonomy.xsd#Cash" />
1265 <!-- Arcs connect locators to resources -->
1266 <link:referenceArc xlink:type="arc"
1267 xlink:from="loc_assets"
1268 xlink:to="ref_assets"
1269 order="1" />
1270 <link:referenceArc xlink:type="arc"
1271 xlink:from="loc_cash"
1272 xlink:to="ref_cash"
1273 order="2" />
1274 <!-- Resources provide textual references -->
1275 <link:reference xlink:type="resource"
1276 xlink:label="ref_assets"
1277 xlink:role="http://www.xbrl.org/2003/role/statementRef">
1278 <link:content>Test content 1</link:content>
1279 </link:reference>
1280 <link:reference xlink:type="resource"
1281 xlink:label="ref_cash"
1282 xlink:role="http://www.xbrl.org/2003/role/statementRef">
1283 <link:content>Test content 2</link:content>
1284 </link:reference>
1285 </link:referenceLink>
1286 </link:linkbase>"#;
1287 let mut parser = LinkbaseParser::from_reader(xml.as_bytes());
1288 let mut linkbases = RawLinkbases::default();
1289 parser.parse_linkbase(&mut linkbases).unwrap();
1290
1291 assert_eq!(linkbases.presentation_links.len(), 0);
1292 assert_eq!(linkbases.calculation_links.len(), 0);
1293 assert_eq!(linkbases.definition_links.len(), 0);
1294 assert_eq!(linkbases.label_links.len(), 0);
1295 assert_eq!(linkbases.reference_links.len(), 1);
1296 let reference_link = &linkbases.reference_links[0];
1297 assert_eq!(
1298 reference_link,
1299 &ReferenceLink {
1300 role: "http://www.xbrl.org/2003/role/reference".to_string(),
1301 locators: vec![
1302 Locator {
1303 label: "loc_assets".to_string(),
1304 href: "my-taxonomy.xsd#Assets".to_string(),
1305 },
1306 Locator {
1307 label: "loc_cash".to_string(),
1308 href: "my-taxonomy.xsd#Cash".to_string(),
1309 },
1310 ],
1311 arcs: vec![
1312 RawReferenceArc {
1313 from: "loc_assets".to_string(),
1314 to: "ref_assets".to_string(),
1315 },
1316 RawReferenceArc {
1317 from: "loc_cash".to_string(),
1318 to: "ref_cash".to_string(),
1319 },
1320 ],
1321 references: vec![
1322 ReferenceResource {
1323 label: "ref_assets".to_string(),
1324 role: Some("http://www.xbrl.org/2003/role/statementRef".to_string()),
1325 },
1326 ReferenceResource {
1327 label: "ref_cash".to_string(),
1328 role: Some("http://www.xbrl.org/2003/role/statementRef".to_string()),
1329 },
1330 ],
1331 }
1332 );
1333 }
1334}