1use anyhow::{Error, bail};
2use pest::iterators::Pair;
3
4use document_tree::{
5 Element, ExtraAttributes, HasChildren, attribute_types as at, element_categories as c,
6 elements as e, extra_attributes as a,
7};
8use uuid::Uuid;
9
10use super::{inline::convert_inlines, whitespace_normalize_name};
11use crate::{pair_ext_parse::PairExt, pest_rst::Rule};
12
13#[derive(PartialEq)]
14pub(super) enum TitleKind {
15 Double(char),
16 Single(char),
17}
18
19pub(super) enum TitleOrSsubel {
20 Title(e::Title, TitleKind),
21 Ssubel(c::StructuralSubElement),
22}
23
24pub(super) fn convert_ssubel(pair: Pair<Rule>) -> Result<Option<TitleOrSsubel>, Error> {
25 use self::TitleOrSsubel::{Ssubel, Title};
26 Ok(Some(match pair.as_rule() {
27 Rule::title => {
28 let (t, k) = convert_title(pair)?;
29 Title(t, k)
30 }
31 Rule::EOI => return Ok(None),
33 _ => Ssubel(convert_substructure(pair)?.into()),
34 }))
35}
36
37fn convert_substructure(pair: Pair<Rule>) -> Result<c::SubStructure, Error> {
38 #[allow(clippy::match_single_binding)]
39 Ok(match pair.as_rule() {
40 _ => convert_body_elem(pair)?.into(),
43 })
44}
45
46fn convert_body_elem(pair: Pair<Rule>) -> Result<c::BodyElement, Error> {
47 Ok(match pair.as_rule() {
48 Rule::paragraph => convert_paragraph(pair)?.into(),
49 Rule::target => convert_target(pair)?.into(),
50 Rule::footnote => convert_footnote(pair)?.into(),
51 Rule::substitution_def => convert_substitution_def(pair)?.into(),
52 Rule::block_quote_directive => convert_block_quote_directive(pair)?.into(),
53 Rule::admonition_gen => convert_admonition_gen(pair),
54 Rule::image => convert_image::<e::Image>(pair)?.into(),
55 Rule::bullet_list => convert_bullet_list(pair)?.into(),
56 Rule::block_quote => convert_block_quote(pair)?.into(),
57 Rule::literal_block => convert_literal_block(pair).into(),
58 Rule::code_directive => convert_code_directive(pair).into(),
59 Rule::raw_directive => convert_raw_directive(pair).into(),
60 Rule::block_comment => convert_comment(pair).into(),
61 rule => unimplemented!("unhandled rule {:?}", rule),
62 })
63}
64
65fn convert_title(pair: Pair<Rule>) -> Result<(e::Title, TitleKind), Error> {
66 let mut title: Option<String> = None;
67 let mut title_inlines: Option<Vec<c::TextOrInlineElement>> = None;
68 let mut adornment_char: Option<char> = None;
69 let inner_pair = pair.into_inner().next().unwrap();
71 let kind = inner_pair.as_rule();
72 for p in inner_pair.into_inner() {
73 match p.as_rule() {
74 Rule::line => {
75 title = Some(p.as_str().to_owned());
76 title_inlines = Some(convert_inlines(p)?);
77 }
78 Rule::adornments => {
79 adornment_char = Some(p.as_str().chars().next().expect("Empty adornment?"));
80 }
81 rule => unimplemented!("Unexpected rule in title: {:?}", rule),
82 }
83 }
84 let mut elem = e::Title::with_children(title_inlines.expect("No text in title"));
87 if let Some(title) = title {
88 let slug = title.to_lowercase().replace('\n', "").replace(' ', "-");
90 elem.names_mut().push(at::NameToken(slug));
91 }
92 let title_kind = match kind {
93 Rule::title_double => TitleKind::Double(adornment_char.unwrap()),
94 Rule::title_single => TitleKind::Single(adornment_char.unwrap()),
95 _ => unreachable!(),
96 };
97 Ok((elem, title_kind))
98}
99
100fn convert_paragraph(pair: Pair<Rule>) -> Result<e::Paragraph, Error> {
101 Ok(e::Paragraph::with_children(convert_inlines(pair)?))
102}
103
104fn convert_target(pair: Pair<Rule>) -> Result<e::Target, Error> {
105 let mut elem = e::Target::default();
106 elem.extra_mut().anonymous = false;
107 for p in pair.into_inner() {
108 match p.as_rule() {
109 Rule::target_name_uq | Rule::target_name_qu => {
110 elem.ids_mut().push(p.as_str().into());
111 elem.names_mut().push(p.as_str().into());
112 }
113 Rule::link_target => elem.extra_mut().refuri = Some(p.parse()?),
115 rule => panic!("Unexpected rule in target: {rule:?}"),
116 }
117 }
118 Ok(elem)
119}
120
121fn convert_footnote(pair: Pair<Rule>) -> Result<e::Footnote, Error> {
122 let mut pairs = pair.into_inner();
123 let label = pairs.next().unwrap().as_str();
124 let mut children: Vec<c::SubFootnote> = vec![];
125 children.push(convert_paragraph(pairs.next().unwrap())?.into());
127 for p in pairs {
128 children.push(convert_body_elem(p)?.into());
129 }
130 let mut footnote = e::Footnote::with_children(children);
131 match label.chars().next().unwrap() {
132 '#' => {
133 if label.len() >= 2 {
134 footnote.names_mut().push(label[1..].into());
135 }
136 footnote.extra_mut().auto = Some(at::AutoFootnoteType::Number);
137 }
138 '*' => {
139 footnote.extra_mut().auto = Some(at::AutoFootnoteType::Symbol);
140 }
141 _ => {
142 footnote
143 .children_mut()
144 .insert(0, e::Label::with_children(vec![label.into()]).into());
145 }
146 }
147 footnote.ids_mut().push(at::ID(Uuid::new_v4().to_string()));
148 Ok(footnote)
149}
150
151fn convert_substitution_def(pair: Pair<Rule>) -> Result<e::SubstitutionDefinition, Error> {
152 let mut pairs = pair.into_inner();
153 let name = whitespace_normalize_name(pairs.next().unwrap().as_str()); let inner_pair = pairs.next().unwrap();
155 let inner: Vec<c::TextOrInlineElement> = match inner_pair.as_rule() {
156 Rule::replace => convert_replace(inner_pair)?,
157 Rule::image => vec![convert_image::<e::ImageInline>(inner_pair)?.into()],
158 rule => panic!("Unknown substitution rule {rule:?}"),
159 };
160 let mut subst_def = e::SubstitutionDefinition::with_children(inner);
161 subst_def.names_mut().push(at::NameToken(name));
162 Ok(subst_def)
163}
164
165fn convert_replace(pair: Pair<Rule>) -> Result<Vec<c::TextOrInlineElement>, Error> {
166 let mut pairs = pair.into_inner();
167 let paragraph = pairs.next().unwrap();
168 convert_inlines(paragraph)
169}
170
171fn convert_image<I>(pair: Pair<Rule>) -> Result<I, Error>
172where
173 I: Element + ExtraAttributes<a::Image>,
174{
175 let mut pairs = pair.into_inner();
176 let mut image = I::with_extra(a::Image::new(
177 pairs.next().unwrap().as_str().trim().parse()?, ));
179 for opt in pairs {
180 let mut opt_iter = opt.into_inner();
181 let opt_name = opt_iter.next().unwrap();
182 let opt_val = opt_iter.next().unwrap();
183 match opt_name.as_str() {
184 "class" => image.classes_mut().push(opt_val.as_str().to_owned()),
185 "name" => image.names_mut().push(opt_val.as_str().into()),
186 "alt" => image.extra_mut().alt = Some(opt_val.as_str().to_owned()),
187 "height" => image.extra_mut().height = Some(opt_val.parse()?),
188 "width" => image.extra_mut().width = Some(opt_val.parse()?),
189 "scale" => image.extra_mut().scale = Some(parse_scale(&opt_val)?),
190 "align" => image.extra_mut().align = Some(opt_val.parse()?),
191 "target" => image.extra_mut().target = Some(opt_val.parse()?),
192 name => bail!("Unknown Image option {}", name),
193 }
194 }
195 Ok(image)
196}
197
198fn parse_scale(pair: &Pair<Rule>) -> Result<u8, Error> {
199 use pest::error::{Error, ErrorVariant};
200
201 let input = pair.as_str().trim();
202 let input = if let Some(percentage) = input.strip_suffix('%') {
203 percentage.trim_end()
204 } else {
205 input
206 };
207 Ok(input.parse().map_err(|e: std::num::ParseIntError| {
208 let var: ErrorVariant<Rule> = ErrorVariant::CustomError {
209 message: e.to_string(),
210 };
211 Error::new_from_span(var, pair.as_span())
212 })?)
213}
214
215fn convert_admonition_gen(pair: Pair<Rule>) -> document_tree::element_categories::BodyElement {
216 let mut iter = pair.into_inner();
217 let typ = iter.next().unwrap().as_str();
218 let children: Vec<c::BodyElement> = iter
220 .map(|p| e::Paragraph::with_children(vec![p.as_str().into()]).into())
221 .collect();
222 match typ {
223 "attention" => e::Attention::with_children(children).into(),
224 "hint" => e::Hint::with_children(children).into(),
225 "note" => e::Note::with_children(children).into(),
226 "caution" => e::Caution::with_children(children).into(),
227 "danger" => e::Danger::with_children(children).into(),
228 "error" => e::Error::with_children(children).into(),
229 "important" => e::Important::with_children(children).into(),
230 "tip" => e::Tip::with_children(children).into(),
231 "warning" => e::Warning::with_children(children).into(),
232 typ => panic!("Unknown admontion type {typ}!"),
233 }
234}
235
236fn convert_bullet_list(pair: Pair<Rule>) -> Result<e::BulletList, Error> {
237 Ok(e::BulletList::with_children(
238 pair.into_inner()
239 .map(convert_bullet_item)
240 .collect::<Result<_, _>>()?,
241 ))
242}
243
244fn convert_bullet_item(pair: Pair<Rule>) -> Result<e::ListItem, Error> {
245 let mut iter = pair.into_inner();
246 let mut children: Vec<c::BodyElement> = vec![convert_paragraph(iter.next().unwrap())?.into()];
247 for p in iter {
248 children.push(convert_body_elem(p)?);
249 }
250 Ok(e::ListItem::with_children(children))
251}
252
253fn convert_block_quote(pair: Pair<Rule>) -> Result<e::BlockQuote, Error> {
254 Ok(e::BlockQuote::with_children(
255 pair.into_inner()
256 .map(convert_block_quote_inner)
257 .collect::<Result<_, _>>()?,
258 ))
259}
260
261fn convert_block_quote_directive(pair: Pair<Rule>) -> Result<e::BlockQuote, Error> {
262 let mut iter = pair.into_inner();
263 let typ = iter.next().unwrap().as_str();
264 let children: Vec<c::SubBlockQuote> = iter
265 .map(convert_block_quote_inner)
266 .collect::<Result<_, _>>()?;
267 let mut bq = e::BlockQuote::with_children(children);
268 bq.classes_mut().push(typ.to_owned());
269 Ok(bq)
270}
271
272fn convert_block_quote_inner(pair: Pair<Rule>) -> Result<c::SubBlockQuote, Error> {
273 Ok(if pair.as_rule() == Rule::attribution {
274 e::Attribution::with_children(convert_inlines(pair)?).into()
275 } else {
276 convert_body_elem(pair)?.into()
277 })
278}
279
280fn convert_literal_block(pair: Pair<Rule>) -> e::LiteralBlock {
281 convert_literal_lines(pair.into_inner().next().unwrap())
282}
283
284fn convert_literal_lines(pair: Pair<Rule>) -> e::LiteralBlock {
285 let children = pair
286 .into_inner()
287 .map(|l| {
288 match l.as_rule() {
289 Rule::literal_line => l.as_str(),
290 Rule::literal_line_blank => "\n",
291 _ => unreachable!(),
292 }
293 .into()
294 })
295 .collect();
296 e::LiteralBlock::with_children(children)
297}
298
299fn convert_code_directive(pair: Pair<Rule>) -> e::LiteralBlock {
300 let mut iter = pair.into_inner();
301 let (lang, code) = match (iter.next().unwrap(), iter.next()) {
302 (lang, Some(code)) => (Some(lang), code),
303 (code, None) => (None, code),
304 };
305 let mut code_block = convert_literal_lines(code);
306 code_block.classes_mut().push("code".to_owned());
307 if let Some(lang) = lang {
308 code_block.classes_mut().push(lang.as_str().to_owned());
309 }
310 code_block
311}
312
313fn convert_raw_directive(pair: Pair<Rule>) -> e::Raw {
314 let mut iter = pair.into_inner();
315 let format = iter.next().unwrap();
316 let block = iter.next().unwrap();
317 let children = block
318 .into_inner()
319 .map(|l| {
320 match l.as_rule() {
321 Rule::raw_line => l.as_str(),
322 Rule::raw_line_blank => "\n",
323 _ => unreachable!(),
324 }
325 .into()
326 })
327 .collect();
328 let mut raw_block = e::Raw::with_children(children);
329 raw_block
330 .extra_mut()
331 .format
332 .push(at::NameToken(format.as_str().to_owned()));
333 raw_block
334}
335
336fn convert_comment(pair: Pair<Rule>) -> e::Comment {
337 let lines = pair
338 .into_inner()
339 .map(|l| {
340 match l.as_rule() {
341 Rule::comment_line_blank => "\n",
342 Rule::comment_line => l.as_str(),
343 _ => unreachable!(),
344 }
345 .into()
346 })
347 .collect();
348 e::Comment::with_children(lines)
349}