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}