view Implab/Xml/XmlNameContext.cs @ 227:8d5de4eb9c2c v2

Reimplemented JsonXmlReader, added support for null values: JSON null values are mapped to empty nodes with 'xsi:nil' attribute set to 'true'
author cin
date Sat, 09 Sep 2017 03:53:13 +0300
parents
children 5f7a3e1d32b9
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 {
    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<string, string> m_ns2prefix;
        Dictionary<string, string> 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<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?.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));
            }
        }
    }
}