ktrs_cli/ktlint/baseline/
xml.rs1pub struct Element {
5 pub name: String,
6 pub attributes: Vec<(String, String)>,
7 pub children: Vec<Element>,
8}
9
10impl Element {
11 pub fn attribute(&self, name: &str) -> &str {
13 self.attributes.iter().find(|(n, _)| n == name).map_or("", |(_, v)| v.as_str())
14 }
15
16 pub fn elements_by_tag_name<'a>(&'a self, name: &str, found: &mut Vec<&'a Element>) {
18 for child in &self.children {
19 if child.name == name {
20 found.push(child);
21 }
22 child.elements_by_tag_name(name, found);
23 }
24 }
25}
26
27#[derive(Debug, PartialEq, Eq)]
29pub struct XmlError {
30 pub line: usize,
31 pub col: usize,
32 pub message: String,
33}
34
35struct Parser {
36 chars: Vec<char>,
37 pos: usize,
38 line: usize,
39 col: usize,
40}
41
42pub fn parse_document(text: &str) -> Result<Element, XmlError> {
44 let mut p = Parser { chars: text.trim_start_matches('\u{feff}').chars().collect(), pos: 0, line: 1, col: 1 };
45 p.skip_misc(true)?;
46 if p.peek().is_none() {
47 return Err(p.error("Premature end of file."));
48 }
49 let root = p.element()?;
50 p.skip_misc(false)?;
51 Ok(Element { name: "#document".to_owned(), attributes: Vec::new(), children: vec![root] })
52}
53
54impl Parser {
55 fn peek(&self) -> Option<char> {
56 self.chars.get(self.pos).copied()
57 }
58
59 fn starts_with(&self, s: &str) -> bool {
60 s.chars().enumerate().all(|(i, c)| self.chars.get(self.pos + i) == Some(&c))
61 }
62
63 fn bump(&mut self) -> Option<char> {
64 let c = self.peek()?;
65 self.pos += 1;
66 if c == '\n' {
67 self.line += 1;
68 self.col = 1;
69 } else {
70 self.col += 1;
71 }
72 Some(c)
73 }
74
75 fn error(&self, message: &str) -> XmlError {
76 XmlError { line: self.line, col: self.col, message: message.to_owned() }
77 }
78
79 fn eof(&self) -> XmlError {
80 self.error("XML document structures must start and end within the same entity.")
81 }
82
83 fn skip_until(&mut self, end: &str) -> Result<(), XmlError> {
84 while !self.starts_with(end) {
85 self.bump().ok_or_else(|| self.eof())?;
86 }
87 end.chars().for_each(|_| {
88 self.bump();
89 });
90 Ok(())
91 }
92
93 fn skip_misc(&mut self, prolog: bool) -> Result<(), XmlError> {
95 loop {
96 match self.peek() {
97 Some(c) if c.is_whitespace() => {
98 self.bump();
99 }
100 Some('<') if self.starts_with("<?") => self.skip_until("?>")?,
101 Some('<') if self.starts_with("<!--") => self.skip_until("-->")?,
102 Some('<') if prolog && self.starts_with("<!DOCTYPE") => self.skip_until(">")?,
103 Some('<') if prolog => return Ok(()),
104 Some('<') => {
105 self.bump();
106 return Err(self.error("The markup in the document following the root element must be well-formed."));
107 }
108 Some(_) if prolog => return Err(self.error("Content is not allowed in prolog.")),
109 Some(_) => return Err(self.error("Content is not allowed in trailing section.")),
110 None => return Ok(()),
111 }
112 }
113 }
114
115 fn name(&mut self) -> String {
116 let mut name = String::new();
117 while let Some(c) = self.peek().filter(|&c| c.is_alphanumeric() || matches!(c, '_' | ':' | '-' | '.')) {
118 name.push(c);
119 self.bump();
120 }
121 name
122 }
123
124 fn skip_whitespace(&mut self) {
125 while self.peek().is_some_and(char::is_whitespace) {
126 self.bump();
127 }
128 }
129
130 fn element(&mut self) -> Result<Element, XmlError> {
131 self.bump();
132 let name = self.name();
133 let mut element = Element { name, attributes: Vec::new(), children: Vec::new() };
134 loop {
135 self.skip_whitespace();
136 match self.peek() {
137 None => return Err(self.eof()),
138 Some('/') if self.starts_with("/>") => {
139 self.bump();
140 self.bump();
141 return Ok(element);
142 }
143 Some('>') => {
144 self.bump();
145 break;
146 }
147 Some(_) => {
148 let attribute = self.attribute(&element.name)?;
149 element.attributes.push(attribute);
150 }
151 }
152 }
153 loop {
154 match self.peek() {
155 None => return Err(self.eof()),
156 Some('<') if self.starts_with("</") => {
157 self.bump();
158 self.bump();
159 let at_name = self.error("");
160 let end = self.name();
161 if end != element.name {
162 return Err(XmlError { message: format!(
163 "The element type \"{0}\" must be terminated by the matching end-tag \"</{0}>\".",
164 element.name
165 ), ..at_name });
166 }
167 self.skip_whitespace();
168 if self.bump() != Some('>') {
169 return Err(self.error(&format!("The end-tag for element type \"{end}\" must end with a '>' delimiter.")));
170 }
171 return Ok(element);
172 }
173 Some('<') if self.starts_with("<!--") => self.skip_until("-->")?,
174 Some('<') if self.starts_with("<![CDATA[") => self.skip_until("]]>")?,
175 Some('<') if self.starts_with("<?") => self.skip_until("?>")?,
176 Some('<') => element.children.push(self.element()?),
177 Some('&') => {
178 self.reference()?;
179 }
180 Some(_) => {
181 self.bump();
182 }
183 }
184 }
185 }
186
187 fn attribute(&mut self, element_name: &str) -> Result<(String, String), XmlError> {
188 let expected = || {
189 format!("Element type \"{element_name}\" must be followed by either attribute specifications, \">\" or \"/>\".")
190 };
191 let name = self.name();
192 if name.is_empty() {
193 return Err(self.error(&expected()));
194 }
195 self.skip_whitespace();
196 if self.bump() != Some('=') {
197 return Err(self.error(&format!(
198 "Attribute name \"{name}\" associated with an element type \"{element_name}\" must be followed by the ' = ' character."
199 )));
200 }
201 self.skip_whitespace();
202 let quote = match self.peek() {
203 Some(q @ ('"' | '\'')) => {
204 self.bump();
205 q
206 }
207 _ => {
208 return Err(self.error(&format!(
209 "Open quote is expected for attribute \"{name}\" associated with an element type \"{element_name}\"."
210 )));
211 }
212 };
213 let mut value = String::new();
214 loop {
215 match self.peek() {
216 None => return Err(self.eof()),
217 Some(c) if c == quote => {
218 self.bump();
219 return Ok((name, value));
220 }
221 Some('<') => {
222 return Err(self.error(&format!(
223 "The value of attribute \"{name}\" associated with an element type \"{element_name}\" must not contain the '<' character."
224 )));
225 }
226 Some('&') => value.push_str(&self.reference()?),
227 Some(c) => {
228 self.bump();
229 value.push(if matches!(c, '\n' | '\r' | '\t') { ' ' } else { c });
230 }
231 }
232 }
233 }
234
235 fn reference(&mut self) -> Result<String, XmlError> {
236 self.bump();
237 let mut name = String::new();
238 loop {
239 match self.bump() {
240 None => return Err(self.eof()),
241 Some(';') => break,
242 Some(c) => name.push(c),
243 }
244 }
245 let numeric = |digits: &str, radix| u32::from_str_radix(digits, radix).ok().and_then(char::from_u32);
246 let c = match name.as_str() {
247 "amp" => Some('&'),
248 "lt" => Some('<'),
249 "gt" => Some('>'),
250 "quot" => Some('"'),
251 "apos" => Some('\''),
252 n if n.starts_with("#x") => numeric(&n[2..], 16),
253 n if n.starts_with('#') => numeric(&n[1..], 10),
254 _ => None,
255 };
256 c.map(String::from)
257 .ok_or_else(|| self.error(&format!("The entity \"{name}\" was referenced, but not declared.")))
258 }
259}