asciidoc_parser/document/
header.rs1use std::slice::Iter;
2
3use crate::{
4 HasSpan, Parser, Span,
5 content::{Content, SubstitutionGroup},
6 document::Attribute,
7 span::MatchedItem,
8 warnings::{MatchAndWarnings, Warning, WarningType},
9};
10
11#[derive(Clone, Debug, Eq, PartialEq)]
15pub struct Header<'src> {
16 title_source: Option<Span<'src>>,
17 title: Option<String>,
18 attributes: Vec<Attribute<'src>>,
19 source: Span<'src>,
20}
21
22impl<'src> Header<'src> {
23 pub(crate) fn parse(
24 source: Span<'src>,
25 parser: &mut Parser,
26 ) -> MatchAndWarnings<'src, MatchedItem<'src, Self>> {
27 let original_src = source;
28
29 let mut attributes: Vec<Attribute> = vec![];
30 let mut warnings: Vec<Warning<'src>> = vec![];
31
32 let source = source.discard_empty_lines();
33
34 let (title_source, mut after) = if let Some(mi) = parse_title(source) {
35 (Some(mi.item), mi.after)
36 } else {
37 (None, source)
38 };
39
40 let title = title_source.map(|ref span| {
41 let mut content = Content::from(*span);
42 SubstitutionGroup::Header.apply(&mut content, parser, None);
43 content.rendered.into_string()
44 });
45
46 while let Some(attr) = Attribute::parse(after, parser) {
47 parser.set_attribute_from_header(&attr.item, &mut warnings);
48 attributes.push(attr.item);
49 after = attr.after;
50 }
51
52 let source = source.trim_remainder(after);
53
54 if title_source.is_none() && attributes.is_empty() {
56 return MatchAndWarnings {
57 item: MatchedItem {
58 item: Self {
59 title_source: None,
60 title: None,
61 attributes,
62 source: original_src.into_parse_result(0).item,
63 },
64 after,
65 },
66 warnings,
67 };
68 }
69
70 after = match after.take_empty_line() {
72 Some(mi) => mi.after.discard_empty_lines(),
73 None => {
74 warnings.push(Warning {
75 source: after.take_line().item,
76 warning: WarningType::DocumentHeaderNotTerminated,
77 });
78 after
79 }
80 };
81
82 MatchAndWarnings {
83 item: MatchedItem {
84 item: Self {
85 title_source,
86 title,
87 attributes,
88 source: source.trim_trailing_whitespace(),
89 },
90 after,
91 },
92 warnings,
93 }
94 }
95
96 pub fn title_source(&'src self) -> Option<Span<'src>> {
98 self.title_source
99 }
100
101 pub fn title(&self) -> Option<&str> {
104 self.title.as_deref()
105 }
106
107 pub fn attributes(&'src self) -> Iter<'src, Attribute<'src>> {
109 self.attributes.iter()
110 }
111}
112
113impl<'src> HasSpan<'src> for Header<'src> {
114 fn span(&self) -> Span<'src> {
115 self.source
116 }
117}
118
119fn parse_title(source: Span<'_>) -> Option<MatchedItem<'_, Span<'_>>> {
120 let line = source.take_non_empty_line()?;
121 let equal = line.item.take_prefix("=")?;
122 let ws = equal.after.take_required_whitespace()?;
123
124 Some(MatchedItem {
125 item: ws.after,
126 after: line.after,
127 })
128}