1use std::borrow::Cow;
2
3pub use self::error::ParseError;
4use self::util::{parse_start_tag, trim_start_newlines_end};
5
6use crate::{
7 parser::util::parse_end_tag, Attribute, AttributeValue, Block, BlockName, Raw, Section,
8};
9
10mod error;
11mod util;
12
13#[derive(Debug)]
15enum State<'a> {
16 Root,
18 Data {
21 name: BlockName<'a>,
22 attributes: Vec<Attribute<'a>>,
23 depth: u16,
24 },
25 RawText {
28 name: BlockName<'a>,
29 attributes: Vec<Attribute<'a>>,
30 },
31}
32
33pub fn parse(input: &str) -> Result<Vec<Section<'_>>, ParseError> {
64 let mut less_than_symbols = memchr::memmem::find_iter(input.as_bytes(), "<");
65
66 let mut buffer = Vec::new();
67 let mut offset = 0;
68 let mut state = State::Root;
69
70 loop {
71 match state {
72 State::Root => {
73 let index = if let Some(index) = less_than_symbols.next() {
74 index
75 } else {
76 let raw = trim_start_newlines_end(&input[offset..]);
77
78 if !raw.is_empty() {
79 let raw = unsafe { Raw::from_cow_unchecked(Cow::Borrowed(raw)) };
81 buffer.push(Section::Raw(raw));
82 }
83
84 break;
85 };
86
87 if let Ok((_, name)) = parse_end_tag(&input[index..]) {
88 return Err(ParseError::UnexpectedEndTag(name.as_str().to_owned()));
89 }
90
91 if let Ok((remaining, (name, attributes))) = parse_start_tag(&input[index..]) {
92 let raw = trim_start_newlines_end(&input[offset..index]);
93
94 if !raw.is_empty() {
95 let raw = unsafe { Raw::from_cow_unchecked(Cow::Borrowed(raw)) };
97 buffer.push(Section::Raw(raw));
98 }
99
100 let raw_text = name.as_str() != "template"
101 || attributes.iter().any(|(name, value)| {
102 matches!(
103 (name.as_str(), value.as_ref().map(AttributeValue::as_str)),
104 ("lang", Some(lang)) if lang != "html"
105 )
106 });
107
108 offset = input.len() - remaining.len();
109 state = if raw_text {
110 State::RawText { name, attributes }
111 } else {
112 State::Data {
113 name,
114 attributes,
115 depth: 0,
116 }
117 };
118 }
119 }
120 State::Data {
121 name: ref parent_name,
122 ref mut attributes,
123 ref mut depth,
124 } => {
125 let index = less_than_symbols
126 .next()
127 .ok_or_else(|| ParseError::MissingEndTag(parent_name.as_str().to_owned()))?;
128
129 match parse_end_tag(&input[index..]) {
130 Ok((remaining, name)) if &name == parent_name => {
131 if *depth == 0 {
132 buffer.push(Section::Block(Block {
133 name,
134 attributes: std::mem::take(attributes),
135 content: Cow::Borrowed(trim_start_newlines_end(
136 &input[offset..index],
137 )),
138 }));
139
140 offset = input.len() - remaining.len();
141 state = State::Root;
142 } else {
143 *depth -= 1;
144 }
145
146 continue;
148 }
149 _ => { }
150 }
151
152 match parse_start_tag(&input[index..]) {
153 Ok((_, (name, _))) if &name == parent_name => {
154 *depth += 1;
155 }
156 _ => { }
157 }
158 }
159 State::RawText {
160 name: ref parent_name,
161 ref mut attributes,
162 } => {
163 let index = less_than_symbols
164 .next()
165 .ok_or_else(|| ParseError::MissingEndTag(parent_name.as_str().to_owned()))?;
166
167 match parse_end_tag(&input[index..]) {
168 Ok((remaining, name)) if &name == parent_name => {
169 buffer.push(Section::Block(Block {
170 name,
171 attributes: std::mem::take(attributes),
172 content: Cow::Borrowed(trim_start_newlines_end(&input[offset..index])),
173 }));
174
175 offset = input.len() - remaining.len();
176 state = State::Root;
177 }
178 _ => { }
179 }
180 }
181 }
182 }
183
184 Ok(buffer)
185}
186
187#[cfg(test)]
188mod tests {
189 use std::borrow::Cow;
190
191 use crate::{Block, BlockName, Raw, Section};
192
193 use super::parse;
194
195 #[test]
196 fn test_parse_empty() {
197 assert_eq!(parse("").unwrap(), vec![]);
198 }
199
200 #[test]
201 fn test_parse_raw() {
202 assert_eq!(
203 parse("<!-- a comment -->").unwrap(),
204 vec![Section::Raw(Raw::try_from("<!-- a comment -->").unwrap())]
205 );
206 }
207
208 #[test]
209 fn test_parse_block() {
210 assert_eq!(
211 parse("<template></template>").unwrap(),
212 vec![Section::Block(Block {
213 name: BlockName::try_from("template").unwrap(),
214 attributes: vec![],
215 content: Cow::default()
216 })]
217 );
218 }
219
220 #[test]
221 fn test_parse_consecutive_blocks() {
222 assert_eq!(
223 parse("<template></template><script></script>").unwrap(),
224 vec![
225 Section::Block(Block {
226 name: BlockName::try_from("template").unwrap(),
227 attributes: vec![],
228 content: Cow::default()
229 }),
230 Section::Block(Block {
231 name: BlockName::try_from("script").unwrap(),
232 attributes: vec![],
233 content: Cow::default()
234 })
235 ]
236 );
237 }
238
239 #[test]
240 fn test_parse() {
241 let raw = r#"<template>
242 <router-view v-slot="{ Component }"
243 >
244 <suspense v-if="Component" :timeout="150">
245 <template #default>
246 <component :is="Component"/>
247 </template>
248 <template #fallback>
249 Loading...
250 </template>
251 </suspense>
252 </router-view>
253</template>
254
255<script lang="ts" setup>
256onErrorCaptured((err) => {
257 console.error(err);
258});
259</script>"#;
260
261 let sfc = parse(raw).unwrap();
262
263 match &sfc[0] {
264 Section::Block(Block {
265 name,
266 attributes,
267 content,
268 }) => {
269 assert_eq!(name.as_str(), "template");
270 assert_eq!(content.len(), 266);
271 assert!(attributes.is_empty());
272 }
273 _ => panic!("expected a block"),
274 }
275
276 match &sfc[1] {
277 Section::Block(Block {
278 name,
279 attributes,
280 content,
281 }) => {
282 assert_eq!(name.as_str(), "script");
283 assert_eq!(content.len(), 52);
284 assert_eq!(attributes[0].0.as_str(), "lang");
285 assert_eq!(attributes[0].1.as_ref().unwrap().as_str(), "ts");
286 assert_eq!(attributes[1].0.as_str(), "setup");
287 assert!(attributes[1].1.is_none());
288 }
289 _ => panic!("expected a block"),
290 }
291 }
292}