use std::collections::HashMap;
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct QualifiedName {
pub namespace_uri: Option<String>,
pub local_name: String,
pub qualified_name: String,
}
impl QualifiedName {
pub fn new(namespace_uri: Option<String>, local_name: String) -> Self {
let qualified_name = if let Some(ref uri) = namespace_uri {
let prefix = Self::uri_to_prefix(uri);
if prefix.is_empty() {
local_name.clone()
} else {
format!("{}:{}", prefix, local_name)
}
} else {
local_name.clone()
};
Self {
namespace_uri,
local_name,
qualified_name,
}
}
pub fn from_string(name: &str) -> Self {
if let Some(colon_pos) = name.find(':') {
let prefix = &name[..colon_pos];
let local_name = &name[colon_pos + 1..];
let namespace_uri = Self::prefix_to_uri(prefix);
Self {
namespace_uri,
local_name: local_name.to_string(),
qualified_name: name.to_string(),
}
} else {
Self {
namespace_uri: None,
local_name: name.to_string(),
qualified_name: name.to_string(),
}
}
}
fn uri_to_prefix(uri: &str) -> &'static str {
match uri {
"urn:oasis:names:tc:opendocument:xmlns:text:1.0" => "text",
"urn:oasis:names:tc:opendocument:xmlns:style:1.0" => "style",
"urn:oasis:names:tc:opendocument:xmlns:table:1.0" => "table",
"urn:oasis:names:tc:opendocument:xmlns:draw:1.0" => "draw",
"urn:oasis:names:tc:opendocument:xmlns:office:1.0" => "office",
"urn:oasis:names:tc:opendocument:xmlns:meta:1.0" => "meta",
"urn:oasis:names:tc:opendocument:xmlns:fo:1.0" => "fo",
"http://www.w3.org/1999/xlink" => "xlink",
"http://www.w3.org/XML/1998/namespace" => "xml",
_ => "",
}
}
fn prefix_to_uri(prefix: &str) -> Option<String> {
match prefix {
"text" => Some("urn:oasis:names:tc:opendocument:xmlns:text:1.0".to_string()),
"style" => Some("urn:oasis:names:tc:opendocument:xmlns:style:1.0".to_string()),
"table" => Some("urn:oasis:names:tc:opendocument:xmlns:table:1.0".to_string()),
"draw" => Some("urn:oasis:names:tc:opendocument:xmlns:draw:1.0".to_string()),
"office" => Some("urn:oasis:names:tc:opendocument:xmlns:office:1.0".to_string()),
"meta" => Some("urn:oasis:names:tc:opendocument:xmlns:meta:1.0".to_string()),
"fo" => Some("urn:oasis:names:tc:opendocument:xmlns:fo:1.0".to_string()),
"xlink" => Some("http://www.w3.org/1999/xlink".to_string()),
"xml" => Some("http://www.w3.org/XML/1998/namespace".to_string()),
_ => None,
}
}
pub fn matches(&self, other: &QualifiedName) -> bool {
self.namespace_uri == other.namespace_uri && self.local_name == other.local_name
}
pub fn matches_str(&self, name: &str, namespace_context: Option<&NamespaceContext>) -> bool {
let other = QualifiedName::from_string_with_context(name, namespace_context);
self.matches(&other)
}
}
impl From<&str> for QualifiedName {
fn from(name: &str) -> Self {
Self::from_string(name)
}
}
impl std::fmt::Display for QualifiedName {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.qualified_name)
}
}
#[derive(Debug, Clone, Default)]
pub struct NamespaceContext {
pub prefixes: HashMap<String, String>,
pub default_namespace: Option<String>,
}
impl NamespaceContext {
pub fn new() -> Self {
Self::default()
}
pub fn add_namespace(&mut self, prefix: &str, uri: &str) {
if prefix == "xmlns" {
self.default_namespace = Some(uri.to_string());
} else if let Some(prefix) = prefix.strip_prefix("xmlns:") {
self.prefixes.insert(prefix.to_string(), uri.to_string());
}
}
pub fn resolve_prefix(&self, prefix: &str) -> Option<&str> {
self.prefixes.get(prefix).map(|s| s.as_str())
}
pub fn default_namespace(&self) -> Option<&str> {
self.default_namespace.as_deref()
}
pub fn parse_qualified_name(&self, name: &str) -> QualifiedName {
QualifiedName::from_string_with_context(name, Some(self))
}
}
impl QualifiedName {
fn from_string_with_context(name: &str, context: Option<&NamespaceContext>) -> Self {
if let Some(colon_pos) = name.find(':') {
let prefix = &name[..colon_pos];
let local_name = &name[colon_pos + 1..];
let namespace_uri = if let Some(ctx) = context {
ctx.resolve_prefix(prefix).map(|s| s.to_string())
} else {
Self::prefix_to_uri(prefix)
};
Self {
namespace_uri,
local_name: local_name.to_string(),
qualified_name: name.to_string(),
}
} else {
let namespace_uri = if let Some(ctx) = context {
ctx.default_namespace().map(|s| s.to_string())
} else {
None
};
Self {
namespace_uri,
local_name: name.to_string(),
qualified_name: name.to_string(),
}
}
}
}