Skip to main content

rusty_xml_parser/
catalog.rs

1//! Local XML Catalogs (OASIS). No network. `XML_PARSE_NO_SYS_CATALOG` respected by callers.
2
3use rusty_xml_tree::{NodeKind, XmlDoc};
4use std::collections::HashMap;
5use std::path::{Path, PathBuf};
6
7const CAT_NS: &str = "urn:oasis:names:tc:entity:xmlns:xml:catalog";
8
9#[derive(Clone, Debug, Default)]
10pub struct XmlCatalog {
11    pub public: HashMap<String, String>,
12    pub system: HashMap<String, String>,
13    pub uri: HashMap<String, String>,
14    pub rewrite_system: Vec<(String, String)>,
15    pub rewrite_uri: Vec<(String, String)>,
16    pub next_catalog: Vec<PathBuf>,
17}
18
19impl XmlCatalog {
20    /// `xmlLoadCatalog`.
21    #[doc(alias = "xmlLoadCatalog")]
22    pub fn xml_load_catalog(path: &Path) -> Result<Self, String> {
23        let bytes = std::fs::read(path).map_err(|e| e.to_string())?;
24        let doc = crate::xml_read_memory(&bytes, path.to_str(), None, crate::default_parse_options())
25            .map_err(|e| e.to_string())?;
26        let mut cat = XmlCatalog::default();
27        walk(&doc, doc.xml_doc_get_root_element(), &mut cat, path.parent().unwrap_or(path));
28        Ok(cat)
29    }
30
31    /// `xmlCatalogResolve`.
32    #[doc(alias = "xmlCatalogResolve")]
33    pub fn xml_catalog_resolve(&self, public_id: Option<&str>, system_id: Option<&str>) -> Option<String> {
34        if let Some(p) = public_id {
35            if let Some(u) = self.public.get(p) {
36                return Some(u.clone());
37            }
38        }
39        if let Some(s) = system_id {
40            if let Some(u) = self.system.get(s) {
41                return Some(u.clone());
42            }
43            for (prefix, replace) in &self.rewrite_system {
44                if s.starts_with(prefix) {
45                    return Some(format!("{}{}", replace, &s[prefix.len()..]));
46                }
47            }
48        }
49        None
50    }
51
52    /// `xmlCatalogResolveURI`.
53    #[doc(alias = "xmlCatalogResolveURI")]
54    pub fn xml_catalog_resolve_uri(&self, uri: &str) -> Option<String> {
55        if let Some(u) = self.uri.get(uri) {
56            return Some(u.clone());
57        }
58        for (prefix, replace) in &self.rewrite_uri {
59            if uri.starts_with(prefix) {
60                return Some(format!("{}{}", replace, &uri[prefix.len()..]));
61            }
62        }
63        None
64    }
65}
66
67fn walk(doc: &XmlDoc, node: Option<rusty_xml_tree::NodeId>, cat: &mut XmlCatalog, base: &Path) {
68    let Some(id) = node else { return };
69    if doc.kind(id) == NodeKind::Element {
70        let name = doc.name(id);
71        let ns = doc.ns_uri(id);
72        let in_cat = ns == Some(CAT_NS) || ns.is_none();
73        if in_cat {
74            match name {
75                "public" => {
76                    if let (Some(idv), Some(uri)) = (doc.xml_get_prop(id, "publicId"), doc.xml_get_prop(id, "uri")) {
77                        cat.public.insert(idv, resolve_uri(base, &uri));
78                    }
79                }
80                "system" => {
81                    if let (Some(idv), Some(uri)) = (doc.xml_get_prop(id, "systemId"), doc.xml_get_prop(id, "uri")) {
82                        cat.system.insert(idv, resolve_uri(base, &uri));
83                    }
84                }
85                "uri" => {
86                    if let (Some(name), Some(uri)) = (doc.xml_get_prop(id, "name"), doc.xml_get_prop(id, "uri")) {
87                        cat.uri.insert(name, resolve_uri(base, &uri));
88                    }
89                }
90                "rewriteSystem" => {
91                    if let (Some(p), Some(r)) = (
92                        doc.xml_get_prop(id, "systemIdStartString"),
93                        doc.xml_get_prop(id, "rewritePrefix"),
94                    ) {
95                        cat.rewrite_system.push((p, resolve_uri(base, &r)));
96                    }
97                }
98                "rewriteURI" => {
99                    if let (Some(p), Some(r)) = (
100                        doc.xml_get_prop(id, "uriStartString"),
101                        doc.xml_get_prop(id, "rewritePrefix"),
102                    ) {
103                        cat.rewrite_uri.push((p, resolve_uri(base, &r)));
104                    }
105                }
106                "nextCatalog" => {
107                    if let Some(c) = doc.xml_get_prop(id, "catalog") {
108                        cat.next_catalog.push(base.join(c));
109                    }
110                }
111                _ => {}
112            }
113        }
114        let mut ch = doc.first_child(id);
115        while let Some(c) = ch {
116            walk(doc, Some(c), cat, base);
117            ch = doc.next_sibling(c);
118        }
119    }
120}
121
122fn resolve_uri(base: &Path, uri: &str) -> String {
123    if uri.contains("://") {
124        uri.to_string()
125    } else {
126        base.join(uri).to_string_lossy().into_owned()
127    }
128}
129
130/// `xmlInitializeCatalog` — no-op (no process-global catalog).
131#[doc(alias = "xmlInitializeCatalog")]
132pub fn xml_initialize_catalog() {}
133
134/// `xmlCatalogCleanup` — no-op.
135#[doc(alias = "xmlCatalogCleanup")]
136pub fn xml_catalog_cleanup() {}