1 extern crate xml;
2
3 use std::env;
4 use std::fmt;
5 use std::fs::File;
6 use std::io::{BufRead, BufReader, Write, stderr};
7 use std::path::Path;
8 use std::sync::{Once, ONCE_INIT};
9
10 use xml::name::OwnedName;
11 use xml::common::Position;
12 use xml::reader::{Result, XmlEvent, ParserConfig, EventReader};
13
14 /// Dummy function that opens a file, parses it, and returns a `Result`.
15 /// There can be IO errors (from `File::open`) and XML errors (from the parser).
16 /// Having `impl From<std::io::Error> for xml::reader::Error` allows the user to
17 /// do this without defining their own error type.
18 #[allow(dead_code)]
count_event_in_file(name: &Path) -> Result<usize>19 fn count_event_in_file(name: &Path) -> Result<usize> {
20 let mut event_count = 0;
21 for event in EventReader::new(BufReader::new(try!(File::open(name)))) {
22 try!(event);
23 event_count += 1;
24 }
25 Ok(event_count)
26 }
27
28 #[test]
sample_1_short()29 fn sample_1_short() {
30 test(
31 include_bytes!("documents/sample_1.xml"),
32 include_bytes!("documents/sample_1_short.txt"),
33 ParserConfig::new()
34 .ignore_comments(true)
35 .whitespace_to_characters(true)
36 .cdata_to_characters(true)
37 .trim_whitespace(true)
38 .coalesce_characters(true),
39 false
40 );
41 }
42
43 #[test]
sample_1_full()44 fn sample_1_full() {
45 test(
46 include_bytes!("documents/sample_1.xml"),
47 include_bytes!("documents/sample_1_full.txt"),
48 ParserConfig::new()
49 .ignore_comments(false)
50 .whitespace_to_characters(false)
51 .cdata_to_characters(false)
52 .trim_whitespace(false)
53 .coalesce_characters(false),
54 false
55 );
56 }
57
58 #[test]
sample_2_short()59 fn sample_2_short() {
60 test(
61 include_bytes!("documents/sample_2.xml"),
62 include_bytes!("documents/sample_2_short.txt"),
63 ParserConfig::new()
64 .ignore_comments(true)
65 .whitespace_to_characters(true)
66 .cdata_to_characters(true)
67 .trim_whitespace(true)
68 .coalesce_characters(true),
69 false
70 );
71 }
72
73 #[test]
sample_2_full()74 fn sample_2_full() {
75 test(
76 include_bytes!("documents/sample_2.xml"),
77 include_bytes!("documents/sample_2_full.txt"),
78 ParserConfig::new()
79 .ignore_comments(false)
80 .whitespace_to_characters(false)
81 .cdata_to_characters(false)
82 .trim_whitespace(false)
83 .coalesce_characters(false),
84 false
85 );
86 }
87
88 #[test]
sample_3_short()89 fn sample_3_short() {
90 test(
91 include_bytes!("documents/sample_3.xml"),
92 include_bytes!("documents/sample_3_short.txt"),
93 ParserConfig::new()
94 .ignore_comments(true)
95 .whitespace_to_characters(true)
96 .cdata_to_characters(true)
97 .trim_whitespace(true)
98 .coalesce_characters(true),
99 true
100 );
101 }
102
103 #[test]
sample_3_full()104 fn sample_3_full() {
105 test(
106 include_bytes!("documents/sample_3.xml"),
107 include_bytes!("documents/sample_3_full.txt"),
108 ParserConfig::new()
109 .ignore_comments(false)
110 .whitespace_to_characters(false)
111 .cdata_to_characters(false)
112 .trim_whitespace(false)
113 .coalesce_characters(false),
114 true
115 );
116 }
117
118 #[test]
sample_4_short()119 fn sample_4_short() {
120 test(
121 include_bytes!("documents/sample_4.xml"),
122 include_bytes!("documents/sample_4_short.txt"),
123 ParserConfig::new()
124 .ignore_comments(true)
125 .whitespace_to_characters(true)
126 .cdata_to_characters(true)
127 .trim_whitespace(true)
128 .coalesce_characters(true),
129 false
130 );
131 }
132
133 #[test]
sample_4_full()134 fn sample_4_full() {
135 test(
136 include_bytes!("documents/sample_4.xml"),
137 include_bytes!("documents/sample_4_full.txt"),
138 ParserConfig::new()
139 .ignore_comments(false)
140 .whitespace_to_characters(false)
141 .cdata_to_characters(false)
142 .trim_whitespace(false)
143 .coalesce_characters(false),
144 false
145 );
146
147 }
148
149 #[test]
sample_5_short()150 fn sample_5_short() {
151 test(
152 include_bytes!("documents/sample_5.xml"),
153 include_bytes!("documents/sample_5_short.txt"),
154 ParserConfig::new()
155 .ignore_comments(true)
156 .whitespace_to_characters(true)
157 .cdata_to_characters(true)
158 .trim_whitespace(true)
159 .coalesce_characters(true)
160 .add_entity("nbsp", " ")
161 .add_entity("copy", "©")
162 .add_entity("NotEqualTilde", "≂̸"),
163 false
164 );
165 }
166
167 #[test]
eof_1()168 fn eof_1() {
169 test(
170 br#"<?xml"#,
171 br#"1:6 Unexpected end of stream: no root element found"#,
172 ParserConfig::new(),
173 false
174 );
175 }
176
177 #[test]
bad_1()178 fn bad_1() {
179 test(
180 br#"<?xml&.,"#,
181 br#"1:6 Unexpected token: <?xml&"#,
182 ParserConfig::new(),
183 false
184 );
185 }
186
187 #[test]
dashes_in_comments()188 fn dashes_in_comments() {
189 test(
190 br#"<!-- comment -- --><hello/>"#,
191 br#"
192 |1:14 Unexpected token '--' before ' '
193 "#,
194 ParserConfig::new(),
195 false
196 );
197
198 test(
199 br#"<!-- comment ---><hello/>"#,
200 br#"
201 |1:14 Unexpected token '--' before '-'
202 "#,
203 ParserConfig::new(),
204 false
205 );
206 }
207
208 #[test]
tabs_1()209 fn tabs_1() {
210 test(
211 b"\t<a>\t<b/></a>",
212 br#"
213 |1:2 StartDocument(1.0, UTF-8)
214 |1:2 StartElement(a)
215 |1:6 StartElement(b)
216 |1:6 EndElement(b)
217 |1:10 EndElement(a)
218 |1:14 EndDocument
219 "#,
220 ParserConfig::new()
221 .trim_whitespace(true),
222 true
223 );
224 }
225
226 #[test]
issue_83_duplicate_attributes()227 fn issue_83_duplicate_attributes() {
228 test(
229 br#"<hello><some-tag a='10' a="20"></hello>"#,
230 br#"
231 |StartDocument(1.0, UTF-8)
232 |StartElement(hello)
233 |1:30 Attribute 'a' is redefined
234 "#,
235 ParserConfig::new(),
236 false
237 );
238 }
239
240 #[test]
issue_93_large_characters_in_entity_references()241 fn issue_93_large_characters_in_entity_references() {
242 test(
243 r#"<hello>&;</hello>"#.as_bytes(),
244 r#"
245 |StartDocument(1.0, UTF-8)
246 |StartElement(hello)
247 |1:10 Unexpected entity:
248 "#.as_bytes(), // FIXME: it shouldn't be 10, looks like indices are off slightly
249 ParserConfig::new(),
250 false
251 )
252 }
253
254 #[test]
issue_98_cdata_ending_with_right_bracket()255 fn issue_98_cdata_ending_with_right_bracket() {
256 test(
257 br#"<hello><![CDATA[Foo [Bar]]]></hello>"#,
258 br#"
259 |StartDocument(1.0, UTF-8)
260 |StartElement(hello)
261 |CData("Foo [Bar]")
262 |EndElement(hello)
263 |EndDocument
264 "#,
265 ParserConfig::new(),
266 false
267 )
268 }
269
270 #[test]
issue_105_unexpected_double_dash()271 fn issue_105_unexpected_double_dash() {
272 test(
273 br#"<hello>-- </hello>"#,
274 br#"
275 |StartDocument(1.0, UTF-8)
276 |StartElement(hello)
277 |Characters("-- ")
278 |EndElement(hello)
279 |EndDocument
280 "#,
281 ParserConfig::new(),
282 false
283 );
284
285 test(
286 br#"<hello>--</hello>"#,
287 br#"
288 |StartDocument(1.0, UTF-8)
289 |StartElement(hello)
290 |Characters("--")
291 |EndElement(hello)
292 |EndDocument
293 "#,
294 ParserConfig::new(),
295 false
296 );
297
298 test(
299 br#"<hello>--></hello>"#,
300 br#"
301 |StartDocument(1.0, UTF-8)
302 |StartElement(hello)
303 |Characters("-->")
304 |EndElement(hello)
305 |EndDocument
306 "#,
307 ParserConfig::new(),
308 false
309 );
310
311 test(
312 br#"<hello><![CDATA[--]]></hello>"#,
313 br#"
314 |StartDocument(1.0, UTF-8)
315 |StartElement(hello)
316 |CData("--")
317 |EndElement(hello)
318 |EndDocument
319 "#,
320 ParserConfig::new(),
321 false
322 );
323 }
324
325 #[test]
issue_attribues_have_no_default_namespace()326 fn issue_attribues_have_no_default_namespace () {
327 test(
328 br#"<hello xmlns="urn:foo" x="y"/>"#,
329 br#"
330 |StartDocument(1.0, UTF-8)
331 |StartElement({urn:foo}hello [x="y"])
332 |EndElement({urn:foo}hello)
333 |EndDocument
334 "#,
335 ParserConfig::new(),
336 false
337 );
338 }
339
340
341 static START: Once = ONCE_INIT;
342 static mut PRINT: bool = false;
343
344 // clones a lot but that's fine
trim_until_bar(s: String) -> String345 fn trim_until_bar(s: String) -> String {
346 match s.trim() {
347 ts if ts.starts_with('|') => return ts[1..].to_owned(),
348 _ => {}
349 }
350 s
351 }
352
test(input: &[u8], output: &[u8], config: ParserConfig, test_position: bool)353 fn test(input: &[u8], output: &[u8], config: ParserConfig, test_position: bool) {
354 // If PRINT_SPEC env variable is set, print the lines
355 // to stderr instead of comparing with the output
356 // it can be used like this:
357 // PRINT_SPEC=1 cargo test --test event_reader sample_1_full 2> sample_1_full.txt
358 START.call_once(|| {
359 for (key, value) in env::vars() {
360 if key == "PRINT_SPEC" && value == "1" {
361 unsafe { PRINT = true; }
362 }
363 }
364 });
365
366 let mut reader = config.create_reader(input);
367 let mut spec_lines = BufReader::new(output).lines()
368 .map(|line| line.unwrap())
369 .enumerate()
370 .map(|(i, line)| (i, trim_until_bar(line)))
371 .filter(|&(_, ref line)| !line.trim().is_empty());
372
373 loop {
374 let e = reader.next();
375 let line =
376 if test_position {
377 format!("{} {}", reader.position(), Event(&e))
378 } else {
379 format!("{}", Event(&e))
380 };
381
382 if unsafe { PRINT } {
383 writeln!(&mut stderr(), "{}", line).unwrap();
384 } else {
385 if let Some((n, spec)) = spec_lines.next() {
386 if line != spec {
387 const SPLITTER: &'static str = "-------------------";
388 panic!("\n{}\nUnexpected event at line {}:\nExpected: {}\nFound: {}\n{}\n",
389 SPLITTER, n + 1, spec, line, SPLITTER);
390 }
391 } else {
392 panic!("Unexpected event: {}", line);
393 }
394 }
395
396 match e {
397 Ok(XmlEvent::EndDocument) | Err(_) => break,
398 _ => {},
399 }
400 }
401 }
402
403 // Here we define our own string representation of events so we don't depend
404 // on the specifics of Display implementation for XmlEvent and OwnedName.
405
406 struct Name<'a>(&'a OwnedName);
407
408 impl <'a> fmt::Display for Name<'a> {
fmt(&self, f: &mut fmt::Formatter) -> fmt::Result409 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
410 if let Some(ref namespace) = self.0.namespace {
411 try! { write!(f, "{{{}}}", namespace) }
412 }
413
414 if let Some(ref prefix) = self.0.prefix {
415 try! { write!(f, "{}:", prefix) }
416 }
417
418 write!(f, "{}", self.0.local_name)
419 }
420 }
421
422 struct Event<'a>(&'a Result<XmlEvent>);
423
424 impl<'a> fmt::Display for Event<'a> {
fmt(&self, f: &mut fmt::Formatter) -> fmt::Result425 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
426 let empty = String::new();
427 match *self.0 {
428 Ok(ref e) => match *e {
429 XmlEvent::StartDocument { ref version, ref encoding, .. } =>
430 write!(f, "StartDocument({}, {})", version, encoding),
431 XmlEvent::EndDocument =>
432 write!(f, "EndDocument"),
433 XmlEvent::ProcessingInstruction { ref name, ref data } =>
434 write!(f, "ProcessingInstruction({}={:?})", name,
435 data.as_ref().unwrap_or(&empty)),
436 XmlEvent::StartElement { ref name, ref attributes, .. } => {
437 if attributes.is_empty() {
438 write!(f, "StartElement({})", Name(name))
439 }
440 else {
441 let attrs: Vec<_> = attributes.iter()
442 .map(|a| format!("{}={:?}", Name(&a.name), a.value)) .collect();
443 write!(f, "StartElement({} [{}])", Name(name), attrs.join(", "))
444 }
445 },
446 XmlEvent::EndElement { ref name } =>
447 write!(f, "EndElement({})", Name(name)),
448 XmlEvent::Comment(ref data) =>
449 write!(f, "Comment({:?})", data),
450 XmlEvent::CData(ref data) =>
451 write!(f, "CData({:?})", data),
452 XmlEvent::Characters(ref data) =>
453 write!(f, "Characters({:?})", data),
454 XmlEvent::Whitespace(ref data) =>
455 write!(f, "Whitespace({:?})", data),
456 },
457 Err(ref e) => e.fmt(f),
458 }
459 }
460 }
461