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