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