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};
8
9use super::{inline::convert_inlines, whitespace_normalize_name};
10use crate::{pair_ext_parse::PairExt, pest_rst::Rule};
11
12#[derive(PartialEq)]
13pub(super) enum TitleKind {
14 Double(char),
15 Single(char),
16}
17
18pub(super) enum TitleOrSsubel {
19 Title(e::Title, TitleKind),
20 Ssubel(c::StructuralSubElement),
21}
22
23pub(super) fn convert_ssubel(pair: Pair<Rule>) -> Result<Option<TitleOrSsubel>, Error> {
24 use self::TitleOrSsubel::{Ssubel, Title};
25 Ok(Some(match pair.as_rule() {
26 Rule::title => {
27 let (t, k) = convert_title(pair)?;
28 Title(t, k)
29 }
30 Rule::EOI => return Ok(None),
32 _ => Ssubel(convert_substructure(pair)?.into()),
33 }))
34}
35
36fn convert_substructure(pair: Pair<Rule>) -> Result<c::SubStructure, Error> {
37 #[allow(clippy::match_single_binding)]
38 Ok(match pair.as_rule() {
39 _ => convert_body_elem(pair)?.into(),
42 })
43}
44
45fn convert_body_elem(pair: Pair<Rule>) -> Result<c::BodyElement, Error> {
46 Ok(match pair.as_rule() {
47 Rule::paragraph => convert_paragraph(pair)?.into(),
48 Rule::target => convert_target(pair)?.into(),
49 Rule::footnote => convert_footnote(pair)?.into(),
50 Rule::substitution_def => convert_substitution_def(pair)?.into(),
51 Rule::block_quote_directive => convert_block_quote_directive(pair)?.into(),
52 Rule::admonition_gen => convert_admonition_gen(pair),
53 Rule::image => convert_image::<e::Image>(pair)?.into(),
54 Rule::bullet_list => convert_bullet_list(pair)?.into(),
55 Rule::block_quote => convert_block_quote(pair)?.into(),
56 Rule::literal_block => convert_literal_block(pair).into(),
57 Rule::code_directive => convert_code_directive(pair).into(),
58 Rule::raw_directive => convert_raw_directive(pair).into(),
59 Rule::block_comment => convert_comment(pair).into(),
60 rule => unimplemented!("unhandled rule {:?}", rule),
61 })
62}
63
64fn convert_title(pair: Pair<Rule>) -> Result<(e::Title, TitleKind), Error> {
65 let mut title: Option<String> = None;
66 let mut title_inlines: Option<Vec<c::TextOrInlineElement>> = None;
67 let mut adornment_char: Option<char> = None;
68 let inner_pair = pair.into_inner().next().unwrap();
70 let kind = inner_pair.as_rule();
71 for p in inner_pair.into_inner() {
72 match p.as_rule() {
73 Rule::line => {
74 title = Some(p.as_str().to_owned());
75 title_inlines = Some(convert_inlines(p)?);
76 }
77 Rule::adornments => {
78 adornment_char = Some(p.as_str().chars().next().expect("Empty adornment?"));
79 }
80 rule => unimplemented!("Unexpected rule in title: {:?}", rule),
81 }
82 }
83 let mut elem = e::Title::with_children(title_inlines.expect("No text in title"));
86 if let Some(title) = title {
87 let slug = title.to_lowercase().replace('\n', "").replace(' ', "-");
89 elem.names_mut().push(at::NameToken(slug));
90 }
91 let title_kind = match kind {
92 Rule::title_double => TitleKind::Double(adornment_char.unwrap()),
93 Rule::title_single => TitleKind::Single(adornment_char.unwrap()),
94 _ => unreachable!(),
95 };
96 Ok((elem, title_kind))
97}
98
99fn convert_paragraph(pair: Pair<Rule>) -> Result<e::Paragraph, Error> {
100 Ok(e::Paragraph::with_children(convert_inlines(pair)?))
101}
102
103fn convert_target(pair: Pair<Rule>) -> Result<e::Target, Error> {
104 let mut elem = e::Target::default();
105 elem.extra_mut().anonymous = false;
106 for p in pair.into_inner() {
107 match p.as_rule() {
108 Rule::target_name_uq | Rule::target_name_qu => {
109 elem.ids_mut().push(p.as_str().into());
110 elem.names_mut().push(p.as_str().into());
111 }
112 Rule::link_target => elem.extra_mut().refuri = Some(p.parse()?),
114 rule => panic!("Unexpected rule in target: {rule:?}"),
115 }
116 }
117 Ok(elem)
118}
119
120fn convert_footnote(pair: Pair<Rule>) -> Result<e::Footnote, Error> {
124 let mut pairs = pair.into_inner();
125 let label = pairs.next().unwrap().as_str();
126 let mut children: Vec<c::SubFootnote> = vec![];
127 children.push(convert_paragraph(pairs.next().unwrap())?.into());
129 for p in pairs {
130 children.push(convert_body_elem(p)?.into());
131 }
132 let mut footnote = e::Footnote::with_children(children);
133 footnote.extra_mut().auto = label.chars().next().unwrap().try_into().ok();
134 match footnote.extra().auto {
135 Some(at::FootnoteType::Number) => {
136 if label.len() > 1 {
137 let name = whitespace_normalize_name(&label[1..]);
138 footnote.names_mut().push(at::NameToken(name));
139 }
140 }
141 Some(at::FootnoteType::Symbol) => {}
142 None => {
143 footnote
144 .children_mut()
145 .insert(0, e::Label::with_children(vec![label.into()]).into());
146 }
147 }
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}