using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Xml; namespace Implab.Xml { public class XmlNameContext { public const string XmlnsNamespace = "http://www.w3.org/2000/xmlns/"; public const string XmlnsPrefix = "xmlns"; public const string XmlNamespace = "http://www.w3.org/XML/1998/namespace"; public const string XmlPrefix = "xml"; public const string XsiNamespace = "http://www.w3.org/2001/XMLSchema-instance"; public const string XsiPrefix = "xsi"; readonly static char[] _qNameDelim = new[] { ':' }; Dictionary m_ns2prefix; Dictionary m_prefix2ns; int m_nextPrefix = 1; public XmlNameContext ParentContext { get; private set; } public XmlNameContext(XmlNameContext parent) { ParentContext = parent; if (parent == null) { DefinePrefixNoCheck(XmlnsNamespace, XmlnsPrefix); DefinePrefixNoCheck(XmlNamespace, XmlPrefix); } else { m_nextPrefix = parent.m_nextPrefix; } } public bool LookupNamespacePrefix(string ns, out string prefix) { if (ns == null) ns = string.Empty; prefix = null; for (var ctx = this; ctx != null; ctx = ctx.ParentContext) { if (ctx.m_ns2prefix?.TryGetValue(ns, out prefix) == true) { if (ctx != this) // cache for the future use DefinePrefixNoCheck(ns, prefix); return true; } } return false; } public string CreateNamespacePrefix(string ns) { var prefix = $"p{m_nextPrefix++}"; DefinePrefixNoCheck(ns, prefix); return prefix; } void DefinePrefixNoCheck(string ns, string prefix) { if (ns == null) ns = string.Empty; if (prefix == null) prefix = string.Empty; if (m_ns2prefix == null) m_ns2prefix = new Dictionary(); m_ns2prefix[ns] = prefix; if (m_prefix2ns == null) m_prefix2ns = new Dictionary(); m_prefix2ns[prefix] = ns; } public void DefinePrefix(string ns, string prefix) { // according to https://www.w3.org/TR/xml-names/#ns-decl // It MUST NOT be declared . Other prefixes MUST NOT be bound to this namespace name, and it MUST NOT be declared as the default namespace if (ns == XmlnsNamespace) throw new Exception($"Attempt to define xmlns:{prefix}='{ns}'"); // It MAY, but need not, be declared, and MUST NOT be bound to any other namespace name if (ns == XmlNamespace && prefix != XmlPrefix) throw new Exception($"Attempt to define xmlns:{prefix}='{ns}'"); // add mapping DefinePrefixNoCheck(ns, prefix); } public string ResolvePrefix(string prefix) { if (prefix == null) prefix = string.Empty; string ns = null; for(var ctx = this; ctx != null; ctx = ctx.ParentContext) { if (ctx.m_prefix2ns?.TryGetValue(prefix, out ns) == true) { if (ctx != this) // cache for the future use DefinePrefixNoCheck(ns, prefix); return ns; } } return null; } public XmlQualifiedName Resolve(string name) { Safe.ArgumentNotEmpty(name, nameof(name)); var parts = name.Split(_qNameDelim, 2, StringSplitOptions.RemoveEmptyEntries); if (parts.Length == 2) { return new XmlQualifiedName(parts[1], ResolvePrefix(parts[0])); } else { return new XmlQualifiedName(parts[0], ResolvePrefix(string.Empty)); } } } }