1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
use crate::{ExecutionError, Result};
use log::debug;
use quick_xml::events::BytesStart;
use quick_xml::Reader;
use std::io::BufRead;
#[derive(Debug)]
pub enum Tag {
Include {
src: String,
alt: Option<String>,
continue_on_error: bool,
},
}
#[derive(Debug)]
#[allow(clippy::upper_case_acronyms)]
pub enum Event<'e> {
XML(quick_xml::events::Event<'e>),
ESI(Tag),
}
pub fn parse_tags<'a, R>(
namespace: &str,
reader: &mut Reader<R>,
callback: &mut dyn FnMut(Event<'a>) -> Result<()>,
) -> Result<()>
where
R: BufRead,
{
debug!("Parsing document...");
let mut remove = false;
let mut open_include = false;
let esi_include = format!("{}:include", namespace).into_bytes();
let esi_comment = format!("{}:comment", namespace).into_bytes();
let esi_remove = format!("{}:remove", namespace).into_bytes();
let mut buffer = Vec::new();
loop {
match reader.read_event_into(&mut buffer) {
Ok(quick_xml::events::Event::Start(elem)) if elem.starts_with(&esi_remove) => {
remove = true;
}
Ok(quick_xml::events::Event::End(elem)) if elem.starts_with(&esi_remove) => {
if !remove {
return Err(ExecutionError::UnexpectedClosingTag(
String::from_utf8(elem.to_vec()).unwrap(),
));
}
remove = false;
}
_ if remove => continue,
Ok(quick_xml::events::Event::Empty(elem))
if elem.name().into_inner().starts_with(&esi_include) =>
{
callback(parse_include(&elem)?)?;
}
Ok(quick_xml::events::Event::Start(elem))
if elem.name().into_inner().starts_with(&esi_include) =>
{
open_include = true;
callback(parse_include(&elem)?)?;
}
Ok(quick_xml::events::Event::End(elem))
if elem.name().into_inner().starts_with(&esi_include) =>
{
if !open_include {
return Err(ExecutionError::UnexpectedClosingTag(
String::from_utf8(elem.to_vec()).unwrap(),
));
}
open_include = false;
}
_ if open_include => continue,
Ok(quick_xml::events::Event::Empty(elem))
if elem.name().into_inner().starts_with(&esi_comment) =>
{
continue
}
Ok(quick_xml::events::Event::Eof) => {
debug!("End of document");
break;
}
Ok(e) => callback(Event::XML(e.into_owned()))?,
_ => {}
}
}
Ok(())
}
fn parse_include<'a, 'b>(elem: &'a BytesStart) -> Result<Event<'b>> {
let src = match elem
.attributes()
.flatten()
.find(|attr| attr.key.into_inner() == b"src")
{
Some(attr) => String::from_utf8(attr.value.to_vec()).unwrap(),
None => {
return Err(ExecutionError::MissingRequiredParameter(
String::from_utf8(elem.name().into_inner().to_vec()).unwrap(),
"src".to_string(),
));
}
};
let alt = elem
.attributes()
.flatten()
.find(|attr| attr.key.into_inner() == b"alt")
.map(|attr| String::from_utf8(attr.value.to_vec()).unwrap());
let continue_on_error = elem
.attributes()
.flatten()
.find(|attr| attr.key.into_inner() == b"onerror")
.map(|attr| &attr.value.to_vec() == b"continue")
== Some(true);
Ok(Event::ESI(Tag::Include {
src,
alt,
continue_on_error,
}))
}