view Implab/Formats/JSON/JsonReader.cs @ 234:8dd666e6b6bf v2

Added implab nuget spec
author cin
date Thu, 05 Oct 2017 09:21:23 +0300
parents 5f7a3e1d32b9
children
line wrap: on
line source

using System;
using System.Diagnostics;
using System.IO;
using Implab.Automaton;
using Implab.Automaton.RegularExpressions;
using System.Linq;
using Implab.Components;
using System.Collections.Generic;
using System.Text;
using System.Globalization;

namespace Implab.Formats.Json {
    /// <summary>
    /// Pull парсер JSON данных.
    /// </summary>
    /// <remarks>
    /// Следует отметить отдельную интерпретацию свойства <see cref="Level"/>,
    /// оно означает текущий уровень вложенности объектов, однако закрывающий
    /// элемент объекта и массива имеет уровень меньше, чем сам объект.
    /// <code>
    /// { // Level = 1
    ///     "name" : "Peter", // Level = 1
    ///     "address" : { // Level = 2
    ///         city : "Stern" // Level = 2
    ///     } // Level = 1
    /// } // Level = 0
    /// </code>
    /// </remarks>
    public class JsonReader : Disposable {

        enum MemberContext {
            MemberName,
            MemberValue
        }

        #region Parser rules
        struct ParserContext {
            readonly int[,] m_dfa;
            int m_state;

            readonly JsonElementContext m_elementContext;

            public ParserContext(int[,] dfa, int state, JsonElementContext context) {
                m_dfa = dfa;
                m_state = state;
                m_elementContext = context;
            }

            public bool Move(JsonTokenType token) {
                var next = m_dfa[m_state, (int)token];
                if (next == AutomatonConst.UNREACHABLE_STATE)
                    return false;
                m_state = next;
                return true;
            }

            public JsonElementContext ElementContext {
                get { return m_elementContext; }
            }
        }

        static readonly ParserContext _jsonContext;
        static readonly ParserContext _objectContext;
        static readonly ParserContext _arrayContext;

        static JsonReader() {

            var valueExpression = MakeToken(JsonTokenType.BeginArray, JsonTokenType.BeginObject, JsonTokenType.Literal, JsonTokenType.Number, JsonTokenType.String);
            var memberExpression = MakeToken(JsonTokenType.String).Cat(MakeToken(JsonTokenType.NameSeparator)).Cat(valueExpression);

            var objectExpression = memberExpression
                .Cat(
                    MakeToken(JsonTokenType.ValueSeparator)
                    .Cat(memberExpression)
                    .EClosure()
                )
                .Optional()
                .Cat(MakeToken(JsonTokenType.EndObject))
                .End();
            
            var arrayExpression = valueExpression
                .Cat(
                    MakeToken(JsonTokenType.ValueSeparator)
                    .Cat(valueExpression)
                    .EClosure()
                )
                .Optional()
                .Cat(MakeToken(JsonTokenType.EndArray))
                .End();

            var jsonExpression = valueExpression.End();

            _jsonContext = CreateParserContext(jsonExpression, JsonElementContext.None);
            _objectContext = CreateParserContext(objectExpression, JsonElementContext.Object);
            _arrayContext = CreateParserContext(arrayExpression, JsonElementContext.Array);
        }

        static Token MakeToken(params JsonTokenType[] input) {
            return Token.New( input.Select(t => (int)t).ToArray() );
        }

        static ParserContext CreateParserContext(Token expr, JsonElementContext context) {
            
            var dfa = new DFATable();
            var builder = new RegularExpressionVisitor(dfa);
            expr.Accept(builder);
            builder.BuildDFA();

            return new ParserContext(dfa.CreateTransitionTable(), dfa.InitialState, context);
        }

        #endregion

        readonly JsonScanner m_scanner;
        // json starts from the value context and may content even a single literal
        MemberContext m_memberContext = MemberContext.MemberValue;

        JsonElementType m_elementType;
        object m_elementValue;
        string m_memberName = String.Empty;

        Stack<ParserContext> m_stack = new Stack<ParserContext>();
        ParserContext m_context = _jsonContext;

        /// <summary>
        /// Создает новый парсер на основе строки, содержащей JSON
        /// </summary>
        /// <param name="text"></param>
        JsonReader(JsonScanner scanner) {
            m_scanner = scanner;
        }
        
        public int Level {
            get { return m_stack.Count; }
        }

        /// <summary>
        /// Тип текущего элемента на котором стоит парсер.
        /// </summary>
        public JsonElementType ElementType {
            get { return m_elementType; }
        }

        /// <summary>
        /// Имя элемента - имя свойства родительского контейнера. Для элементов массивов и корневого всегда
        /// пустая строка.
        /// </summary>
        public string ElementName {
            get { return m_memberName; }
        }

