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