view Implab/Formats/JSON/JSONXmlReader.cs @ 187:dd4a3590f9c6 ref20160224

Reworked cancelation handling, if the cancel handler isn't specified the OperationCanceledException will be handled by the error handler Any unhandled OperationCanceledException will cause the promise cancelation
author cin
date Tue, 19 Apr 2016 17:35:20 +0300
parents c32688129f14
children 8222a2ab3ab7
line wrap: on
line source

using Implab;
using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Xml;

namespace Implab.Formats.JSON {
    public class JSONXmlReader : XmlReader {

        enum ValueContext {
            Undefined,
            ElementStart,
            ElementValue,
            ElementEnd,
            ElementEmpty
        }

        struct LocalNameContext {
            public string localName;
            public bool isArray;
        }

        JSONParser m_parser;
        ValueContext m_valueContext;
        ReadState m_state = ReadState.Initial;
        Stack<LocalNameContext> m_localNameStack = new Stack<LocalNameContext>();
        LocalNameContext m_localName;
        int m_depthCorrection;

        readonly string m_rootName;
        readonly string m_prefix;
        readonly string m_namespaceUri;
        readonly bool m_flattenArrays;
        readonly string m_arrayItemName;
        readonly XmlNameTable m_nameTable;

        JSONXmlReader(JSONParser parser, JSONXmlReaderOptions options) {
            m_parser = parser;

            if (options != null) {
                m_prefix = options.NodesPrefix ?? String.Empty;
                m_namespaceUri = options.NamespaceURI ?? String.Empty;
                m_rootName = options.RootName ?? "json";
                m_flattenArrays = options.FlattenArrays;
                m_arrayItemName = options.ArrayItemName ?? "item";
                m_nameTable = options.NameTable ?? new NameTable();
            } else {
                m_prefix = String.Empty;
                m_namespaceUri = String.Empty;
                m_rootName = "json";
                m_flattenArrays = false;
                m_arrayItemName = "item";
                m_nameTable = new NameTable();
            }
        }

        /// <summary>
        /// Always 0, JSON doesn't support attributes
        /// </summary>
        public override int AttributeCount {
            get { return 0; }
        }

        public override string BaseURI {
            get { return String.Empty; }
        }

        public override int Depth {
            get {
                return m_localNameStack.Count + m_depthCorrection;
            }
        }

        public override bool EOF {
            get { return m_parser.EOF; }
        }

        /// <summary>
        /// Always throws an exception
        /// </summary>
        /// <param name="i"></param>
        /// <returns></returns>
        public override string GetAttribute(int i) {
            throw new ArgumentOutOfRangeException();
        }

        /// <summary>
        /// Always returns empty string
        /// </summary>
        /// <param name="name"></param>
        /// <param name="namespaceURI"></param>
        /// <returns></returns>
        public override string GetAttribute(string name, string namespaceURI) {
            return String.Empty;
        }

        /// <summary>
        /// Always returns empty string
        /// </summary>
        /// <param name="name"></param>
        /// <returns></returns>
        public override string GetAttribute(string name) {
            return String.Empty;
        }

        public override bool IsEmptyElement {
            get { return m_parser.ElementType == JSONElementType.Value && m_valueContext == ValueContext.ElementEmpty; }
        }

        public override string LocalName {
            get { return m_localName.localName; }
        }

        public override string LookupNamespace(string prefix) {
            if (String.IsNullOrEmpty(prefix) || prefix == m_prefix)
                return m_namespaceUri;
            
            return String.Empty;
        }

        public override bool MoveToAttribute(string name, string ns) {
            return false;
        }

        public override bool MoveToAttribute(string name) {
            return false;
        }

        public override bool MoveToElement() {
            return false;
        }

        public override bool MoveToFirstAttribute() {
            return false;
        }

        public override bool MoveToNextAttribute() {
            return false;
        }

        public override XmlNameTable NameTable {
            get { return m_nameTable; }
        }

        public override string NamespaceURI {
            get { return m_namespaceUri; }
        }

        public override XmlNodeType NodeType {
            get {
                switch (m_parser.ElementType) {
                    case JSONElementType.BeginObject:
                    case JSONElementType.BeginArray:
                        return XmlNodeType.Element;
                    case JSONElementType.EndObject:
                    case JSONElementType.EndArray:
                        return XmlNodeType.EndElement;
                    case JSONElementType.Value:
                        switch (m_valueContext) {
                            case ValueContext.ElementStart:
                            case ValueContext.ElementEmpty:
                                return XmlNodeType.Element;
                            case ValueContext.ElementValue:
                                return XmlNodeType.Text;
                            case ValueContext.ElementEnd:
                                return XmlNodeType.EndElement;
                            default:
                                throw new InvalidOperationException();
                        }
                    default:
                        throw new InvalidOperationException();
                }
            }
        }