        /// <summary>
        /// Значение элемента. Только для элементов типа <see cref="JsonElementType.Value"/>, для остальных <c>null</c>
        /// </summary>
        public object ElementValue {
            get { return m_elementValue; }
        }

        /// <summary>
        /// Читает слеюудущий объект из потока
        /// </summary>
        /// <returns><c>true</c> - операция чтения прошла успешно, <c>false</c> - конец данных</returns>
        public bool Read() {
            string tokenValue;
            JsonTokenType tokenType;

            m_memberName = String.Empty;

            while (m_scanner.ReadToken(out tokenValue, out tokenType)) {
                if(!m_context.Move(tokenType))
                    UnexpectedToken(tokenValue, tokenType);

                switch (tokenType) {
                    case JsonTokenType.BeginObject:
                        m_stack.Push(m_context);
                        m_context = _objectContext;

                        m_elementValue = null;
                        m_memberContext = MemberContext.MemberName;
                        m_elementType = JsonElementType.BeginObject;
                        return true;
                    case JsonTokenType.EndObject:
                        if (m_stack.Count == 0)
                            UnexpectedToken(tokenValue, tokenType);
                        m_context = m_stack.Pop();

                        m_elementValue = null;
                        m_elementType = JsonElementType.EndObject;
                        return true;
                    case JsonTokenType.BeginArray:
                        m_stack.Push(m_context);
                        m_context = _arrayContext;

                        m_elementValue = null;
                        m_memberContext = MemberContext.MemberValue;
                        m_elementType = JsonElementType.BeginArray;
                        return true;
                    case JsonTokenType.EndArray:
                        if (m_stack.Count == 0)
                            UnexpectedToken(tokenValue, tokenType);
                        m_context = m_stack.Pop();

                        m_elementValue = null;
                        m_elementType = JsonElementType.EndArray;
                        return true;
                    case JsonTokenType.String:
                        if (m_memberContext == MemberContext.MemberName) {
                            m_memberName = tokenValue;
                            break;
                        }
                        m_elementType = JsonElementType.Value;
                        m_elementValue = tokenValue;
                        return true;
                    case JsonTokenType.Number:
                        m_elementType = JsonElementType.Value;
                        m_elementValue = double.Parse(tokenValue, CultureInfo.InvariantCulture);
                        return true;
                    case JsonTokenType.Literal:
                        m_elementType = JsonElementType.Value;
                        m_elementValue = ParseLiteral(tokenValue);
                        return true;
                    case JsonTokenType.NameSeparator:
                        m_memberContext = MemberContext.MemberValue;
                        break;
                    case JsonTokenType.ValueSeparator:
                        m_memberContext = m_context.ElementContext == JsonElementContext.Object ? MemberContext.MemberName : MemberContext.MemberValue;
                        break;
                    default:
                        UnexpectedToken(tokenValue, tokenType);
                        break;
                }
            }
            if (m_context.ElementContext != JsonElementContext.None)
                throw new ParserException("Unexpedted end of data");

            Eof = true;

            return false;
        }

        object ParseLiteral(string literal) {
            switch (literal) {
                case "null":
                    return null;
                case "false":
                    return false;
                case "true":
                    return true;
                default:
                    UnexpectedToken(literal, JsonTokenType.Literal);
                    return null; // avoid compliler error
            }
        }

        void UnexpectedToken(object value, JsonTokenType tokenType) {
            throw new ParserException(String.Format("Unexpected token {0}: '{1}'", tokenType, value));
        }


        /// <summary>
        /// Признак конца потока
        /// </summary>
        public bool Eof {
            get;
            private set;
        }

        protected override void Dispose(bool disposing) {
            if (disposing)
                m_scanner.Dispose();
        }

        /// <summary>
        /// Переходит в конец текущего объекта.
        /// </summary>
        public void SeekElementEnd() {
            var level = Level - 1;

            Debug.Assert(level >= 0);

            while (Level != level)
                Read();
        }

        public static JsonReader Create(string file, Encoding encoding) {
            return new JsonReader(JsonTextScanner.Create(file, encoding));
        }

        public static JsonReader Create(string file) {
            return new JsonReader(JsonTextScanner.Create(file));
        }

        public static JsonReader Create(Stream stream, Encoding encoding) {
            return new JsonReader(JsonTextScanner.Create(stream, encoding));
        }

        public static JsonReader Create(Stream stream) {
            return new JsonReader(JsonTextScanner.Create(stream));
        }

        public static JsonReader Create(TextReader reader) {
            return new JsonReader(JsonTextScanner.Create(reader));
        }

        public static JsonReader ParseString(string data) {
            return new JsonReader(JsonStringScanner.Create(data));
        }

        public static JsonReader ParseString(string data, int offset, int length) {
            return new JsonReader(JsonStringScanner.Create(data, offset, length));
        }

        public static JsonReader ParseString(char[] data, int offset, int lenght) {
            return new JsonReader(JsonStringScanner.Create(data, offset, lenght));
        }
    }

}