1use std::{marker::PhantomData, slice::Iter};
4
5use self_cell::self_cell;
6
7use crate::{
8 Parser, Span,
9 attributes::Attrlist,
10 blocks::{Block, ContentModel, IsBlock, Preamble, parse_utils::parse_blocks_until},
11 document::{Catalog, Header},
12 internal::debug::DebugSliceReference,
13 parser::SourceMap,
14 strings::CowStr,
15 warnings::Warning,
16};
17
18#[derive(Eq, PartialEq)]
31pub struct Document<'src> {
32 internal: Internal,
33 _phantom: PhantomData<&'src ()>,
34}
35
36#[derive(Debug, Eq, PartialEq)]
39struct InternalDependent<'src> {
40 header: Header<'src>,
41 blocks: Vec<Block<'src>>,
42 source: Span<'src>,
43 warnings: Vec<Warning<'src>>,
44 source_map: SourceMap,
45 catalog: Catalog,
46}
47
48self_cell! {
49 struct Internal {
51 owner: String,
52 #[covariant]
53 dependent: InternalDependent,
54 }
55 impl {Debug, Eq, PartialEq}
56}
57
58impl<'src> Document<'src> {
59 pub(crate) fn parse(source: &str, source_map: SourceMap, parser: &mut Parser) -> Self {
60 let owned_source = source.to_string();
61
62 let internal = Internal::new(owned_source, |owned_src| {
63 let source = Span::new(owned_src);
64
65 let mi = Header::parse(source, parser);
66 let after_header = mi.item.after;
67
68 parser.sectnumlevels = parser
69 .attribute_value("sectnumlevels")
70 .as_maybe_str()
71 .and_then(|s| s.parse::<usize>().ok())
72 .unwrap_or(3);
73
74 let header = mi.item.item;
75 let mut warnings = mi.warnings;
76
77 let mut maw_blocks = parse_blocks_until(after_header, |_| false, parser);
78
79 if !maw_blocks.warnings.is_empty() {
80 warnings.append(&mut maw_blocks.warnings);
81 }
82
83 let mut blocks = maw_blocks.item.item;
84 let mut has_content_blocks = false;
85 let mut preamble_split_index: Option<usize> = None;
86
87 for (index, block) in blocks.iter().enumerate() {
88 match block {
89 Block::DocumentAttribute(_) => (),
90 Block::Section(_) => {
91 if has_content_blocks {
92 preamble_split_index = Some(index);
93 }
94 break;
95 }
96 _ => {
97 has_content_blocks = true;
98 }
99 }
100 }
101
102 if let Some(index) = preamble_split_index {
103 let mut section_blocks = blocks.split_off(index);
104
105 let preamble = Preamble::from_blocks(blocks, after_header);
106
107 section_blocks.insert(0, Block::Preamble(preamble));
108 blocks = section_blocks;
109 }
110
111 InternalDependent {
112 header,
113 blocks,
114 source: source.trim_trailing_whitespace(),
115 warnings,
116 source_map,
117 catalog: parser.take_catalog(),
118 }
119 });
120
121 Self {
122 internal,
123 _phantom: PhantomData,
124 }
125 }
126
127 pub fn header(&self) -> &Header<'_> {
129 &self.internal.borrow_dependent().header
130 }
131
132 pub fn warnings(&self) -> Iter<'_, Warning<'_>> {
134 self.internal.borrow_dependent().warnings.iter()
135 }
136
137 pub fn span(&self) -> Span<'_> {
139 self.internal.borrow_dependent().source
140 }
141
142 pub fn source_map(&self) -> &SourceMap {
144 &self.internal.borrow_dependent().source_map
145 }
146
147 pub fn catalog(&self) -> &Catalog {
149 &self.internal.borrow_dependent().catalog
150 }
151}
152
153impl<'src> IsBlock<'src> for Document<'src> {
154 fn content_model(&self) -> ContentModel {
155 ContentModel::Compound
156 }
157
158 fn raw_context(&self) -> CowStr<'src> {
159 "document".into()
160 }
161
162 fn nested_blocks(&'src self) -> Iter<'src, Block<'src>> {
163 self.internal.borrow_dependent().blocks.iter()
164 }
165
166 fn title_source(&'src self) -> Option<Span<'src>> {
167 None
169 }
170
171 fn title(&self) -> Option<&str> {
172 None
174 }
175
176 fn anchor(&'src self) -> Option<Span<'src>> {
177 None
178 }
179
180 fn anchor_reftext(&'src self) -> Option<Span<'src>> {
181 None
182 }
183
184 fn attrlist(&'src self) -> Option<&'src Attrlist<'src>> {
185 None
187 }
188}
189
190impl std::fmt::Debug for Document<'_> {
191 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
192 let dependent = self.internal.borrow_dependent();
193 f.debug_struct("Document")
194 .field("header", &dependent.header)
195 .field("blocks", &DebugSliceReference(&dependent.blocks))
196 .field("source", &dependent.source)
197 .field("warnings", &DebugSliceReference(&dependent.warnings))
198 .field("source_map", &dependent.source_map)
199 .field("catalog", &dependent.catalog)
200 .finish()
201 }
202}
203
204#[cfg(test)]
205mod tests {
206 #![allow(clippy::unwrap_used)]
207
208 use std::{collections::HashMap, ops::Deref};
209
210 use pretty_assertions_sorted::assert_eq;
211
212 use crate::{
213 Parser,
214 blocks::{ContentModel, IsBlock, MediaType, SimpleBlockStyle},
215 content::SubstitutionGroup,
216 document::RefType,
217 tests::prelude::*,
218 warnings::WarningType,
219 };
220
221 #[test]
222 fn empty_source() {
223 let doc = Parser::default().parse("");
224
225 assert_eq!(doc.content_model(), ContentModel::Compound);
226 assert_eq!(doc.raw_context().deref(), "document");
227 assert_eq!(doc.resolved_context().deref(), "document");
228 assert!(doc.declared_style().is_none());
229 assert!(doc.id().is_none());
230 assert!(doc.roles().is_empty());
231 assert!(doc.title_source().is_none());
232 assert!(doc.title().is_none());
233 assert!(doc.anchor().is_none());
234 assert!(doc.anchor_reftext().is_none());
235 assert!(doc.attrlist().is_none());
236 assert_eq!(doc.substitution_group(), SubstitutionGroup::Normal);
237
238 assert_eq!(
239 doc,
240 Document {
241 header: Header {
242 title_source: None,
243 title: None,
244 attributes: &[],
245 author_line: None,
246 revision_line: None,
247 comments: &[],
248 source: Span {
249 data: "",
250 line: 1,
251 col: 1,
252 offset: 0
253 },
254 },
255 source: Span {
256 data: "",
257 line: 1,
258 col: 1,
259 offset: 0
260 },
261 blocks: &[],
262 warnings: &[],
263 source_map: SourceMap(&[]),
264 catalog: Catalog::default(),
265 }
266 );
267 }
268
269 #[test]
270 fn only_spaces() {
271 assert_eq!(
272 Parser::default().parse(" "),
273 Document {
274 header: Header {
275 title_source: None,
276 title: None,
277 attributes: &[],
278 author_line: None,
279 revision_line: None,
280 comments: &[],
281 source: Span {
282 data: "",
283 line: 1,
284 col: 5,
285 offset: 4
286 },
287 },
288 source: Span {
289 data: "",
290 line: 1,
291 col: 1,
292 offset: 0
293 },
294 blocks: &[],
295 warnings: &[],
296 source_map: SourceMap(&[]),
297 catalog: Catalog::default(),
298 }
299 );
300 }
301
302 #[test]
303 fn one_simple_block() {
304 let doc = Parser::default().parse("abc");
305 assert_eq!(
306 doc,
307 Document {
308 header: Header {
309 title_source: None,
310 title: None,
311 attributes: &[],
312 author_line: None,
313 revision_line: None,
314 comments: &[],
315 source: Span {
316 data: "",
317 line: 1,
318 col: 1,
319 offset: 0
320 },
321 },
322 source: Span {
323 data: "abc",
324 line: 1,
325 col: 1,
326 offset: 0
327 },
328 blocks: &[Block::Simple(SimpleBlock {
329 content: Content {
330 original: Span {
331 data: "abc",
332 line: 1,
333 col: 1,
334 offset: 0,
335 },
336 rendered: "abc",
337 },
338 source: Span {
339 data: "abc",
340 line: 1,
341 col: 1,
342 offset: 0,
343 },
344 style: SimpleBlockStyle::Paragraph,
345 title_source: None,
346 title: None,
347 anchor: None,
348 anchor_reftext: None,
349 attrlist: None,
350 })],
351 warnings: &[],
352 source_map: SourceMap(&[]),
353 catalog: Catalog::default(),
354 }
355 );
356
357 assert!(doc.anchor().is_none());
358 assert!(doc.anchor_reftext().is_none());
359 }
360
361 #[test]
362 fn two_simple_blocks() {
363 assert_eq!(
364 Parser::default().parse("abc\n\ndef"),
365 Document {
366 header: Header {
367 title_source: None,
368 title: None,
369 attributes: &[],
370 author_line: None,
371 revision_line: None,
372 comments: &[],
373 source: Span {
374 data: "",
375 line: 1,
376 col: 1,
377 offset: 0
378 },
379 },
380 source: Span {
381 data: "abc\n\ndef",
382 line: 1,
383 col: 1,
384 offset: 0
385 },
386 blocks: &[
387 Block::Simple(SimpleBlock {
388 content: Content {
389 original: Span {
390 data: "abc",
391 line: 1,
392 col: 1,
393 offset: 0,
394 },
395 rendered: "abc",
396 },
397 source: Span {
398 data: "abc",
399 line: 1,
400 col: 1,
401 offset: 0,
402 },
403 style: SimpleBlockStyle::Paragraph,
404 title_source: None,
405 title: None,
406 anchor: None,
407 anchor_reftext: None,
408 attrlist: None,
409 }),
410 Block::Simple(SimpleBlock {
411 content: Content {
412 original: Span {
413 data: "def",
414 line: 3,
415 col: 1,
416 offset: 5,
417 },
418 rendered: "def",
419 },
420 source: Span {
421 data: "def",
422 line: 3,
423 col: 1,
424 offset: 5,
425 },
426 style: SimpleBlockStyle::Paragraph,
427 title_source: None,
428 title: None,
429 anchor: None,
430 anchor_reftext: None,
431 attrlist: None,
432 })
433 ],
434 warnings: &[],
435 source_map: SourceMap(&[]),
436 catalog: Catalog::default(),
437 }
438 );
439 }
440
441 #[test]
442 fn two_blocks_and_title() {
443 assert_eq!(
444 Parser::default().parse("= Example Title\n\nabc\n\ndef"),
445 Document {
446 header: Header {
447 title_source: Some(Span {
448 data: "Example Title",
449 line: 1,
450 col: 3,
451 offset: 2,
452 }),
453 title: Some("Example Title"),
454 attributes: &[],
455 author_line: None,
456 revision_line: None,
457 comments: &[],
458 source: Span {
459 data: "= Example Title",
460 line: 1,
461 col: 1,
462 offset: 0,
463 }
464 },
465 blocks: &[
466 Block::Simple(SimpleBlock {
467 content: Content {
468 original: Span {
469 data: "abc",
470 line: 3,
471 col: 1,
472 offset: 17,
473 },
474 rendered: "abc",
475 },
476 source: Span {
477 data: "abc",
478 line: 3,
479 col: 1,
480 offset: 17,
481 },
482 style: SimpleBlockStyle::Paragraph,
483 title_source: None,
484 title: None,
485 anchor: None,
486 anchor_reftext: None,
487 attrlist: None,
488 }),
489 Block::Simple(SimpleBlock {
490 content: Content {
491 original: Span {
492 data: "def",
493 line: 5,
494 col: 1,
495 offset: 22,
496 },
497 rendered: "def",
498 },
499 source: Span {
500 data: "def",
501 line: 5,
502 col: 1,
503 offset: 22,
504 },
505 style: SimpleBlockStyle::Paragraph,
506 title_source: None,
507 title: None,
508 anchor: None,
509 anchor_reftext: None,
510 attrlist: None,
511 })
512 ],
513 source: Span {
514 data: "= Example Title\n\nabc\n\ndef",
515 line: 1,
516 col: 1,
517 offset: 0
518 },
519 warnings: &[],
520 source_map: SourceMap(&[]),
521 catalog: Catalog::default(),
522 }
523 );
524 }
525
526 #[test]
527 fn blank_lines_before_header() {
528 let doc = Parser::default().parse("\n\n= Example Title\n\nabc\n\ndef");
529
530 assert_eq!(
531 doc,
532 Document {
533 header: Header {
534 title_source: Some(Span {
535 data: "Example Title",
536 line: 3,
537 col: 3,
538 offset: 4,
539 },),
540 title: Some("Example Title",),
541 attributes: &[],
542 author_line: None,
543 revision_line: None,
544 comments: &[],
545 source: Span {
546 data: "= Example Title",
547 line: 3,
548 col: 1,
549 offset: 2,
550 },
551 },
552 blocks: &[
553 Block::Simple(SimpleBlock {
554 content: Content {
555 original: Span {
556 data: "abc",
557 line: 5,
558 col: 1,
559 offset: 19,
560 },
561 rendered: "abc",
562 },
563 source: Span {
564 data: "abc",
565 line: 5,
566 col: 1,
567 offset: 19,
568 },
569 style: SimpleBlockStyle::Paragraph,
570 title_source: None,
571 title: None,
572 anchor: None,
573 anchor_reftext: None,
574 attrlist: None,
575 },),
576 Block::Simple(SimpleBlock {
577 content: Content {
578 original: Span {
579 data: "def",
580 line: 7,
581 col: 1,
582 offset: 24,
583 },
584 rendered: "def",
585 },
586 source: Span {
587 data: "def",
588 line: 7,
589 col: 1,
590 offset: 24,
591 },
592 style: SimpleBlockStyle::Paragraph,
593 title_source: None,
594 title: None,
595 anchor: None,
596 anchor_reftext: None,
597 attrlist: None,
598 },),
599 ],
600 source: Span {
601 data: "\n\n= Example Title\n\nabc\n\ndef",
602 line: 1,
603 col: 1,
604 offset: 0,
605 },
606 warnings: &[],
607 source_map: SourceMap(&[]),
608 catalog: Catalog::default(),
609 }
610 );
611 }
612
613 #[test]
614 fn blank_lines_and_comment_before_header() {
615 let doc =
616 Parser::default().parse("\n// ignore this comment\n= Example Title\n\nabc\n\ndef");
617
618 assert_eq!(
619 doc,
620 Document {
621 header: Header {
622 title_source: Some(Span {
623 data: "Example Title",
624 line: 3,
625 col: 3,
626 offset: 26,
627 },),
628 title: Some("Example Title",),
629 attributes: &[],
630 author_line: None,
631 revision_line: None,
632 comments: &[Span {
633 data: "// ignore this comment",
634 line: 2,
635 col: 1,
636 offset: 1,
637 },],
638 source: Span {
639 data: "// ignore this comment\n= Example Title",
640 line: 2,
641 col: 1,
642 offset: 1,
643 },
644 },
645 blocks: &[
646 Block::Simple(SimpleBlock {
647 content: Content {
648 original: Span {
649 data: "abc",
650 line: 5,
651 col: 1,
652 offset: 41,
653 },
654 rendered: "abc",
655 },
656 source: Span {
657 data: "abc",
658 line: 5,
659 col: 1,
660 offset: 41,
661 },
662 style: SimpleBlockStyle::Paragraph,
663 title_source: None,
664 title: None,
665 anchor: None,
666 anchor_reftext: None,
667 attrlist: None,
668 },),
669 Block::Simple(SimpleBlock {
670 content: Content {
671 original: Span {
672 data: "def",
673 line: 7,
674 col: 1,
675 offset: 46,
676 },
677 rendered: "def",
678 },
679 source: Span {
680 data: "def",
681 line: 7,
682 col: 1,
683 offset: 46,
684 },
685 style: SimpleBlockStyle::Paragraph,
686 title_source: None,
687 title: None,
688 anchor: None,
689 anchor_reftext: None,
690 attrlist: None,
691 },),
692 ],
693 source: Span {
694 data: "\n// ignore this comment\n= Example Title\n\nabc\n\ndef",
695 line: 1,
696 col: 1,
697 offset: 0,
698 },
699 warnings: &[],
700 source_map: SourceMap(&[]),
701 catalog: Catalog::default(),
702 }
703 );
704 }
705
706 #[test]
707 fn extra_space_before_title() {
708 assert_eq!(
709 Parser::default().parse("= Example Title\n\nabc"),
710 Document {
711 header: Header {
712 title_source: Some(Span {
713 data: "Example Title",
714 line: 1,
715 col: 5,
716 offset: 4,
717 }),
718 title: Some("Example Title"),
719 attributes: &[],
720 author_line: None,
721 revision_line: None,
722 comments: &[],
723 source: Span {
724 data: "= Example Title",
725 line: 1,
726 col: 1,
727 offset: 0,
728 }
729 },
730 blocks: &[Block::Simple(SimpleBlock {
731 content: Content {
732 original: Span {
733 data: "abc",
734 line: 3,
735 col: 1,
736 offset: 19,
737 },
738 rendered: "abc",
739 },
740 source: Span {
741 data: "abc",
742 line: 3,
743 col: 1,
744 offset: 19,
745 },
746 style: SimpleBlockStyle::Paragraph,
747 title_source: None,
748 title: None,
749 anchor: None,
750 anchor_reftext: None,
751 attrlist: None,
752 })],
753 source: Span {
754 data: "= Example Title\n\nabc",
755 line: 1,
756 col: 1,
757 offset: 0
758 },
759 warnings: &[],
760 source_map: SourceMap(&[]),
761 catalog: Catalog::default(),
762 }
763 );
764 }
765
766 #[test]
767 fn err_bad_header() {
768 assert_eq!(
769 Parser::default().parse(
770 "= Title\nJane Smith <jane@example.com>\nv1, 2025-09-28\nnot an attribute\n"
771 ),
772 Document {
773 header: Header {
774 title_source: Some(Span {
775 data: "Title",
776 line: 1,
777 col: 3,
778 offset: 2,
779 }),
780 title: Some("Title"),
781 attributes: &[],
782 author_line: Some(AuthorLine {
783 authors: &[Author {
784 name: "Jane Smith",
785 firstname: "Jane",
786 middlename: None,
787 lastname: Some("Smith"),
788 email: Some("jane@example.com"),
789 }],
790 source: Span {
791 data: "Jane Smith <jane@example.com>",
792 line: 2,
793 col: 1,
794 offset: 8,
795 },
796 }),
797 revision_line: Some(RevisionLine {
798 revnumber: Some("1",),
799 revdate: "2025-09-28",
800 revremark: None,
801 source: Span {
802 data: "v1, 2025-09-28",
803 line: 3,
804 col: 1,
805 offset: 38,
806 },
807 },),
808 comments: &[],
809 source: Span {
810 data: "= Title\nJane Smith <jane@example.com>\nv1, 2025-09-28",
811 line: 1,
812 col: 1,
813 offset: 0,
814 }
815 },
816 blocks: &[Block::Simple(SimpleBlock {
817 content: Content {
818 original: Span {
819 data: "not an attribute",
820 line: 4,
821 col: 1,
822 offset: 53,
823 },
824 rendered: "not an attribute",
825 },
826 source: Span {
827 data: "not an attribute",
828 line: 4,
829 col: 1,
830 offset: 53,
831 },
832 style: SimpleBlockStyle::Paragraph,
833 title_source: None,
834 title: None,
835 anchor: None,
836 anchor_reftext: None,
837 attrlist: None,
838 })],
839 source: Span {
840 data: "= Title\nJane Smith <jane@example.com>\nv1, 2025-09-28\nnot an attribute",
841 line: 1,
842 col: 1,
843 offset: 0
844 },
845 warnings: &[Warning {
846 source: Span {
847 data: "not an attribute",
848 line: 4,
849 col: 1,
850 offset: 53,
851 },
852 warning: WarningType::DocumentHeaderNotTerminated,
853 },],
854 source_map: SourceMap(&[]),
855 catalog: Catalog::default(),
856 }
857 );
858 }
859
860 #[test]
861 fn err_bad_header_and_bad_macro() {
862 let doc = Parser::default().parse("= Title\nJane Smith <jane@example.com>\nv1, 2025-09-28\nnot an attribute\n\n== Section Title\n\nimage::bar[alt=Sunset,width=300,,height=400]");
863
864 assert_eq!(
865 Document {
866 header: Header {
867 title_source: Some(Span {
868 data: "Title",
869 line: 1,
870 col: 3,
871 offset: 2,
872 }),
873 title: Some("Title"),
874 attributes: &[],
875 author_line: Some(AuthorLine {
876 authors: &[Author {
877 name: "Jane Smith",
878 firstname: "Jane",
879 middlename: None,
880 lastname: Some("Smith"),
881 email: Some("jane@example.com"),
882 }],
883 source: Span {
884 data: "Jane Smith <jane@example.com>",
885 line: 2,
886 col: 1,
887 offset: 8,
888 },
889 }),
890 revision_line: Some(RevisionLine {
891 revnumber: Some("1"),
892 revdate: "2025-09-28",
893 revremark: None,
894 source: Span {
895 data: "v1, 2025-09-28",
896 line: 3,
897 col: 1,
898 offset: 38,
899 },
900 },),
901 comments: &[],
902 source: Span {
903 data: "= Title\nJane Smith <jane@example.com>\nv1, 2025-09-28",
904 line: 1,
905 col: 1,
906 offset: 0,
907 }
908 },
909 blocks: &[
910 Block::Preamble(Preamble {
911 blocks: &[Block::Simple(SimpleBlock {
912 content: Content {
913 original: Span {
914 data: "not an attribute",
915 line: 4,
916 col: 1,
917 offset: 53,
918 },
919 rendered: "not an attribute",
920 },
921 source: Span {
922 data: "not an attribute",
923 line: 4,
924 col: 1,
925 offset: 53,
926 },
927 style: SimpleBlockStyle::Paragraph,
928 title_source: None,
929 title: None,
930 anchor: None,
931 anchor_reftext: None,
932 attrlist: None,
933 },),],
934 source: Span {
935 data: "not an attribute",
936 line: 4,
937 col: 1,
938 offset: 53,
939 },
940 },),
941 Block::Section(SectionBlock {
942 level: 1,
943 section_title: Content {
944 original: Span {
945 data: "Section Title",
946 line: 6,
947 col: 4,
948 offset: 74,
949 },
950 rendered: "Section Title",
951 },
952 blocks: &[Block::Media(MediaBlock {
953 type_: MediaType::Image,
954 target: Span {
955 data: "bar",
956 line: 8,
957 col: 8,
958 offset: 96,
959 },
960 macro_attrlist: Attrlist {
961 attributes: &[
962 ElementAttribute {
963 name: Some("alt"),
964 shorthand_items: &[],
965 value: "Sunset"
966 },
967 ElementAttribute {
968 name: Some("width"),
969 shorthand_items: &[],
970 value: "300"
971 },
972 ElementAttribute {
973 name: Some("height"),
974 shorthand_items: &[],
975 value: "400"
976 },
977 ],
978 anchor: None,
979 source: Span {
980 data: "alt=Sunset,width=300,,height=400",
981 line: 8,
982 col: 12,
983 offset: 100,
984 },
985 },
986 source: Span {
987 data: "image::bar[alt=Sunset,width=300,,height=400]",
988 line: 8,
989 col: 1,
990 offset: 89,
991 },
992 title_source: None,
993 title: None,
994 anchor: None,
995 anchor_reftext: None,
996 attrlist: None,
997 },),],
998 source: Span {
999 data: "== Section Title\n\nimage::bar[alt=Sunset,width=300,,height=400]",
1000 line: 6,
1001 col: 1,
1002 offset: 71,
1003 },
1004 title_source: None,
1005 title: None,
1006 anchor: None,
1007 anchor_reftext: None,
1008 attrlist: None,
1009 section_type: SectionType::Normal,
1010 section_id: Some("_section_title"),
1011 section_number: None,
1012 },)
1013 ],
1014 source: Span {
1015 data: "= Title\nJane Smith <jane@example.com>\nv1, 2025-09-28\nnot an attribute\n\n== Section Title\n\nimage::bar[alt=Sunset,width=300,,height=400]",
1016 line: 1,
1017 col: 1,
1018 offset: 0
1019 },
1020 warnings: &[
1021 Warning {
1022 source: Span {
1023 data: "not an attribute",
1024 line: 4,
1025 col: 1,
1026 offset: 53,
1027 },
1028 warning: WarningType::DocumentHeaderNotTerminated,
1029 },
1030 Warning {
1031 source: Span {
1032 data: "alt=Sunset,width=300,,height=400",
1033 line: 8,
1034 col: 12,
1035 offset: 100,
1036 },
1037 warning: WarningType::EmptyAttributeValue,
1038 },
1039 ],
1040 source_map: SourceMap(&[]),
1041 catalog: Catalog {
1042 refs: HashMap::from([(
1043 "_section_title",
1044 RefEntry {
1045 id: "_section_title",
1046 reftext: Some("Section Title",),
1047 ref_type: RefType::Section,
1048 }
1049 ),]),
1050 reftext_to_id: HashMap::from([("Section Title", "_section_title"),]),
1051 }
1052 },
1053 doc
1054 );
1055 }
1056
1057 #[test]
1058 fn impl_debug() {
1059 let doc = Parser::default().parse("= Example Title\n\nabc\n\ndef");
1060
1061 assert_eq!(
1062 format!("{doc:#?}"),
1063 r#"Document {
1064 header: Header {
1065 title_source: Some(
1066 Span {
1067 data: "Example Title",
1068 line: 1,
1069 col: 3,
1070 offset: 2,
1071 },
1072 ),
1073 title: Some(
1074 "Example Title",
1075 ),
1076 attributes: &[],
1077 author_line: None,
1078 revision_line: None,
1079 comments: &[],
1080 source: Span {
1081 data: "= Example Title",
1082 line: 1,
1083 col: 1,
1084 offset: 0,
1085 },
1086 },
1087 blocks: &[
1088 Block::Simple(
1089 SimpleBlock {
1090 content: Content {
1091 original: Span {
1092 data: "abc",
1093 line: 3,
1094 col: 1,
1095 offset: 17,
1096 },
1097 rendered: "abc",
1098 },
1099 source: Span {
1100 data: "abc",
1101 line: 3,
1102 col: 1,
1103 offset: 17,
1104 },
1105 style: SimpleBlockStyle::Paragraph,
1106 title_source: None,
1107 title: None,
1108 anchor: None,
1109 anchor_reftext: None,
1110 attrlist: None,
1111 },
1112 ),
1113 Block::Simple(
1114 SimpleBlock {
1115 content: Content {
1116 original: Span {
1117 data: "def",
1118 line: 5,
1119 col: 1,
1120 offset: 22,
1121 },
1122 rendered: "def",
1123 },
1124 source: Span {
1125 data: "def",
1126 line: 5,
1127 col: 1,
1128 offset: 22,
1129 },
1130 style: SimpleBlockStyle::Paragraph,
1131 title_source: None,
1132 title: None,
1133 anchor: None,
1134 anchor_reftext: None,
1135 attrlist: None,
1136 },
1137 ),
1138 ],
1139 source: Span {
1140 data: "= Example Title\n\nabc\n\ndef",
1141 line: 1,
1142 col: 1,
1143 offset: 0,
1144 },
1145 warnings: &[],
1146 source_map: SourceMap(&[]),
1147 catalog: Catalog {
1148 refs: HashMap::from([]),
1149 reftext_to_id: HashMap::from([]),
1150 },
1151}"#
1152 );
1153 }
1154}