        public override string Prefix {
            get { return m_prefix; }
        }

        public override bool Read() {
            if (m_state != ReadState.Interactive && m_state != ReadState.Initial)
                return false;

            if (m_state == ReadState.Initial)
                m_state = ReadState.Interactive;

            try {
                switch (m_parser.ElementType) {
                    case JSONElementType.Value:
                        switch (m_valueContext) {
                            case ValueContext.ElementStart:
                                SetLocalName(String.Empty);
                                m_valueContext = ValueContext.ElementValue;
                                return true;
                            case ValueContext.ElementValue:
                                RestoreLocalName();
                                m_valueContext = ValueContext.ElementEnd;
                                return true;
                            case ValueContext.ElementEmpty:
                            case ValueContext.ElementEnd:
                                RestoreLocalName();
                                break;
                        }
                        break;
                    case JSONElementType.EndArray:
                    case JSONElementType.EndObject:
                        RestoreLocalName();
                        break;
                }
                string itemName = m_parser.ElementType == JSONElementType.None ? m_rootName : m_flattenArrays ? m_localName.localName : m_arrayItemName;
                while (m_parser.Read()) {
                    if (!String.IsNullOrEmpty(m_parser.ElementName))
                        itemName = m_parser.ElementName;

                    switch (m_parser.ElementType) {
                        case JSONElementType.BeginArray:
                            if (m_flattenArrays && !m_localName.isArray) {
                                m_depthCorrection--;
                                SetLocalName(itemName, true);
                                continue;
                            }
                            SetLocalName(itemName, true);
                            break;
                        case JSONElementType.BeginObject:
                            SetLocalName(itemName);
                            break;
                        case JSONElementType.EndArray:
                            if (m_flattenArrays && !m_localNameStack.Peek().isArray) {
                                RestoreLocalName();
                                m_depthCorrection++;
                                continue;
                            }
                            break;
                        case JSONElementType.EndObject:
                            break;
                        case JSONElementType.Value:
                            SetLocalName(itemName);
                            m_valueContext = m_parser.ElementValue == null ? ValueContext.ElementEmpty : ValueContext.ElementStart;
                            break;
                    }
                    return true;
                }

                m_state = ReadState.EndOfFile;
                return false;
            } catch {
                m_state = ReadState.Error;
                throw;
            }
        }

        public override bool ReadAttributeValue() {
            return false;
        }

        public override ReadState ReadState {
            get { return m_state; }
        }

        public override void ResolveEntity() {
            // do nothing
        }

        public override string Value {
            get {
                if (m_parser.ElementValue == null)
                    return String.Empty;
                if (Convert.GetTypeCode(m_parser.ElementValue) == TypeCode.Double)
                    return ((double)m_parser.ElementValue).ToString(CultureInfo.InvariantCulture);
                return m_parser.ElementValue.ToString();
            }
        }

        void SetLocalName(string name) {
            m_localNameStack.Push(m_localName);
            m_localName.localName = name;
            m_localName.isArray = false;
        }

        void SetLocalName(string name, bool isArray) {
            m_localNameStack.Push(m_localName);
            m_localName.localName = name;
            m_localName.isArray = isArray;
        }

        void RestoreLocalName() {
            m_localName = m_localNameStack.Pop();
        }

        public override void Close() {

        }

        protected override void Dispose(bool disposing) {
            #if MONO
            disposing = true;
            #endif
            if (disposing) {
                m_parser.Dispose();
            }
            base.Dispose(disposing);
        }

        public static JSONXmlReader Create(string file, JSONXmlReaderOptions options) {
            return Create(File.OpenText(file), options);
        }

        /// <summary>
        /// Creates the XmlReader for the specified text stream with JSON data.
        /// </summary>
        /// <param name="reader">Text reader.</param>
        /// <param name="options">Options.</param>
        /// <remarks>
        /// The reader will be disposed when the XmlReader is disposed.
        /// </remarks>
        public static JSONXmlReader Create(TextReader reader,  JSONXmlReaderOptions options) {
            return new JSONXmlReader(new JSONParser(reader), options);
        }

        /// <summary>
        /// Creates the XmlReader for the specified stream with JSON data.
        /// </summary>
        /// <param name="stream">Stream.</param>
        /// <param name="options">Options.</param>
        /// <remarks>
        /// The stream will be disposed when the XmlReader is disposed.
        /// </remarks>
        public static JSONXmlReader Create(Stream stream,  JSONXmlReaderOptions options) {
            Safe.ArgumentNotNull(stream, "stream");
            // HACK don't dispose StreaReader to keep stream opened
            return Create(new StreamReader(stream), options);
        }
    }
}