use crate::core::error::{XmpError, XmpResult};
use std::collections::HashMap;
use std::sync::{OnceLock, RwLock};
static GLOBAL_NAMESPACE_MAP: OnceLock<RwLock<NamespaceMap>> = OnceLock::new();
pub mod ns {
pub const XMP: &str = "http://ns.adobe.com/xap/1.0/";
pub const DC: &str = "http://purl.org/dc/elements/1.1/";
pub const EXIF: &str = "http://ns.adobe.com/exif/1.0/";
pub const EXIF_AUX: &str = "http://ns.adobe.com/exif/1.0/aux/";
pub const IPTC_CORE: &str = "http://iptc.org/std/Iptc4xmpCore/1.0/xmlns/";
pub const IPTC_EXT: &str = "http://iptc.org/std/Iptc4xmpExt/2008-02-29/";
pub const PHOTOSHOP: &str = "http://ns.adobe.com/photoshop/1.0/";
pub const CAMERA_RAW: &str = "http://ns.adobe.com/camera-raw-settings/1.0/";
pub const XMP_RIGHTS: &str = "http://ns.adobe.com/xap/1.0/rights/";
pub const XMP_MM: &str = "http://ns.adobe.com/xap/1.0/mm/";
pub const XMP_BJ: &str = "http://ns.adobe.com/xap/1.0/bj/";
pub const TIFF: &str = "http://ns.adobe.com/tiff/1.0/";
pub const PDF: &str = "http://ns.adobe.com/pdf/1.3/";
pub const PDFX: &str = "http://ns.adobe.com/pdfx/1.3/";
pub const PDFA: &str = "http://www.aiim.org/pdfa/ns/id/";
pub const XMP_DM: &str = "http://ns.adobe.com/xmp/1.0/DynamicMedia/";
pub const XMP_PAGED: &str = "http://ns.adobe.com/xap/1.0/t/pg/";
pub const XMP_GRAPHICS: &str = "http://ns.adobe.com/xap/1.0/g/";
pub const XMP_IMAGE: &str = "http://ns.adobe.com/xap/1.0/g/img/";
pub const RDF: &str = "http://www.w3.org/1999/02/22-rdf-syntax-ns#";
pub const XML: &str = "http://www.w3.org/XML/1998/namespace";
pub const XMP_PREFIX: &str = "xmp";
pub const DC_PREFIX: &str = "dc";
pub const EXIF_PREFIX: &str = "exif";
pub const RDF_PREFIX: &str = "rdf";
pub const XML_PREFIX: &str = "xml";
pub const EXIF_AUX_PREFIX: &str = "exifEX";
pub const IPTC_CORE_PREFIX: &str = "Iptc4xmpCore";
pub const IPTC_EXT_PREFIX: &str = "Iptc4xmpExt";
pub const PHOTOSHOP_PREFIX: &str = "photoshop";
pub const CAMERA_RAW_PREFIX: &str = "crs";
pub const XMP_RIGHTS_PREFIX: &str = "xmpRights";
pub const XMP_MM_PREFIX: &str = "xmpMM";
pub const XMP_BJ_PREFIX: &str = "xmpBJ";
pub const TIFF_PREFIX: &str = "tiff";
pub const PDF_PREFIX: &str = "pdf";
pub const PDFX_PREFIX: &str = "pdfx";
pub const PDFA_PREFIX: &str = "pdfaid";
pub const XMP_DM_PREFIX: &str = "xmpDM";
pub const XMP_PAGED_PREFIX: &str = "xmpTPg";
pub const XMP_GRAPHICS_PREFIX: &str = "xmpG";
pub const XMP_IMAGE_PREFIX: &str = "xmpGImg";
}
#[derive(Debug, Clone, Default)]
pub struct NamespaceMap {
uri_to_prefix: HashMap<String, String>,
prefix_to_uri: HashMap<String, String>,
}
impl NamespaceMap {
pub fn new() -> Self {
let mut map = Self::default();
map.register_builtin_namespaces();
map
}
pub fn register(&mut self, uri: &str, prefix: &str) -> XmpResult<()> {
if let Some(existing_uri) = self.prefix_to_uri.get(prefix) {
if existing_uri != uri {
return Err(XmpError::BadParam(format!(
"Prefix '{}' is already registered to '{}'",
prefix, existing_uri
)));
}
return Ok(());
}
self.uri_to_prefix
.insert(uri.to_string(), prefix.to_string());
self.prefix_to_uri
.insert(prefix.to_string(), uri.to_string());
Ok(())
}
pub fn get_prefix(&self, uri: &str) -> Option<&str> {
self.uri_to_prefix.get(uri).map(|s| s.as_str())
}
pub fn get_uri(&self, prefix: &str) -> Option<&str> {
self.prefix_to_uri.get(prefix).map(|s| s.as_str())
}
pub fn has_uri(&self, uri: &str) -> bool {
self.uri_to_prefix.contains_key(uri)
}
pub fn has_prefix(&self, prefix: &str) -> bool {
self.prefix_to_uri.contains_key(prefix)
}
pub fn get_all_namespaces(&self) -> Vec<(String, String)> {
self.uri_to_prefix
.iter()
.map(|(uri, prefix)| (uri.clone(), prefix.clone()))
.collect()
}
fn register_builtin_namespaces(&mut self) {
self.register(ns::XMP, ns::XMP_PREFIX).unwrap();
self.register(ns::DC, ns::DC_PREFIX).unwrap();
self.register(ns::EXIF, ns::EXIF_PREFIX).unwrap();
self.register(ns::RDF, ns::RDF_PREFIX).unwrap();
self.register(ns::XML, ns::XML_PREFIX).unwrap();
self.register(ns::EXIF_AUX, ns::EXIF_AUX_PREFIX).unwrap();
self.register(ns::IPTC_CORE, ns::IPTC_CORE_PREFIX).unwrap();
self.register(ns::IPTC_EXT, ns::IPTC_EXT_PREFIX).unwrap();
self.register(ns::PHOTOSHOP, ns::PHOTOSHOP_PREFIX).unwrap();
self.register(ns::CAMERA_RAW, ns::CAMERA_RAW_PREFIX)
.unwrap();
self.register(ns::XMP_RIGHTS, ns::XMP_RIGHTS_PREFIX)
.unwrap();
self.register(ns::XMP_MM, ns::XMP_MM_PREFIX).unwrap();
self.register(ns::XMP_BJ, ns::XMP_BJ_PREFIX).unwrap();
self.register(ns::TIFF, ns::TIFF_PREFIX).unwrap();
self.register(ns::PDF, ns::PDF_PREFIX).unwrap();
self.register(ns::PDFX, ns::PDFX_PREFIX).unwrap();
self.register(ns::PDFA, ns::PDFA_PREFIX).unwrap();
self.register(ns::XMP_DM, ns::XMP_DM_PREFIX).unwrap();
self.register(ns::XMP_PAGED, ns::XMP_PAGED_PREFIX).unwrap();
self.register(ns::XMP_GRAPHICS, ns::XMP_GRAPHICS_PREFIX)
.unwrap();
self.register(ns::XMP_IMAGE, ns::XMP_IMAGE_PREFIX).unwrap();
}
}
fn get_global_namespace_map() -> &'static RwLock<NamespaceMap> {
GLOBAL_NAMESPACE_MAP.get_or_init(|| RwLock::new(NamespaceMap::new()))
}
pub fn register_namespace(uri: &str, prefix: &str) -> XmpResult<()> {
if uri.is_empty() {
return Err(XmpError::BadParam("URI cannot be empty".to_string()));
}
if prefix.is_empty() {
return Err(XmpError::BadParam("Prefix cannot be empty".to_string()));
}
let map = get_global_namespace_map();
let mut guard = map.write().expect("Namespace registry lock poisoned");
guard.register(uri, prefix)
}
pub fn is_namespace_registered(uri: &str) -> bool {
let map = get_global_namespace_map();
let guard = map.read().expect("Namespace registry lock poisoned");
guard.has_uri(uri)
}
pub fn get_global_namespace_prefix(uri: &str) -> Option<String> {
let map = get_global_namespace_map();
let guard = map.read().expect("Namespace registry lock poisoned");
guard.get_prefix(uri).map(|s| s.to_string())
}
pub fn get_global_namespace_uri(prefix: &str) -> Option<String> {
let map = get_global_namespace_map();
let guard = map.read().expect("Namespace registry lock poisoned");
guard.get_uri(prefix).map(|s| s.to_string())
}
pub fn get_all_registered_namespaces() -> Vec<(String, String)> {
let map = get_global_namespace_map();
let guard = map.read().expect("Namespace registry lock poisoned");
guard.get_all_namespaces()
}
pub fn get_builtin_namespace_uris() -> Vec<String> {
vec![
ns::XMP.to_string(),
ns::DC.to_string(),
ns::EXIF.to_string(),
ns::EXIF_AUX.to_string(),
ns::IPTC_CORE.to_string(),
ns::IPTC_EXT.to_string(),
ns::PHOTOSHOP.to_string(),
ns::CAMERA_RAW.to_string(),
ns::XMP_RIGHTS.to_string(),
ns::XMP_MM.to_string(),
ns::XMP_BJ.to_string(),
ns::TIFF.to_string(),
ns::PDF.to_string(),
ns::PDFX.to_string(),
ns::PDFA.to_string(),
ns::XMP_DM.to_string(),
ns::XMP_PAGED.to_string(),
ns::XMP_GRAPHICS.to_string(),
ns::XMP_IMAGE.to_string(),
ns::RDF.to_string(),
ns::XML.to_string(),
]
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_namespace_map_new() {
let map = NamespaceMap::new();
assert!(map.has_uri(ns::XMP));
assert!(map.has_uri(ns::DC));
assert!(map.has_prefix(ns::XMP_PREFIX));
}
#[test]
fn test_namespace_map_register() {
let mut map = NamespaceMap::new();
assert!(map.register("http://example.com/ns", "ex").is_ok());
assert_eq!(map.get_prefix("http://example.com/ns"), Some("ex"));
assert_eq!(map.get_uri("ex"), Some("http://example.com/ns"));
}
#[test]
fn test_namespace_map_duplicate_prefix() {
let mut map = NamespaceMap::new();
assert!(map.register("http://example.com/ns1", "ex").is_ok());
assert!(map.register("http://example.com/ns2", "ex").is_err());
}
#[test]
fn test_namespace_map_same_uri_prefix() {
let mut map = NamespaceMap::new();
assert!(map.register("http://example.com/ns", "ex").is_ok());
assert!(map.register("http://example.com/ns", "ex").is_ok());
}
#[test]
fn test_get_global_namespace_prefix() {
assert_eq!(
get_global_namespace_prefix(ns::XMP),
Some(ns::XMP_PREFIX.to_string())
);
assert_eq!(
get_global_namespace_prefix(ns::DC),
Some(ns::DC_PREFIX.to_string())
);
assert_eq!(get_global_namespace_prefix("http://unknown.com/ns"), None);
register_namespace("http://example.com/ns", "ex").unwrap();
assert_eq!(
get_global_namespace_prefix("http://example.com/ns"),
Some("ex".to_string())
);
}
}