1use nom::{
2 branch::alt,
3 bytes::complete::{take_till, take_till1},
4 character::complete::{char, line_ending, multispace0, none_of, one_of, space0, space1},
5 combinator::all_consuming,
6 multi::{many0, many_till},
7 sequence::{delimited, preceded, terminated, tuple},
8 Finish, Parser,
9};
10
11type Input<'a> = &'a str;
12
13type Result<'a, O = Input<'a>> = nom::IResult<Input<'a>, O>;
14
15#[derive(Debug, Clone)]
16pub struct Element<'a> {
17 pub tag: &'a str,
19 pub attr: Vec<&'a str>,
21 pub children: Vec<Child<'a>>,
23}
24
25#[derive(Debug, Clone)]
26pub enum Child<'a> {
27 Line(Vec<&'a str>),
29 Element(Element<'a>),
31}
32
33fn quoted_string(i: Input) -> Result {
34 let (i, quote_char) = one_of("\"'`")(i)?;
35 let (i, contents) = take_till(|x| x == quote_char || x == '\n' || x == '\r')(i)?;
36 let (i, _) = char(quote_char)(i)?;
37
38 Ok((i, contents))
39}
40
41fn unquoted_string(i: Input) -> Result {
42 none_of("\"'`")(i)?;
44 take_till1(|x| x == ' ' || x == '\n' || x == '\r')(i)
46}
47
48fn string(i: Input) -> Result {
49 alt((unquoted_string, quoted_string))(i)
50}
51
52fn string_list(i: Input) -> Result<Vec<&str>> {
53 let (i, first_element) = string(i)?;
55
56 let (i, mut other_elements) = many0(preceded(space1, string))(i)?;
58
59 other_elements.insert(0, first_element);
62
63 Ok((i, other_elements))
64}
65
66fn element_start(i: Input) -> Result<()> {
67 char('<').map(|_| ()).parse(i)
68}
69
70fn element_end(i: Input) -> Result<()> {
71 char('>').map(|_| ()).parse(i)
72}
73
74fn element_tag(i: Input) -> Result {
75 unquoted_string(i)
76}
77
78fn element(i: Input) -> Result<Element> {
79 let (i, _) = element_start(i)?;
81 let (i, tag) = element_tag(i)?;
82
83 let (i, attr) = terminated(
86 many0(preceded(space1, string)),
87 tuple((space0, line_ending)),
88 )(i)?;
89
90 let (i, (children, _end)) = many_till(
91 delimited(
93 space0,
94 alt((element.map(Child::Element), string_list.map(Child::Line))),
95 tuple((space0, line_ending)),
96 ),
97 tuple((space0, element_end)),
100 )(i)?;
101
102 let element = Element {
103 tag,
104 attr,
105 children,
106 };
107
108 Ok((i, element))
109}
110
111pub fn parse_element(i: Input) -> std::result::Result<Element, nom::error::Error<Input>> {
113 all_consuming(delimited(multispace0, element, multispace0))(i)
114 .finish()
115 .map(|(_, element)| element)
116}
117
118#[cfg(test)]
119mod tests {
120 use super::*;
121
122 mod quoted_string {
123 use super::*;
124
125 #[test]
126 fn test_01() {
127 let input = "'apple'";
128 let expected = "apple";
129 let (_, result) = quoted_string(input).unwrap();
130 assert_eq!(result, expected)
131 }
132
133 #[test]
134 fn test_02() {
135 let input = "'app\"le'";
136 let expected = "app\"le";
137 let (_, result) = quoted_string(input).unwrap();
138 assert_eq!(result, expected)
139 }
140
141 #[test]
142 fn test_03() {
143 let input = "\"asd asjhd basjh \"";
144 let expected = "asd asjhd basjh ";
145 let (_, result) = quoted_string(input).unwrap();
146 assert_eq!(result, expected)
147 }
148
149 #[test]
150 fn test_04() {
151 let input = "`asd asjhd basjh `";
152 let expected = "asd asjhd basjh ";
153 let (_, result) = quoted_string(input).unwrap();
154 assert_eq!(result, expected)
155 }
156
157 #[test]
158 fn test_05() {
159 let input = "`asd asjhd basjh ` 'hello'";
160 let expected = "asd asjhd basjh ";
161 let (_, result) = quoted_string(input).unwrap();
162 assert_eq!(result, expected)
163 }
164
165 #[test]
166 fn test_06() {
167 let input = "`asd asjhd b\nasjh ` 'hello'";
168 assert!(quoted_string(input).is_err())
169 }
170 }
171
172 #[cfg(test)]
173 mod unquoted_string {
174 use super::*;
175
176 #[test]
177 fn test_01() {
178 let input = "apple";
179 let expected = "apple";
180 let (_, result) = unquoted_string(input).unwrap();
181 assert_eq!(result, expected)
182 }
183
184 #[test]
185 fn test_02() {
186 let input = "hasquote''``' askjdla";
187 let expected = "hasquote''``'";
188 let (_, result) = unquoted_string(input).unwrap();
189 assert_eq!(result, expected)
190 }
191
192 #[test]
193 fn test_03() {
194 let input = "has space";
195 let expected = "has";
196 let (_, result) = unquoted_string(input).unwrap();
197 assert_eq!(result, expected)
198 }
199
200 #[test]
201 fn test_04() {
202 let input = " leading space";
203 assert!(unquoted_string(input).is_err())
204 }
205
206 #[test]
207 fn test_05() {
208 let input = "'hello'";
209 assert!(unquoted_string(input).is_err())
210 }
211 }
212}