Skip to main content

sup_xml_core/
ns_helpers.rs

1//! Shared XML Namespaces 1.0 utilities used by [`crate::parser`] and
2//! [`crate::stream_parser`].
3//!
4//! Lives outside the legacy [`crate::namespace`] module because it's used by
5//! the new arena tree parsers, which don't depend on the legacy `Document` /
6//! `ElementNode` types.  The legacy resolver keeps its own copies of these
7//! checks for symmetry with its `Vec`-based tree.
8
9use crate::error::{ErrorDomain, ErrorLevel, Result, XmlError};
10
11// ── built-in namespace URIs (XML Namespaces 1.0) ─────────────────────────────
12
13pub const XML_NS_URI:   &str = "http://www.w3.org/XML/1998/namespace";
14pub const XMLNS_NS_URI: &str = "http://www.w3.org/2000/xmlns/";
15
16// ── validation ──────────────────────────────────────────────────────────────
17
18/// Validate QName syntax: at most one colon, both halves non-empty when a
19/// colon is present.  XML Namespaces 1.0 § 3.
20pub fn validate_qname(name: &str, context: &str) -> Result<()> {
21    if let Some(colon) = name.find(':') {
22        let prefix = &name[..colon];
23        let local  = &name[colon + 1..];
24        if prefix.is_empty() {
25            return Err(ns_err(format!("empty prefix in {context} QName '{name}'")));
26        }
27        if local.is_empty() {
28            return Err(ns_err(format!("empty local part in {context} QName '{name}'")));
29        }
30        if local.contains(':') {
31            return Err(ns_err(format!("multiple ':' in {context} QName '{name}'")));
32        }
33    }
34    Ok(())
35}
36
37/// Validate the right-hand side of an `xmlns:local="value"` declaration.
38/// XML Namespaces 1.0 §§ 3, 6.
39pub fn validate_xmlns_decl(local: &str, value: &str) -> Result<()> {
40    if local.is_empty() {
41        return Err(ns_err("empty local part in 'xmlns:' namespace declaration".into()));
42    }
43    if local == "xmlns" {
44        return Err(ns_err("prefix 'xmlns' must not be declared".into()));
45    }
46    if local == "xml" && value != XML_NS_URI {
47        return Err(ns_err(format!(
48            "prefix 'xml' must be bound to '{XML_NS_URI}', not '{value}'"
49        )));
50    }
51    if local != "xml" && value == XML_NS_URI {
52        return Err(ns_err(format!(
53            "prefix '{local}' cannot be bound to the XML namespace URI '{XML_NS_URI}'"
54        )));
55    }
56    if value == XMLNS_NS_URI {
57        return Err(ns_err(format!(
58            "prefix '{local}' cannot be bound to the xmlns namespace URI '{XMLNS_NS_URI}'"
59        )));
60    }
61    if value.is_empty() {
62        return Err(ns_err(format!(
63            "prefix '{local}' cannot be unbound (empty URI) in XML Namespaces 1.0"
64        )));
65    }
66    Ok(())
67}
68
69pub fn ns_err(msg: String) -> XmlError {
70    XmlError::new(ErrorDomain::Namespace, ErrorLevel::Fatal, msg)
71}