1#![forbid(unsafe_code)]
8
9#[derive(Clone, Debug, PartialEq, Eq)]
11pub enum SaxEvent {
12 SetDocumentLocator,
13 StartDocument,
14 EndDocument,
15 StartElementNs {
16 local: String,
17 prefix: Option<String>,
18 uri: Option<String>,
19 namespaces: Vec<(Option<String>, String)>,
20 attributes: Vec<SaxAttr>,
21 nb_defaulted: i32,
22 },
23 EndElementNs {
24 local: String,
25 prefix: Option<String>,
26 uri: Option<String>,
27 },
28 Characters {
29 data: String,
30 },
31 CData {
32 data: String,
33 },
34 Comment(String),
35 ProcessingInstruction {
36 target: String,
37 data: Option<String>,
38 },
39 Warning(String),
40 Error(String),
41}
42
43#[derive(Clone, Debug)]
45pub struct SaxAttr {
46 pub local: String,
47 pub prefix: Option<String>,
48 pub uri: Option<String>,
49 pub value: String,
50 pub value_input_off: Option<usize>,
53}
54
55impl PartialEq for SaxAttr {
56 fn eq(&self, other: &Self) -> bool {
57 self.local == other.local
58 && self.prefix == other.prefix
59 && self.uri == other.uri
60 && self.value == other.value
61 }
62}
63
64impl Eq for SaxAttr {}
65
66pub trait SaxHandler {
68 fn set_document_locator(&mut self) {}
69 fn start_document(&mut self) {}
70 fn end_document(&mut self) {}
71 fn start_element_ns(
72 &mut self,
73 local: &str,
74 prefix: Option<&str>,
75 uri: Option<&str>,
76 namespaces: &[(Option<String>, String)],
77 attributes: &[SaxAttr],
78 nb_defaulted: i32,
79 ) {
80 let _ = (local, prefix, uri, namespaces, attributes, nb_defaulted);
81 }
82 fn end_element_ns(&mut self, local: &str, prefix: Option<&str>, uri: Option<&str>) {
83 let _ = (local, prefix, uri);
84 }
85 fn characters(&mut self, data: &str) {
86 let _ = data;
87 }
88 fn cdata_block(&mut self, data: &str) {
89 let _ = data;
90 }
91 fn comment(&mut self, data: &str) {
92 let _ = data;
93 }
94 fn processing_instruction(&mut self, target: &str, data: Option<&str>) {
95 let _ = (target, data);
96 }
97 fn warning(&mut self, msg: &str) {
98 let _ = msg;
99 }
100 fn error(&mut self, msg: &str) {
101 let _ = msg;
102 }
103}
104
105#[derive(Clone, Copy, Debug, Default)]
112pub struct NullSax;
113
114impl SaxHandler for NullSax {}
115
116#[derive(Clone, Debug, Default)]
118pub struct SaxRecorder {
119 pub events: Vec<SaxEvent>,
120}
121
122impl SaxHandler for SaxRecorder {
123 fn set_document_locator(&mut self) {
124 self.events.push(SaxEvent::SetDocumentLocator);
125 }
126 fn start_document(&mut self) {
127 self.events.push(SaxEvent::StartDocument);
128 }
129 fn end_document(&mut self) {
130 self.events.push(SaxEvent::EndDocument);
131 }
132 fn start_element_ns(
133 &mut self,
134 local: &str,
135 prefix: Option<&str>,
136 uri: Option<&str>,
137 namespaces: &[(Option<String>, String)],
138 attributes: &[SaxAttr],
139 nb_defaulted: i32,
140 ) {
141 self.events.push(SaxEvent::StartElementNs {
142 local: local.to_string(),
143 prefix: prefix.map(str::to_string),
144 uri: uri.map(str::to_string),
145 namespaces: namespaces.to_vec(),
146 attributes: attributes.to_vec(),
147 nb_defaulted,
148 });
149 }
150 fn end_element_ns(&mut self, local: &str, prefix: Option<&str>, uri: Option<&str>) {
151 self.events.push(SaxEvent::EndElementNs {
152 local: local.to_string(),
153 prefix: prefix.map(str::to_string),
154 uri: uri.map(str::to_string),
155 });
156 }
157 fn characters(&mut self, data: &str) {
158 self.events.push(SaxEvent::Characters {
159 data: data.to_string(),
160 });
161 }
162 fn cdata_block(&mut self, data: &str) {
163 self.events.push(SaxEvent::CData {
164 data: data.to_string(),
165 });
166 }
167 fn comment(&mut self, data: &str) {
168 self.events.push(SaxEvent::Comment(data.to_string()));
169 }
170 fn processing_instruction(&mut self, target: &str, data: Option<&str>) {
171 self.events.push(SaxEvent::ProcessingInstruction {
172 target: target.to_string(),
173 data: data.map(str::to_string),
174 });
175 }
176 fn warning(&mut self, msg: &str) {
177 self.events.push(SaxEvent::Warning(msg.to_string()));
178 }
179 fn error(&mut self, msg: &str) {
180 self.events.push(SaxEvent::Error(msg.to_string()));
181 }
182}
183
184impl SaxRecorder {
185 pub fn new() -> Self {
186 Self::default()
187 }
188
189 pub fn to_xmllint_debug(&self, input: &[u8]) -> String {
191 let mut out = String::new();
192 for ev in &self.events {
193 out.push_str(&event_to_xmllint_debug(ev, input));
194 }
195 out
196 }
197}
198
199fn trunc_bytes(s: &str, n: usize) -> String {
200 let b = s.as_bytes();
201 let k = b.len().min(n);
202 String::from_utf8_lossy(&b[..k]).into_owned()
203}
204
205fn opt_name(p: &Option<String>) -> String {
206 match p {
207 None => "NULL".into(),
208 Some(s) => s.clone(),
209 }
210}
211
212fn opt_uri(u: &Option<String>) -> String {
213 match u {
214 None => "NULL".into(),
215 Some(s) => format!("'{s}'"),
216 }
217}
218
219pub fn event_to_xmllint_debug(ev: &SaxEvent, input: &[u8]) -> String {
221 match ev {
222 SaxEvent::SetDocumentLocator => "SAX.setDocumentLocator()\n".into(),
223 SaxEvent::StartDocument => "SAX.startDocument()\n".into(),
224 SaxEvent::EndDocument => "SAX.endDocument()\n".into(),
225 SaxEvent::StartElementNs {
226 local,
227 prefix,
228 uri,
229 namespaces,
230 attributes,
231 nb_defaulted,
232 } => {
233 let mut s = format!(
234 "SAX.startElementNs({local}, {}, {}, {}",
235 opt_name(prefix),
236 opt_uri(uri),
237 namespaces.len()
238 );
239 for (pre, href) in namespaces {
240 s.push_str(", xmlns");
241 if let Some(p) = pre {
242 s.push(':');
243 s.push_str(p);
244 }
245 s.push_str(&format!("='{href}'"));
246 }
247 s.push_str(&format!(", {}, {nb_defaulted}", attributes.len()));
248 for a in attributes {
249 if let Some(p) = &a.prefix {
250 s.push_str(&format!(", {p}:{}='", a.local));
251 } else {
252 s.push_str(&format!(", {}='", a.local));
253 }
254 let four = if let Some(off) = a.value_input_off {
255 let end = (off + 4).min(input.len());
256 if off < input.len() {
257 String::from_utf8_lossy(&input[off..end]).into_owned()
258 } else {
259 trunc_bytes(&a.value, 4)
260 }
261 } else {
262 trunc_bytes(&a.value, 4)
263 };
264 s.push_str(&format!("{four}...', {}", a.value.len()));
265 }
266 s.push_str(")\n");
267 s
268 }
269 SaxEvent::EndElementNs {
270 local,
271 prefix,
272 uri,
273 } => {
274 if uri.is_none() {
275 format!("SAX.endElementNs({local}, {}, NULL)\n", opt_name(prefix))
276 } else {
277 format!(
278 "SAX.endElementNs({local}, {}, {})\n",
279 opt_name(prefix),
280 opt_uri(uri)
281 )
282 }
283 }
284 SaxEvent::Characters { data } => {
285 format!("SAX.characters({}, {})\n", trunc_bytes(data, 30), data.len())
286 }
287 SaxEvent::CData { data } => {
288 format!("SAX.pcdata({}, {})\n", trunc_bytes(data, 20), data.len())
289 }
290 SaxEvent::Comment(c) => format!("SAX.comment({c})\n"),
291 SaxEvent::ProcessingInstruction { target, data } => match data {
292 Some(d) => format!("SAX.processingInstruction({target}, {d})\n"),
293 None => format!("SAX.processingInstruction({target}, NULL)\n"),
294 },
295 SaxEvent::Warning(m) => format!("SAX.warning: {m}"),
296 SaxEvent::Error(m) => format!("SAX.error: {m}"),
297 }
298}
299
300#[doc(alias = "xmlSAX2InitDefaultSAXHandler")]
302pub fn xml_sax2_init_default_sax_handler() -> SaxRecorder {
303 SaxRecorder::new()
304}
305
306#[doc(alias = "xmlSAXVersion")]
308pub fn xml_sax_version() -> i32 {
309 2
310}