asciidoc_parser/document/
header.rs1use std::slice::Iter;
2
3use crate::{
4 HasSpan, Parser, Span,
5 content::{Content, SubstitutionGroup},
6 document::{Attribute, Author, AuthorLine, RevisionLine},
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 author_line: Option<AuthorLine<'src>>,
20 revision_line: Option<RevisionLine<'src>>,
21 comments: Vec<Span<'src>>,
22 source: Span<'src>,
23}
24
25impl<'src> Header<'src> {
26 pub(crate) fn parse(
27 mut source: Span<'src>,
28 parser: &mut Parser,
29 ) -> MatchAndWarnings<'src, MatchedItem<'src, Self>> {
30 let original_source = source.discard_empty_lines();
31
32 let mut title_source: Option<Span<'src>> = None;
33 let mut title: Option<String> = None;
34 let mut attributes: Vec<Attribute> = vec![];
35 let mut author_line: Option<AuthorLine<'src>> = None;
36 let mut revision_line: Option<RevisionLine<'src>> = None;
37 let mut comments: Vec<Span<'src>> = vec![];
38 let mut warnings: Vec<Warning<'src>> = vec![];
39
40 while !source.is_empty() {
42 let line_mi = source.take_normalized_line();
43 let line = line_mi.item;
44
45 if line.is_empty() {
47 if title.is_some() {
48 break;
49 }
50 source = line_mi.after;
51 } else if line.starts_with("//") && !line.starts_with("///") {
52 comments.push(line);
53 source = line_mi.after;
54 } else if line.starts_with(':')
55 && let Some(attr) = Attribute::parse(source, parser)
56 {
57 if attr.item.name().data().eq_ignore_ascii_case("author")
60 && let Some(raw_value) = attr.item.raw_value()
61 && let Some(author) = Author::parse(raw_value.data(), parser)
62 {
63 parser.set_attribute_by_value_from_header("firstname", author.firstname());
65 if let Some(middlename) = author.middlename() {
66 parser.set_attribute_by_value_from_header("middlename", middlename);
67 }
68 if let Some(lastname) = author.lastname() {
69 parser.set_attribute_by_value_from_header("lastname", lastname);
70 }
71 parser.set_attribute_by_value_from_header("authorinitials", author.initials());
72 if let Some(email) = author.email() {
73 parser.set_attribute_by_value_from_header("email", email);
74 }
75 }
76
77 parser.set_attribute_from_header(&attr.item, &mut warnings);
78 attributes.push(attr.item);
79 source = attr.after;
80 } else if title.is_none() && line.starts_with("= ") {
81 let title_span = line.discard(2).discard_whitespace();
82 let title_str = apply_header_subs(title_span.data(), parser);
83
84 parser.set_attribute_by_value_from_header("doctitle", &title_str);
85
86 title = Some(title_str);
87 title_source = Some(title_span);
88 source = line_mi.after;
89 } else if title.is_some() && author_line.is_none() {
90 author_line = Some(AuthorLine::parse(line, parser));
91 source = line_mi.after;
92 } else if title.is_some() && author_line.is_some() && revision_line.is_none() {
93 revision_line = Some(RevisionLine::parse(line, parser));
94 source = line_mi.after;
95 } else {
96 if title.is_some() {
97 warnings.push(Warning {
98 source: line,
99 warning: WarningType::DocumentHeaderNotTerminated,
100 });
101 }
102 break;
103 }
104 }
105
106 let after = source.discard_empty_lines();
107 let source = original_source.trim_remainder(source);
108
109 MatchAndWarnings {
110 item: MatchedItem {
111 item: Self {
112 title_source,
113 title,
114 attributes,
115 author_line,
116 revision_line,
117 comments,
118 source: source.trim_trailing_whitespace(),
119 },
120 after,
121 },
122 warnings,
123 }
124 }
125
126 pub fn title_source(&'src self) -> Option<Span<'src>> {
128 self.title_source
129 }
130
131 pub fn title(&self) -> Option<&str> {
134 self.title.as_deref()
135 }
136
137 pub fn attributes(&'src self) -> Iter<'src, Attribute<'src>> {
139 self.attributes.iter()
140 }
141
142 pub fn author_line(&self) -> Option<&AuthorLine<'src>> {
144 self.author_line.as_ref()
145 }
146
147 pub fn revision_line(&self) -> Option<&RevisionLine<'src>> {
149 self.revision_line.as_ref()
150 }
151
152 pub fn comments(&'src self) -> Iter<'src, Span<'src>> {
154 self.comments.iter()
155 }
156}
157
158impl<'src> HasSpan<'src> for Header<'src> {
159 fn span(&self) -> Span<'src> {
160 self.source
161 }
162}
163
164fn apply_header_subs(source: &str, parser: &Parser) -> String {
165 let span = Span::new(source);
166
167 let mut content = Content::from(span);
168 SubstitutionGroup::Header.apply(&mut content, parser, None);
169
170 content.rendered().to_string()
171}