|
|
using System;
|
|
|
using System.Collections.Generic;
|
|
|
using System.Xml;
|
|
|
|
|
|
namespace Implab.Xml {
|
|
|
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<string, string> m_ns2prefix;
|
|
|
Dictionary<string, string> m_prefix2ns;
|
|
|
int m_nextPrefix = 1;
|
|
|
string m_lastNs;
|
|
|
string m_lastPrefix;
|
|
|
|
|
|
public XmlNameContext ParentContext { get; private set; }
|
|
|
|
|
|
public int Depth { get; private set; }
|
|
|
|
|
|
public XmlNameContext(XmlNameContext parent, int depth) {
|
|
|
ParentContext = parent;
|
|
|
Depth = depth;
|
|
|
|
|
|
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;
|
|
|
if (ns == m_lastNs) {
|
|
|
prefix = m_lastPrefix;
|
|
|
return true;
|
|
|
}
|
|
|
|
|
|
|
|
|
prefix = null;
|
|
|
for (var ctx = this; ctx != null; ctx = ctx.ParentContext) {
|
|
|
if (ctx.m_ns2prefix != null && ctx.m_ns2prefix.TryGetValue(ns, out prefix)) {
|
|
|
m_lastNs = ns;
|
|
|
m_lastPrefix = 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<string, string>();
|
|
|
m_ns2prefix[ns] = prefix;
|
|
|
|
|
|
if (m_prefix2ns == null)
|
|
|
m_prefix2ns = new Dictionary<string, string>();
|
|
|
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 != null && ctx.m_prefix2ns.TryGetValue(prefix, out ns) == true)
|
|
|
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));
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
|