view Implab/Xml/XmlNameContext.cs @ 229:5f7a3e1d32b9 v2

JsonXmlReader performance tuning JsonScanner now operates strings and doesn't parses number and literals. Added SerializationHelpers to common serialize/deserialize operations
author cin
date Tue, 12 Sep 2017 19:07:42 +0300
parents 8d5de4eb9c2c
children
line wrap: on
line source

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
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));
            }
        }
    }
}