use std::collections::HashMap;
use std::fmt;
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct QName {
namespace: Option<String>,
local: String,
display: String,
}
impl QName {
pub fn new(namespace: Option<&str>, local: &str) -> Self {
let display = match namespace {
Some(ns) => format!("{{{ns}}}{local}"),
None => local.to_string(),
};
Self {
namespace: namespace.map(|s| s.to_string()),
local: local.to_string(),
display,
}
}
pub fn with_display(namespace: Option<&str>, local: &str, display: &str) -> Self {
Self {
namespace: namespace.map(|s| s.to_string()),
local: local.to_string(),
display: display.to_string(),
}
}
pub fn parse(s: &str) -> Self {
if let Some(rest) = s.strip_prefix('{')
&& let Some(end) = rest.find('}')
{
let ns = rest[..end].to_string();
let local = rest[end + 1..].to_string();
let display = s.to_string();
return Self {
namespace: Some(ns),
local,
display,
};
}
if let Some(colon) = s.find(':') {
Self {
namespace: None,
local: s[colon + 1..].to_string(),
display: s.to_string(),
}
} else {
Self {
namespace: None,
local: s.to_string(),
display: s.to_string(),
}
}
}
pub fn local(&self) -> &str {
&self.local
}
pub fn namespace(&self) -> Option<&str> {
self.namespace.as_deref()
}
pub fn clark(&self) -> String {
match &self.namespace {
Some(ns) => format!("{{{ns}}}{}", self.local),
None => self.local.clone(),
}
}
pub fn resolve(&self, resolver: &NamespaceResolver) -> String {
resolver.format(self.namespace.as_deref(), &self.local)
}
}
impl fmt::Display for QName {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(&self.display)
}
}
impl From<&str> for QName {
fn from(s: &str) -> Self {
Self::parse(s)
}
}
impl From<String> for QName {
fn from(s: String) -> Self {
Self::parse(&s)
}
}
impl From<QName> for String {
fn from(q: QName) -> Self {
q.display
}
}
impl PartialEq<str> for QName {
fn eq(&self, other: &str) -> bool {
self.display == other
}
}
impl PartialEq<&str> for QName {
fn eq(&self, other: &&str) -> bool {
self.display == *other
}
}
impl PartialEq<String> for QName {
fn eq(&self, other: &String) -> bool {
self.display == *other
}
}
#[derive(Debug, Clone)]
pub struct NamespaceResolver {
default_ns: Option<String>,
prefix_to_uri: HashMap<String, String>,
uri_to_prefix: HashMap<String, String>,
}
impl Default for NamespaceResolver {
fn default() -> Self {
Self::new()
}
}
impl NamespaceResolver {
pub fn new() -> Self {
let mut r = Self {
default_ns: None,
prefix_to_uri: HashMap::new(),
uri_to_prefix: HashMap::new(),
};
r.add_prefix("xs", "http://www.w3.org/2001/XMLSchema");
r
}
pub fn with_default_ns(ns: impl Into<String>) -> Self {
let mut r = Self::new();
r.set_default_ns(ns);
r
}
pub fn set_default_ns(&mut self, ns: impl Into<String>) {
self.default_ns = Some(ns.into());
}
pub fn add_prefix(&mut self, prefix: impl Into<String>, uri: impl Into<String>) {
let prefix = prefix.into();
let uri = uri.into();
self.uri_to_prefix.insert(uri.clone(), prefix.clone());
self.prefix_to_uri.insert(prefix, uri);
}
pub fn default_ns(&self) -> Option<&str> {
self.default_ns.as_deref()
}
pub fn prefix_for_uri(&self, uri: &str) -> Option<&str> {
self.uri_to_prefix.get(uri).map(String::as_str)
}
pub fn uri_for_prefix(&self, prefix: &str) -> Option<&str> {
self.prefix_to_uri.get(prefix).map(String::as_str)
}
pub fn format(&self, namespace: Option<&str>, local: &str) -> String {
match namespace {
None => local.to_string(),
Some(ns) if self.default_ns.as_deref() == Some(ns) => local.to_string(),
Some(ns) => match self.prefix_for_uri(ns) {
Some(prefix) => format!("{prefix}:{local}"),
None => format!("{{{ns}}}{local}"),
},
}
}
pub fn resolve(&self, name: &str) -> QName {
if let Some(rest) = name.strip_prefix('{')
&& let Some(end) = rest.find('}')
{
let ns = rest[..end].to_string();
let local = rest[end + 1..].to_string();
let display = self.format(Some(&ns), &local);
return QName {
namespace: Some(ns),
local,
display,
};
}
if let Some(colon) = name.find(':') {
let prefix = &name[..colon];
let local = &name[colon + 1..];
let ns = self.uri_for_prefix(prefix).map(|s| s.to_string());
let display = self.format(ns.as_deref(), local);
QName {
namespace: ns,
local: local.to_string(),
display,
}
} else {
let display = self.format(self.default_ns.as_deref(), name);
QName {
namespace: self.default_ns.clone(),
local: name.to_string(),
display,
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn parse_clark() {
let q = QName::parse("{https://example.com}Foo");
assert_eq!(q.namespace(), Some("https://example.com"));
assert_eq!(q.local(), "Foo");
assert_eq!(q.to_string(), "{https://example.com}Foo");
}
#[test]
fn parse_prefixed() {
let q = QName::parse("xs:string");
assert_eq!(q.local(), "string");
assert_eq!(q.namespace(), None); assert_eq!(q.to_string(), "xs:string");
}
#[test]
fn parse_bare() {
let q = QName::parse("Foo");
assert_eq!(q.local(), "Foo");
assert_eq!(q.namespace(), None);
assert_eq!(q.to_string(), "Foo");
}
#[test]
fn resolve_default_ns() {
let mut r = NamespaceResolver::new();
r.set_default_ns("https://example.com");
let q = QName::new(Some("https://example.com"), "Foo");
assert_eq!(q.resolve(&r), "Foo");
}
#[test]
fn resolve_mapped_prefix() {
let mut r = NamespaceResolver::new();
r.add_prefix("ex", "https://example.com");
let q = QName::new(Some("https://example.com"), "Foo");
assert_eq!(q.resolve(&r), "ex:Foo");
}
#[test]
fn resolve_clark_fallback() {
let r = NamespaceResolver::new();
let q = QName::new(Some("https://unmapped.example.com"), "Foo");
assert_eq!(q.resolve(&r), "{https://unmapped.example.com}Foo");
}
#[test]
fn resolver_resolve_prefixed_name() {
let mut r = NamespaceResolver::new();
r.set_default_ns("https://oam.example.com");
r.add_prefix("uci", "https://oam.example.com");
let q = r.resolve("uci:SystemStatusType");
assert_eq!(q.namespace(), Some("https://oam.example.com"));
assert_eq!(q.local(), "SystemStatusType");
assert_eq!(q.to_string(), "SystemStatusType");
}
#[test]
fn into_string_returns_display() {
let q = QName::with_display(Some("https://example.com"), "Foo", "Foo");
let s: String = q.into();
assert_eq!(s, "Foo");
}
#[test]
fn partial_eq_str() {
let q = QName::with_display(Some("https://example.com"), "Foo", "Foo");
assert!(q == "Foo");
assert!(q != "Bar");
}
}