openscenario_rs/catalog/
loader.rs1use crate::error::{Error, Result};
10use crate::parser::xml::{parse_catalog_from_file, parse_catalog_from_str};
11use crate::types::basic::Directory;
12use crate::types::catalogs::entities::{CatalogController, CatalogPedestrian, CatalogVehicle};
13use crate::types::catalogs::files::CatalogFile;
14use std::fs;
15use std::path::{Path, PathBuf};
16
17pub struct CatalogLoader {
19 base_path: Option<PathBuf>,
21}
22
23impl CatalogLoader {
24 pub fn new() -> Self {
26 Self { base_path: None }
27 }
28
29 pub fn with_base_path<P: AsRef<Path>>(base_path: P) -> Self {
31 Self {
32 base_path: Some(base_path.as_ref().to_path_buf()),
33 }
34 }
35
36 pub fn set_base_path<P: AsRef<Path>>(&mut self, base_path: P) {
38 self.base_path = Some(base_path.as_ref().to_path_buf());
39 }
40
41 pub fn discover_catalog_files(&self, directory: &Directory) -> Result<Vec<PathBuf>> {
43 let path_str = directory.path.as_literal().ok_or_else(|| {
44 Error::invalid_value(
45 "directory.path",
46 "parameterized path",
47 "literal path required",
48 )
49 })?;
50
51 let dir_path = self.resolve_path(path_str)?;
52
53 if !dir_path.exists() {
54 return Err(Error::directory_not_found(&dir_path.to_string_lossy()));
55 }
56
57 if !dir_path.is_dir() {
58 return Err(Error::invalid_value(
59 "directory.path",
60 &dir_path.to_string_lossy(),
61 "path must be a directory",
62 ));
63 }
64
65 let mut catalog_files = Vec::new();
66
67 for entry in fs::read_dir(&dir_path)
68 .map_err(|e| Error::file_read_error(&dir_path.to_string_lossy(), &e.to_string()))?
69 {
70 let entry = entry
71 .map_err(|e| Error::file_read_error(&dir_path.to_string_lossy(), &e.to_string()))?;
72
73 let path = entry.path();
74
75 if path.is_file() {
76 if let Some(extension) = path.extension() {
77 if extension == "xosc" {
78 catalog_files.push(path);
79 }
80 }
81 }
82 }
83
84 catalog_files.sort();
85 Ok(catalog_files)
86 }
87
88 pub fn load_catalog_file<P: AsRef<Path>>(&self, file_path: P) -> Result<String> {
90 let path = file_path.as_ref();
91
92 if !path.exists() {
93 return Err(Error::file_not_found(&path.to_string_lossy()));
94 }
95
96 fs::read_to_string(path)
97 .map_err(|e| Error::file_read_error(&path.to_string_lossy(), &e.to_string()))
98 }
99
100 pub fn load_and_parse_catalog_file<P: AsRef<Path>>(&self, file_path: P) -> Result<CatalogFile> {
102 let path = file_path.as_ref();
103
104 if !path.exists() {
105 return Err(Error::catalog_error(&format!(
106 "Catalog file does not exist: {}",
107 path.display()
108 )));
109 }
110
111 parse_catalog_from_file(path).map_err(|e| {
112 e.with_context(&format!("Failed to parse catalog file: {}", path.display()))
113 })
114 }
115
116 pub fn parse_catalog_from_string(&self, xml: &str) -> Result<CatalogFile> {
118 parse_catalog_from_str(xml)
119 .map_err(|e| e.with_context("Failed to parse catalog from string"))
120 }
121
122 pub fn load_vehicle_catalogs(&self, directory: &Directory) -> Result<Vec<CatalogVehicle>> {
124 let catalog_files = self.discover_catalog_files(directory)?;
125 let mut vehicles = Vec::new();
126
127 for file_path in catalog_files {
128 let catalog = self.load_and_parse_catalog_file(&file_path)?;
129 vehicles.extend(catalog.vehicles().iter().cloned());
130 }
131
132 Ok(vehicles)
133 }
134
135 pub fn load_controller_catalogs(
137 &self,
138 directory: &Directory,
139 ) -> Result<Vec<CatalogController>> {
140 let catalog_files = self.discover_catalog_files(directory)?;
141 let mut controllers = Vec::new();
142
143 for file_path in catalog_files {
144 let catalog = self.load_and_parse_catalog_file(&file_path)?;
145 controllers.extend(catalog.controllers().iter().cloned());
146 }
147
148 Ok(controllers)
149 }
150
151 pub fn load_pedestrian_catalogs(
153 &self,
154 directory: &Directory,
155 ) -> Result<Vec<CatalogPedestrian>> {
156 let catalog_files = self.discover_catalog_files(directory)?;
157 let mut pedestrians = Vec::new();
158
159 for file_path in catalog_files {
160 let catalog = self.load_and_parse_catalog_file(&file_path)?;
161 pedestrians.extend(catalog.pedestrians().iter().cloned());
162 }
163
164 Ok(pedestrians)
165 }
166
167 pub fn find_entity_in_catalog<P: AsRef<Path>>(
169 &self,
170 file_path: P,
171 entity_name: &str,
172 ) -> Result<Option<String>> {
173 let catalog = self.load_and_parse_catalog_file(file_path)?;
174
175 let entity_names = catalog.catalog.entity_names();
177 if entity_names.contains(&entity_name.to_string()) {
178 Ok(Some(entity_name.to_string()))
179 } else {
180 Ok(None)
181 }
182 }
183
184 pub fn load_controller_catalog<P: AsRef<Path>>(
186 &self,
187 file_path: P,
188 ) -> Result<crate::types::catalogs::controllers::ControllerCatalog> {
189 let path = file_path.as_ref();
190 if !path.exists() {
191 return Err(Error::file_not_found(&path.to_string_lossy()));
192 }
193
194 let xml_content = fs::read_to_string(path)
195 .map_err(|e| Error::file_read_error(&path.to_string_lossy(), &e.to_string()))?;
196
197 quick_xml::de::from_str(&xml_content)
198 .map_err(|e| Error::parse_error(&path.to_string_lossy(), &e.to_string()))
199 }
200
201 pub fn load_trajectory_catalog<P: AsRef<Path>>(
203 &self,
204 file_path: P,
205 ) -> Result<crate::types::catalogs::trajectories::TrajectoryCatalog> {
206 let path = file_path.as_ref();
207 if !path.exists() {
208 return Err(Error::file_not_found(&path.to_string_lossy()));
209 }
210
211 let xml_content = fs::read_to_string(path)
212 .map_err(|e| Error::file_read_error(&path.to_string_lossy(), &e.to_string()))?;
213
214 quick_xml::de::from_str(&xml_content)
215 .map_err(|e| Error::parse_error(&path.to_string_lossy(), &e.to_string()))
216 }
217
218 pub fn load_route_catalog<P: AsRef<Path>>(
220 &self,
221 file_path: P,
222 ) -> Result<crate::types::catalogs::routes::RouteCatalog> {
223 let path = file_path.as_ref();
224 if !path.exists() {
225 return Err(Error::file_not_found(&path.to_string_lossy()));
226 }
227
228 let xml_content = fs::read_to_string(path)
229 .map_err(|e| Error::file_read_error(&path.to_string_lossy(), &e.to_string()))?;
230
231 quick_xml::de::from_str(&xml_content)
232 .map_err(|e| Error::parse_error(&path.to_string_lossy(), &e.to_string()))
233 }
234
235 pub fn load_environment_catalog<P: AsRef<Path>>(
237 &self,
238 file_path: P,
239 ) -> Result<crate::types::catalogs::environments::EnvironmentCatalog> {
240 let path = file_path.as_ref();
241 if !path.exists() {
242 return Err(Error::file_not_found(&path.to_string_lossy()));
243 }
244
245 let xml_content = fs::read_to_string(path)
246 .map_err(|e| Error::file_read_error(&path.to_string_lossy(), &e.to_string()))?;
247
248 quick_xml::de::from_str(&xml_content)
249 .map_err(|e| Error::parse_error(&path.to_string_lossy(), &e.to_string()))
250 }
251
252 pub fn load_controller_catalogs_from_directory(
254 &self,
255 directory: &Directory,
256 ) -> Result<
257 std::collections::HashMap<String, crate::types::catalogs::controllers::ControllerCatalog>,
258 > {
259 let catalog_files = self.discover_catalog_files(directory)?;
260 let mut catalogs = std::collections::HashMap::new();
261
262 for file_path in catalog_files {
263 if let Ok(catalog) = self.load_controller_catalog(&file_path) {
264 let catalog_name = file_path
265 .file_stem()
266 .and_then(|s| s.to_str())
267 .unwrap_or("unknown")
268 .to_string();
269 catalogs.insert(catalog_name, catalog);
270 }
271 }
272
273 Ok(catalogs)
274 }
275
276 pub fn load_trajectory_catalogs_from_directory(
278 &self,
279 directory: &Directory,
280 ) -> Result<
281 std::collections::HashMap<String, crate::types::catalogs::trajectories::TrajectoryCatalog>,
282 > {
283 let catalog_files = self.discover_catalog_files(directory)?;
284 let mut catalogs = std::collections::HashMap::new();
285
286 for file_path in catalog_files {
287 if let Ok(catalog) = self.load_trajectory_catalog(&file_path) {
288 let catalog_name = file_path
289 .file_stem()
290 .and_then(|s| s.to_str())
291 .unwrap_or("unknown")
292 .to_string();
293 catalogs.insert(catalog_name, catalog);
294 }
295 }
296
297 Ok(catalogs)
298 }
299
300 pub fn load_route_catalogs_from_directory(
302 &self,
303 directory: &Directory,
304 ) -> Result<std::collections::HashMap<String, crate::types::catalogs::routes::RouteCatalog>>
305 {
306 let catalog_files = self.discover_catalog_files(directory)?;
307 let mut catalogs = std::collections::HashMap::new();
308
309 for file_path in catalog_files {
310 if let Ok(catalog) = self.load_route_catalog(&file_path) {
311 let catalog_name = file_path
312 .file_stem()
313 .and_then(|s| s.to_str())
314 .unwrap_or("unknown")
315 .to_string();
316 catalogs.insert(catalog_name, catalog);
317 }
318 }
319
320 Ok(catalogs)
321 }
322
323 pub fn load_environment_catalogs_from_directory(
325 &self,
326 directory: &Directory,
327 ) -> Result<
328 std::collections::HashMap<String, crate::types::catalogs::environments::EnvironmentCatalog>,
329 > {
330 let catalog_files = self.discover_catalog_files(directory)?;
331 let mut catalogs = std::collections::HashMap::new();
332
333 for file_path in catalog_files {
334 if let Ok(catalog) = self.load_environment_catalog(&file_path) {
335 let catalog_name = file_path
336 .file_stem()
337 .and_then(|s| s.to_str())
338 .unwrap_or("unknown")
339 .to_string();
340 catalogs.insert(catalog_name, catalog);
341 }
342 }
343
344 Ok(catalogs)
345 }
346
347 fn resolve_path(&self, path: &str) -> Result<PathBuf> {
349 let path = Path::new(path);
350
351 if path.is_absolute() {
352 Ok(path.to_path_buf())
353 } else if let Some(base) = &self.base_path {
354 Ok(base.join(path))
355 } else {
356 Ok(std::env::current_dir()?.join(path))
358 }
359 }
360}
361
362impl Default for CatalogLoader {
363 fn default() -> Self {
364 Self::new()
365 }
366}
367
368#[cfg(test)]
369mod tests {
370 use super::*;
371
372 use std::fs;
373 use tempfile::TempDir;
374
375 #[test]
376 fn test_catalog_loader_creation() {
377 let loader = CatalogLoader::new();
378 assert!(loader.base_path.is_none());
379
380 let loader_with_path = CatalogLoader::with_base_path("/tmp");
381 assert_eq!(
382 loader_with_path.base_path.as_ref().unwrap(),
383 Path::new("/tmp")
384 );
385 }
386
387 #[test]
388 fn test_discover_catalog_files() -> Result<()> {
389 let temp_dir = TempDir::new().unwrap();
391 let dir_path = temp_dir.path();
392
393 fs::write(dir_path.join("catalog1.xosc"), "catalog1 content")?;
395 fs::write(dir_path.join("catalog2.xosc"), "catalog2 content")?;
396 fs::write(dir_path.join("not_catalog.txt"), "not a catalog")?;
397
398 let directory = Directory::new(dir_path.to_string_lossy().to_string());
399 let loader = CatalogLoader::new();
400
401 let files = loader.discover_catalog_files(&directory)?;
402 assert_eq!(files.len(), 2);
403
404 assert!(files[0]
406 .file_name()
407 .unwrap()
408 .to_string_lossy()
409 .starts_with("catalog"));
410 assert!(files[1]
411 .file_name()
412 .unwrap()
413 .to_string_lossy()
414 .starts_with("catalog"));
415
416 Ok(())
417 }
418
419 #[test]
420 fn test_load_catalog_file() -> Result<()> {
421 let temp_dir = TempDir::new().unwrap();
422 let file_path = temp_dir.path().join("test_catalog.xosc");
423 let content = "<?xml version=\"1.0\"?>\n<OpenSCENARIO>test</OpenSCENARIO>";
424
425 fs::write(&file_path, &content).unwrap();
426
427 let loader = CatalogLoader::new();
428 let loaded_content = loader.load_catalog_file(&file_path)?;
429 assert_eq!(loaded_content, content);
430
431 Ok(())
432 }
433
434 #[test]
435 fn test_parse_catalog_from_string() {
436 let loader = CatalogLoader::new();
437
438 let catalog_xml = r#"<?xml version="1.0"?>
440 <OpenSCENARIO>
441 <FileHeader author="Test" date="2024-01-01T00:00:00" description="Test" revMajor="1" revMinor="3"/>
442 <Catalog name="TestCatalog">
443 </Catalog>
444 </OpenSCENARIO>"#;
445
446 let catalog = loader.parse_catalog_from_string(catalog_xml).unwrap();
447 assert_eq!(catalog.catalog_name().as_literal().unwrap(), "TestCatalog");
448 assert_eq!(catalog.file_header.author.as_literal().unwrap(), "Test");
449 assert_eq!(catalog.catalog.entity_count(), 0);
450 }
451}