ttpgen/xml_manager.rs
1use crate::data_set::{CapacityConstraints, Distance, Rawdata, SeparationConstraints, Slot, Team};
2use roxmltree::Document;
3use std::fs;
4
5/// Structure responsible for managing XML file reading and parsing.
6pub struct XmlManager;
7
8impl XmlManager {
9
10 /// Reads an XML file and parses into a `Rawdata` struct.
11 ///
12 /// This function opens the specified XML file, parses it using `roxmltree`,
13 /// and fills a `Rawdata` struct with information about the instance name,
14 /// teams, slots, distances, capacity constraints, and separation constraints.
15 ///
16 /// The XML elements are mapped as follows:
17 /// - `<InstanceName>` → `Rawdata.instance_name`
18 /// - `<team>` → `Rawdata.teams`
19 /// - `<slot>` → `Rawdata.slots`
20 /// - `<distance>` → `Rawdata.distances`
21 /// - Elements starting with `"CA"` → `Rawdata.capacity_constraints`
22 /// - Elements starting with `"SE"` → `Rawdata.separation_constraints`
23 ///
24 /// # Arguments
25 /// * `path` - A string slice representing the path to the XML file.
26 ///
27 /// # Returns
28 /// A `Rawdata` struct containing all parsed information from the XML.
29 ///
30 /// # Panics
31 /// This function will panic if the XML file cannot be opened or parsed.
32 ///
33 /// # Example
34 /// ```
35 /// let raw_data = read_xml("instances/example.xml");
36 /// println!("Instance name: {}", raw_data.instance_name);
37 /// println!("Number of teams: {}", raw_data.teams.len());
38 /// ```
39 pub fn read_xml(path: &str) -> Rawdata {
40 let xml = fs::read_to_string(path).expect("Error opening XML file");
41 let doc = Document::parse(&xml).expect("Error parsing XML");
42
43 let mut raw_data = Rawdata {
44 instance_name: String::new(),
45 teams: Vec::new(),
46 slots: Vec::new(),
47 distances: Vec::new(),
48 capacity_constraints: Vec::new(),
49 separation_constraints: Vec::new(),
50 };
51
52 for node in doc.descendants().filter(|n| n.is_element()) {
53 match node.tag_name().name() {
54 "InstanceName" => {
55 if let Some(text) = node.text() {
56 raw_data.instance_name = text.to_string();
57 }
58 }
59 "team" => raw_data.teams.push(Self::parse_team(&node)),
60 "slot" => raw_data.slots.push(Self::parse_slot(&node)),
61 "distance" => raw_data.distances.push(Self::parse_distance(&node)),
62 name if name.starts_with("CA") => raw_data.capacity_constraints.push(Self::parse_capacity(&node)),
63 name if name.starts_with("SE") => raw_data.separation_constraints.push(Self::parse_separation(&node)),
64 _ => {}
65 }
66 }
67
68 raw_data
69 }
70
71 /// Parses a `<Team>` XML node and converts it into a `Team` struct.
72 ///
73 /// This function reads the attributes of the given XML node and fills the corresponding
74 /// fields in `Team`. If a numeric attribute is missing or cannot be parsed, it defaults to `0`.
75 ///
76 /// # Arguments
77 /// * `node` - A reference to a `roxmltree::Node` representing the `<Team>` element.
78 ///
79 /// # Returns
80 /// A `Team` struct populated with the parsed values.
81 ///
82 /// # Example
83 /// ```
84 /// let doc = roxmltree::Document::parse(r#"<Team id="5" league="1" name="Eagles" teamGroups="2"/>"#).unwrap();
85 /// let node = doc.root_element();
86 /// let team = parse_team(&node);
87 /// assert_eq!(team.id, 5);
88 /// assert_eq!(team.league, 1);
89 /// assert_eq!(team.name, "Eagles".to_string());
90 /// assert_eq!(team.team_groups, 2);
91 /// ```
92 fn parse_team(node: &roxmltree::Node) -> Team {
93 let mut team = Team::new();
94 for attr in node.attributes() {
95 match attr.name() {
96 "id" => team.id = attr.value().parse().unwrap_or(0),
97 "league" => team.league = attr.value().parse().unwrap_or(0),
98 "name" => team.name = attr.value().to_string(),
99 "teamGroups" => team.team_groups = attr.value().parse().unwrap_or(0),
100 _ => {}
101 }
102 }
103 team
104 }
105
106 /// Parses a `<Slot>` XML node and converts it into a `Slot` struct.
107 ///
108 /// This function reads the attributes of the given XML node and fills the corresponding
109 /// fields in `Slot`. If an attribute is missing or cannot be parsed as a number,
110 /// it defaults to `0`.
111 ///
112 /// # Arguments
113 /// * `node` - A reference to a `roxmltree::Node` representing the `<Slot>` element.
114 ///
115 /// # Returns
116 /// A `Slot` struct populated with the parsed values.
117 ///
118 /// # Example
119 /// ```
120 /// let doc = roxmltree::Document::parse(r#"<Slot id="3" name="ATL"/>"#).unwrap();
121 /// assert_eq!(doc.id, 3);
122 /// assert_eq!(doc.name, "ATL".to_string());
123 /// ```
124 fn parse_slot(node: &roxmltree::Node) -> Slot {
125 let mut slot = Slot::new();
126 for attr in node.attributes() {
127 match attr.name() {
128 "id" => slot.id = attr.value().parse().unwrap_or(0),
129 "name" => slot.name = attr.value().to_string(),
130 _ => {}
131 }
132 }
133 slot
134 }
135
136 /// Parses a `<Distance>` XML node and converts it into a `Distance` struct.
137 ///
138 /// This function reads the attributes of the given XML node and fills the corresponding
139 /// fields in `Distance`. If an attribute is missing or cannot be parsed as a number,
140 /// it defaults to `0`.
141 ///
142 /// # Arguments
143 /// * `node` - A reference to a `roxmltree::Node` representing the `<Distance>` element.
144 ///
145 /// # Returns
146 /// A `Distance` struct populated with the parsed values.
147 ///
148 /// # Example
149 /// ```
150 /// let doc = roxmltree::Document::parse(r#"<Distance dist="15" team1="2" team2="5"/>"#).unwrap();
151 /// let node = doc.root_element();
152 /// let distance = parse_distance(&node);
153 /// assert_eq!(distance.dist, 15);
154 /// assert_eq!(distance.team1, 2);
155 /// assert_eq!(distance.team2, 5);
156 /// ```
157 fn parse_distance(node: &roxmltree::Node) -> Distance {
158 let mut distance = Distance::new();
159 for attr in node.attributes() {
160 match attr.name() {
161 "dist" => distance.dist = attr.value().parse().unwrap_or(0),
162 "team1" => distance.team1 = attr.value().parse().unwrap_or(0),
163 "team2" => distance.team2 = attr.value().parse().unwrap_or(0),
164 _ => {}
165 }
166 }
167 distance
168 }
169
170 /// Parses a `<CapacityConstraints>` XML node and converts it into a `CapacityConstraints` struct.
171 ///
172 /// This function reads the attributes of the given XML node and fills the corresponding
173 /// fields in `CapacityConstraints`. If an attribute is missing or cannot be parsed,
174 /// numeric fields default to `0`.
175 ///
176 /// # Arguments
177 /// * `node` - A reference to a `roxmltree::Node` representing the `<CapacityConstraints>` element.
178 ///
179 /// # Returns
180 /// A `CapacityConstraints` struct populated with the parsed values.
181 ///
182 /// # Example
183 /// ```
184 /// let doc = roxmltree::Document::parse(r#"<Capacity intp="2" max="5" min="1" mode1="H" mode2="A" penalty="10" teamGroups1="3" teamGroups2="2" type="hard"/>"#).unwrap();
185 /// let node = doc.root_element();
186 /// let capacity = parse_capacity(&node);
187 /// assert_eq!(capacity.c_intp, 2);
188 /// assert_eq!(capacity.c_type, "hard".to_string());
189 /// ```
190 fn parse_capacity(node: &roxmltree::Node) -> CapacityConstraints {
191 let mut cap = CapacityConstraints::new();
192 for attr in node.attributes() {
193 match attr.name() {
194 "intp" => cap.c_intp = attr.value().parse().unwrap_or(0),
195 "max" => cap.c_max = attr.value().parse().unwrap_or(0),
196 "min" => cap.c_min = attr.value().parse().unwrap_or(0),
197 "mode1" => cap.c_mode1 = attr.value().chars().next().unwrap_or('n'),
198 "mode2" => cap.c_mode2 = attr.value().to_string(),
199 "penalty" => cap.c_penalty = attr.value().parse().unwrap_or(0),
200 "teamGroups1" => cap.c_team_groups1 = attr.value().parse().unwrap_or(0),
201 "teamGroups2" => cap.c_team_groups2 = attr.value().parse().unwrap_or(0),
202 "type" => cap.c_type = attr.value().to_string(),
203 _ => {}
204 }
205 }
206 cap
207 }
208
209 /// Parses a `<SeparationConstraint>` XML node and converts it into a `SeparationConstraints` struct.
210 ///
211 /// This function reads the attributes of the given XML node and fills the corresponding
212 /// fields in `SeparationConstraints`. If an attribute is missing or cannot be parsed
213 /// as a number, it defaults to `0`.
214 ///
215 /// # Arguments
216 /// * `node` - A reference to a `roxmltree::Node` representing the `<SeparationConstraint>` element.
217 ///
218 /// # Returns
219 /// A `SeparationConstraints` struct populated with the parsed values.
220 ///
221 /// # Example
222 /// ```
223 /// let doc = roxmltree::Document::parse(r#"<Separation max="3" min="1" penalty="5" teamGroups="2" type="soft"/>"#).unwrap();
224 /// let node = doc.root_element();
225 /// let separation = parse_separation(&node);
226 /// assert_eq!(separation.c_max, 3);
227 /// assert_eq!(separation.c_type, "soft".to_string());
228 /// ```
229 fn parse_separation(node: &roxmltree::Node) -> SeparationConstraints {
230 let mut sep = SeparationConstraints::new();
231 for attr in node.attributes() {
232 match attr.name() {
233 "max" => sep.c_max = attr.value().parse().unwrap_or(0),
234 "min" => sep.c_min = attr.value().parse().unwrap_or(0),
235 "penalty" => sep.c_penalty = attr.value().parse().unwrap_or(0),
236 "teamGroups" => sep.c_team_groups = attr.value().parse().unwrap_or(0),
237 "type" => sep.c_type = attr.value().to_string(),
238 _ => {}
239 }
240 }
241 sep
242 }
243}
244