rst_parser/conversion/
inline.rs
1use anyhow::Error;
2use pest::iterators::Pair;
3
4use document_tree::{
5 CommonAttributes, HasChildren, attribute_types as at, element_categories as c, elements as e,
6 extra_attributes as a, url::Url,
7};
8
9use super::whitespace_normalize_name;
10use crate::pest_rst::Rule;
11
12pub fn convert_inline(pair: Pair<Rule>) -> Result<c::TextOrInlineElement, Error> {
13 Ok(match pair.as_rule() {
14 Rule::str | Rule::str_nested => pair.as_str().into(),
15 Rule::ws_newline => " ".to_owned().into(),
16 Rule::reference => convert_reference(pair)?,
17 Rule::substitution_name => convert_substitution_ref(&pair).into(),
18 Rule::emph => e::Emphasis::with_children(convert_inlines(pair)?).into(),
19 Rule::strong => e::Strong::with_children(convert_inlines(pair)?).into(),
20 Rule::literal => e::Literal::with_children(vec![pair.as_str().to_owned()]).into(),
21 rule => unimplemented!("unknown rule {:?}", rule),
22 })
23}
24
25pub fn convert_inlines(pair: Pair<Rule>) -> Result<Vec<c::TextOrInlineElement>, Error> {
26 pair.into_inner().map(convert_inline).collect()
27}
28
29fn convert_reference(pair: Pair<Rule>) -> Result<c::TextOrInlineElement, Error> {
30 let concrete = pair.into_inner().next().unwrap();
31 match concrete.as_rule() {
32 Rule::reference_target => convert_reference_target(concrete).map(Into::into),
33 Rule::reference_explicit => unimplemented!("explicit reference"),
34 Rule::reference_auto => Ok(convert_reference_auto(concrete)),
35 _ => unreachable!(),
36 }
37}
38
39fn convert_reference_target(concrete: Pair<'_, Rule>) -> Result<e::Reference, Error> {
40 let rt_inner = concrete.into_inner().next().unwrap();
41 Ok(match rt_inner.as_rule() {
42 Rule::reference_target_uq => e::Reference::new(
43 CommonAttributes::default(),
44 a::Reference {
45 name: Some(rt_inner.as_str().into()),
46 refuri: None,
47 refid: None,
48 refname: vec![rt_inner.as_str().into()],
49 },
50 vec![rt_inner.as_str().into()],
51 ),
52 Rule::reference_target_qu => {
53 let (text, reference) = {
54 let mut text = None;
55 let mut reference = None;
56 for inner in rt_inner.clone().into_inner() {
57 match inner.as_rule() {
58 Rule::reference_text => text = Some(inner),
59 Rule::reference_bracketed => reference = Some(inner),
60 _ => unreachable!(),
61 }
62 }
63 (text, reference)
64 };
65 let trimmed_text = match (&text, &reference) {
66 (Some(text), None) => text.as_str(),
67 (_, Some(reference)) => text
68 .map(|text| text.as_str().trim_end_matches(|ch| " \n\r".contains(ch)))
69 .filter(|text| !text.is_empty())
70 .unwrap_or_else(|| reference.clone().into_inner().next().unwrap().as_str()),
71 (None, None) => unreachable!(),
72 };
73 let (refuri, refname): (Option<Url>, Vec<at::NameToken>) =
74 if let Some(reference) = reference {
75 let inner = reference.into_inner().next().unwrap();
76 match inner.as_rule() {
77 Rule::url => {
80 if let Ok(target) = Url::parse_absolute(inner.as_str()) {
81 (Some(target), Vec::new())
82 } else if inner.as_str().ends_with('_') {
83 let full_str = inner.as_str();
85 (None, vec![full_str[0..full_str.len() - 1].into()])
86 } else {
87 (Some(Url::parse_relative(inner.as_str())?), Vec::new())
89 }
90 }
91 Rule::target_name_qu => (None, vec![inner.as_str().into()]),
92 Rule::relative_reference => {
93 (Some(Url::parse_relative(inner.as_str())?), Vec::new())
94 }
95 _ => unreachable!(),
96 }
97 } else {
98 (None, vec![trimmed_text.into()])
99 };
100 e::Reference::new(
101 CommonAttributes::default(),
102 a::Reference {
103 name: Some(trimmed_text.into()),
104 refuri,
105 refid: None,
106 refname,
107 },
108 vec![trimmed_text.into()],
109 )
110 }
111 _ => unreachable!(),
112 })
113}
114
115fn convert_reference_auto(concrete: Pair<'_, Rule>) -> c::TextOrInlineElement {
116 let rt_inner = concrete.into_inner().next().unwrap();
117 let str: c::TextOrInlineElement = rt_inner.as_str().into();
118 let Ok(target) = (match rt_inner.as_rule() {
119 Rule::url_auto => Url::parse_absolute(rt_inner.as_str()),
120 Rule::email => Url::parse_absolute(&format!("mailto:{}", rt_inner.as_str())),
121 _ => unreachable!(),
122 }) else {
123 return str;
125 };
126 e::Reference::new(
127 CommonAttributes::default(),
128 a::Reference {
129 name: None,
130 refuri: Some(target),
131 refid: None,
132 refname: Vec::new(),
133 },
134 vec![str],
135 )
136 .into()
137}
138
139fn convert_substitution_ref(pair: &Pair<Rule>) -> e::SubstitutionReference {
140 let name = whitespace_normalize_name(pair.as_str());
141 a::ExtraAttributes::with_extra(a::SubstitutionReference {
142 refname: vec![at::NameToken(name)],
143 })
144}