1use std::path::Path;
4
5use xot::Xot;
6
7#[derive(Debug, Clone, PartialEq, Eq)]
9pub struct CatalogEntry {
10 pub namespace: String,
12 pub path: String,
14}
15
16pub fn parse_catalog(catalog_path: &Path) -> Result<Vec<CatalogEntry>, crate::Error> {
24 let content = std::fs::read_to_string(catalog_path)?;
25 let mut xot = Xot::new();
26 let doc = xot.parse(&content).map_err(xot::Error::from)?;
27 let root = xot.document_element(doc)?;
28
29 let catalog_ns = xot.add_namespace("urn:oasis:names:tc:entity:xmlns:xml:catalog");
30 let uri_el = xot.add_name_ns("uri", catalog_ns);
31 let name_attr = xot.add_name("name");
32 let uri_attr = xot.add_name("uri");
33
34 let mut entries = Vec::new();
35 for child in xot.children(root) {
36 if !xot.is_element(child) {
37 continue;
38 }
39 let Some(el) = xot.element(child) else {
40 continue;
41 };
42 if el.name() != uri_el {
43 continue;
44 }
45 let Some(name) = xot.get_attribute(child, name_attr) else {
46 continue;
47 };
48 let Some(uri) = xot.get_attribute(child, uri_attr) else {
49 continue;
50 };
51 entries.push(CatalogEntry {
52 namespace: name.to_string(),
53 path: uri.to_string(),
54 });
55 }
56
57 Ok(entries)
58}
59
60#[cfg(test)]
61mod tests {
62 use super::*;
63
64 #[test]
65 fn parse_catalog_extracts_entries() {
66 let dir = tempfile::tempdir().unwrap();
67 let catalog = r#"<?xml version="1.0" encoding="UTF-8"?>
68<catalog xmlns="urn:oasis:names:tc:entity:xmlns:xml:catalog">
69 <uri name="urn:test:foo" uri="foo.xsd"/>
70 <uri name="urn:test:bar" uri="bar.xsd"/>
71</catalog>"#;
72 let path = dir.path().join("catalog.xml");
73 std::fs::write(&path, catalog).unwrap();
74
75 let entries = parse_catalog(&path).unwrap();
76 assert_eq!(entries.len(), 2);
77 assert_eq!(entries[0].namespace, "urn:test:foo");
78 assert_eq!(entries[0].path, "foo.xsd");
79 assert_eq!(entries[1].namespace, "urn:test:bar");
80 assert_eq!(entries[1].path, "bar.xsd");
81 }
82
83 #[test]
84 fn parse_catalog_skips_non_uri_elements() {
85 let dir = tempfile::tempdir().unwrap();
86 let catalog = r#"<?xml version="1.0" encoding="UTF-8"?>
87<catalog xmlns="urn:oasis:names:tc:entity:xmlns:xml:catalog">
88 <!-- A comment -->
89 <uri name="urn:test:only" uri="only.xsd"/>
90</catalog>"#;
91 let path = dir.path().join("catalog.xml");
92 std::fs::write(&path, catalog).unwrap();
93
94 let entries = parse_catalog(&path).unwrap();
95 assert_eq!(entries.len(), 1);
96 assert_eq!(entries[0].namespace, "urn:test:only");
97 }
98
99 #[test]
100 fn parse_catalog_empty_catalog() {
101 let dir = tempfile::tempdir().unwrap();
102 let catalog = r#"<?xml version="1.0" encoding="UTF-8"?>
103<catalog xmlns="urn:oasis:names:tc:entity:xmlns:xml:catalog">
104</catalog>"#;
105 let path = dir.path().join("catalog.xml");
106 std::fs::write(&path, catalog).unwrap();
107
108 let entries = parse_catalog(&path).unwrap();
109 assert!(entries.is_empty());
110 }
111}