event_calendar/
event_calendar.rs

1use std::{fs::File, io::Error};
2
3use spex::{common::XmlError, parsing::XmlReader, xml::Element, xml::XmlDocument};
4
5fn main() -> Result<(), String> {
6    // Attempt to produce an XmlDocument from the XML within the specified file.
7    match read_file_as_xml("examples/EventCalendar.xml") {
8        Err(file_err) => Err(format!(
9            "Something went wrong while trying to read the XML file: {}.",
10            file_err
11        )),
12        Ok(xml_doc) => {
13            // Grab the root Element from the document. This will represent the "CalendarRS" root
14            // element of the XML document within the file.
15            let root = xml_doc.root();
16
17            // Now let's move extraction into a new method, so that it can handle the new type of
18            // Result error (XmlError), because this main method is only expecting to emit errors of
19            // type &str, and that will stop us using the question-mark syntax to handle
20            // XmlError errors returned by the methods offered by the Element type.
21            match print_events_to_console(root) {
22                Ok(()) => {
23                    println!("Extraction completed without error!");
24                }
25                Err(extract_error) => {
26                    return Err(format!(
27                        "Something was not as expected while extracting from the XML tree: {}",
28                        extract_error
29                    ));
30                }
31            }
32
33            Ok(())
34        }
35    }
36}
37
38fn read_file_as_xml(filename: &str) -> Result<XmlDocument, Error> {
39    // Open the file which contains the XML content.
40    let xml_file = File::open(filename)?;
41    // Now use XmlReader to parse the XML content of the file and produce an XmlDocument.
42    let xml_doc = XmlReader::parse_auto(xml_file)?;
43    Ok(xml_doc)
44}
45
46fn print_events_to_console(root: &Element) -> Result<(), XmlError> {
47    let event_list = root.req("EventList").element()?;
48    for event in event_list.elements().filter(|e| e.is_named("Event")) {
49        // The XML unfortunately uses different element names for different event types, but
50        // the XML structure within each is identical. So just check which element name we
51        // find and then hold that element in a non-specifically named `event_type` variable.
52        let event_type = if let Some(journey) = event.opt("Journey").element() {
53            println!("#########################");
54            println!("Found a journey event!");
55            journey
56        } else if let Some(festival) = event.opt("Festival").element() {
57            println!("#########################");
58            println!("Found a festival event!");
59            festival
60        } else if let Some(club) = event.opt("Club").element() {
61            println!("#########################");
62            println!("Found a club event!");
63            club
64        } else {
65            println!("######################");
66            println!("Found an unexpected event type!");
67            return Ok(());
68        };
69
70        let name = event.req("Name").text()?;
71        println!("Event name: {}", name);
72
73        // Now we want to grab the (initial) location and start date. The XML has a deep
74        // structure, forcing us to descend into several child elements to get to the actual
75        // data. Rather than capturing each element in a variable, we can just chain together
76        // calls to `req` to reach down to the element of interest. Note that according to the
77        // design of this XML document, both the "Start" and "From" elements are required, so
78        // we use `req` to point to each of them. Because this chain of cursor methods involves
79        // `req`, we put a `?` after the call to `element()` so that if any of the required
80        // elements is not found then an error will be thrown.
81        let start_from = event_type.req("Start").req("From").element()?;
82
83        // Now that we're holding the "From" element, and we just want the text from the two
84        // child elements, we can use the `req` then `text()` methods to grab the text content
85        // (or throw an error), without bothering to assign the elements to new variables.
86        let start_location = start_from.req("Location").text()?;
87        println!("Start location is {}", start_location);
88        let start_date = start_from.req("Date").text()?;
89        println!("Start date is {}", start_date);
90
91        // According to the design of this XML document, the "End" element is optional, so we
92        // now need to use the `opt` method to point to it. But if the "End" element exists,
93        // then it must contain an "At" method, so we use `req` to point to the "At" method to
94        // indicate that we consider it an error if "At" is not found within "End". Because this
95        // chain of cursor methods involves both `opt` and `req` we need the `?` after
96        // `element()` to throw an error if any required element is missing, and we also need to
97        // use `if let Some(end_at)` to skip this block of code if any optional element is
98        // missing.
99        if let Some(end_at) = event_type.opt("End").req("At").element()? {
100            let end_location = end_at.req("Location").text()?;
101            println!("End location is {}", end_location);
102            let end_date = end_at.req("Date").text()?;
103            println!("End date is {}", end_date);
104        }
105
106        // The XML document design inexplicably states that "Availability" and all of its
107        // descendants are optional, so we only need to use `opt`. Because we're only using
108        // `opt` we need to use `if let Some(availability)` to skip this code if any of the
109        // elements is missing, but we do not need to use `?` after `element()` because it is
110        // not an error if optional elements are missing (and `req` is not used in this method
111        // chain).
112        if let Some(availability) = event
113            .opt("Availability")
114            .opt("RemainingAvailability")
115            .element()
116        {
117            // Note, however, that we always have to use `?` after `text()` because that method
118            // will return an error if the content of the element contains child elements or
119            // processing instructions.
120            if let Some(min_avail) = availability.opt("MinQty").text()? {
121                println!("Only {} tickets remaining! Buy now!!!", min_avail);
122            }
123        }
124
125        // Price is also nested absurdly deeply. The entire branch is required by the XML design
126        // so just use `req` all the way, and don't forget the `?` after `element()` to check
127        // for an error outcome.
128        let price = event
129            .req("Price")
130            .req("PerPerson")
131            .req("IncTax")
132            .element()?;
133        println!(
134            "The price is {}{} per person, inclusive of taxes.",
135            price.req("Amount").text()?,
136            price.req("Currency").text()?
137        );
138
139        println!(
140            "The supplier name is: {}",
141            event.req("Supplier").req("Company").req("Name").text()?
142        );
143    }
144    Ok(())
145}