view Implab/Formats/JSON/JSONXmlReader.cs @ 166:b84cdbe82e7f ref20160224

sync
author cin
date Mon, 29 Feb 2016 18:41:01 +0300
parents 419aa51b04fd
children c32688129f14
line wrap: on
line source

using Implab;
using Implab.Parsing;
using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Xml;

namespace Implab.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 = 0;

        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;
            else
                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 != System.Xml.ReadState.Interactive && m_state != System.Xml.ReadState.Initial)
                return false;

            if (m_state == ReadState.Initial)
                m_state = System.Xml.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;
                            } else {
                                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;
                        default:
                            break;
                    }
                    return true;
                }

                m_state = System.Xml.ReadState.EndOfFile;
                return false;
            } catch {
                m_state = System.Xml.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);
                else
                    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, true), 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);
        }
    }
}