gpx/parser/
link.rs

1//! link handles parsing of GPX-spec links.
2
3use std::io::Read;
4
5use xml::reader::XmlEvent;
6
7use crate::errors::{GpxError, GpxResult};
8use crate::parser::{string, verify_starting_tag, Context};
9use crate::Link;
10
11/// consume consumes a GPX link from the `reader` until it ends.
12/// When it returns, the reader will be at the element after the end GPX link
13/// tag.
14pub fn consume<R: Read>(context: &mut Context<R>) -> GpxResult<Link> {
15    let mut link: Link = Default::default();
16    let attributes = verify_starting_tag(context, "link")?;
17    let attr = attributes
18        .into_iter()
19        .find(|attr| attr.name.local_name == "href");
20
21    let attr = attr.ok_or(GpxError::InvalidElementLacksAttribute("href", "link"))?;
22
23    link.href = attr.value;
24
25    loop {
26        let next_event = {
27            if let Some(next) = context.reader.peek() {
28                match next {
29                    Ok(n) => n,
30                    Err(_) => return Err(GpxError::EventParsingError("link event")),
31                }
32            } else {
33                break;
34            }
35        };
36
37        match next_event {
38            XmlEvent::StartElement { ref name, .. } => match name.local_name.as_ref() {
39                "text" => link.text = Some(string::consume(context, "text", true)?),
40                "type" => link.type_ = Some(string::consume(context, "type", true)?),
41                child => {
42                    return Err(GpxError::InvalidChildElement(String::from(child), "link"));
43                }
44            },
45            XmlEvent::EndElement { ref name } => {
46                if name.local_name != "link" {
47                    return Err(GpxError::InvalidClosingTag(name.local_name.clone(), "link"));
48                }
49                context.reader.next();
50                return Ok(link);
51            }
52            _ => {
53                context.reader.next(); //consume and ignore this event
54            }
55        }
56    }
57
58    Err(GpxError::MissingClosingTag("link"))
59}
60
61#[cfg(test)]
62mod tests {
63    use super::consume;
64    use crate::GpxVersion;
65
66    #[test]
67    fn consume_simple_link() {
68        let link = consume!(
69            "<link href='http://example.com'><text>hello</text><type>world</type></link>",
70            GpxVersion::Gpx11
71        );
72
73        assert!(link.is_ok());
74
75        let link = link.unwrap();
76
77        assert_eq!(link.href, "http://example.com");
78
79        assert!(link.text.is_some());
80        assert_eq!(link.text.unwrap(), "hello");
81
82        assert!(link.type_.is_some());
83        assert_eq!(link.type_.unwrap(), "world");
84    }
85
86    #[test]
87    fn consume_barebones() {
88        let link = consume!(
89            "<link href='http://topografix.com'></link>",
90            GpxVersion::Gpx11
91        );
92
93        assert!(link.is_ok());
94
95        let link = link.unwrap();
96
97        assert_eq!(link.href, "http://topografix.com");
98
99        assert!(link.text.is_none());
100        assert!(link.type_.is_none());
101    }
102
103    #[test]
104    fn consume_no_href() {
105        let link = consume!("<link></link>", GpxVersion::Gpx11);
106
107        assert!(link.is_err());
108    }
109
110    #[test]
111    fn consume_empty_href_text_type() {
112        let link = consume!(
113            r#"<link href=""><text></text><type></type></link>"#,
114            GpxVersion::Gpx11
115        );
116
117        assert!(link.is_ok());
118
119        let link = link.unwrap();
120
121        assert_eq!(link.href, "");
122        assert_eq!(link.text, Some(String::from("")));
123        assert_eq!(link.type_, Some(String::from("")));
124    }
125}