diff Implab/Formats/Json/JsonWriter.cs @ 235:b49969a7043c v2

Слияние
author cin
date Thu, 05 Oct 2017 09:24:49 +0300
parents 3e26338eb977
children
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Implab/Formats/Json/JsonWriter.cs	Thu Oct 05 09:24:49 2017 +0300
@@ -0,0 +1,319 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Globalization;
+using System.Diagnostics;
+
+namespace Implab.Formats.Json {
+    public class JsonWriter {
+        struct Context {
+            public bool needComma;
+            public JsonElementContext element;
+        }
+        Stack<Context> m_contextStack = new Stack<Context>();
+        Context m_context;
+
+        const int BUFFER_SIZE = 64;
+
+        TextWriter m_writer;
+        readonly bool m_indent = true;
+        readonly int m_indentSize = 4;
+        readonly char[] m_buffer = new char[BUFFER_SIZE];
+        int m_bufferPos;
+
+        static readonly char [] _hex = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' };
+        static readonly char [] _escapeBKS,
+            _escapeFWD,
+            _escapeCR,
+            _escapeNL,
+            _escapeTAB,
+            _escapeBSLASH,
+            _escapeQ;
+
+        static JsonWriter() {
+            _escapeBKS = "\\b".ToCharArray();
+            _escapeFWD = "\\f".ToCharArray();
+            _escapeCR = "\\r".ToCharArray();
+            _escapeNL = "\\n".ToCharArray();
+            _escapeTAB = "\\t".ToCharArray();
+            _escapeBSLASH = "\\\\".ToCharArray();
+            _escapeQ = "\\\"".ToCharArray();
+        }
+
+        public JsonWriter(TextWriter writer) {
+            Safe.ArgumentNotNull(writer, "writer");
+            m_writer = writer;
+        }
+
+        public JsonWriter(TextWriter writer, bool indent) {
+            Safe.ArgumentNotNull(writer, "writer");
+
+            m_writer = writer;
+            m_indent = indent;
+        }
+
+        void WriteIndent() {
+            if (m_indent) {
+                var indent = new char[m_contextStack.Count * m_indentSize + 1];
+                indent[0] = '\n';
+                for (int i = 1; i < indent.Length; i++)
+                    indent[i] = ' ';
+                m_writer.Write(new String(indent));
+            } else {
+                m_writer.Write(' ');
+            }
+        }
+
+        void WriteMemberName(string name) {
+            Safe.ArgumentNotEmpty(name, "name");
+            if (m_context.element != JsonElementContext.Object)
+                OperationNotApplicable("WriteMember");
+            if (m_context.needComma)
+                m_writer.Write(",");
+
+            WriteIndent();
+            m_context.needComma = true;
+            Write(name);
+            m_writer.Write(" : ");
+        }
+
+        public void WriteValue(string name, string value) {
+            WriteMemberName(name);
+            Write(value);            
+        }
+
+        public void WriteValue(string name, bool value) {
+            WriteMemberName(name);
+            Write(value);
+        }
+
+        public void WriteValue(string name, double value) {
+            WriteMemberName(name);
+            Write(value);
+        }
+
+        public void WriteValue(string value) {
+            if (m_context.element == JsonElementContext.Array) {
+
+                if (m_context.needComma)
+                    m_writer.Write(",");
+                WriteIndent();
+                m_context.needComma = true;
+
+                Write(value);
+            } else if (m_context.element == JsonElementContext.None) {
+                Write(value);
+                m_context.element = JsonElementContext.Closed;
+            } else {
+                OperationNotApplicable("WriteValue");
+            }
+        }
+
+        public void WriteValue(bool value) {
+            if (m_context.element == JsonElementContext.Array) {
+
+                if (m_context.needComma)
+                    m_writer.Write(",");
+                WriteIndent();
+                m_context.needComma = true;
+
+                Write(value);
+            } else if (m_context.element == JsonElementContext.None) {
+                Write(value);
+                m_context.element = JsonElementContext.Closed;
+            } else {
+                OperationNotApplicable("WriteValue");
+            }
+        }
+
+        public void WriteValue(double value) {
+            if (m_context.element == JsonElementContext.Array) {
+
+                if (m_context.needComma)
+                    m_writer.Write(",");
+                WriteIndent();
+                m_context.needComma = true;
+
+                Write(value);
+            } else if (m_context.element == JsonElementContext.None) {
+                Write(value);
+                m_context.element = JsonElementContext.Closed;
+            } else {
+                OperationNotApplicable("WriteValue");
+            }
+        }
+        
+        public void BeginObject() {
+            if (m_context.element != JsonElementContext.None && m_context.element != JsonElementContext.Array)
+                OperationNotApplicable("BeginObject");
+            if (m_context.needComma)
+                m_writer.Write(",");
+
+            WriteIndent();
+
+            m_context.needComma = true;
+
+            m_contextStack.Push(m_context);
+
+            m_context = new Context { element = JsonElementContext.Object, needComma = false };
+            m_writer.Write("{");
+        }
+
+        public void BeginObject(string name) {
+            WriteMemberName(name);
+
+            m_contextStack.Push(m_context);
+
+            m_context = new Context { element = JsonElementContext.Object, needComma = false };
+            m_writer.Write("{");
+        }
+
+        public void EndObject() {
+            if (m_context.element != JsonElementContext.Object)
+                OperationNotApplicable("EndObject");
+
+            m_context = m_contextStack.Pop();
+            if (m_contextStack.Count == 0)
+                m_context.element = JsonElementContext.Closed;
+            WriteIndent();
+            m_writer.Write("}");
+        }
+
+        public void BeginArray() {
+            if (m_context.element != JsonElementContext.None && m_context.element != JsonElementContext.Array)
+                throw new InvalidOperationException();
+            if (m_context.needComma) {
+                m_writer.Write(",");
+
+            }
+            m_context.needComma = true;
+
+            WriteIndent();
+            m_contextStack.Push(m_context);
+            m_context = new Context { element = JsonElementContext.Array, needComma = false };
+            m_writer.Write("[");
+        }
+
+        public void BeginArray(string name) {
+            WriteMemberName(name);
+
+            m_contextStack.Push(m_context);
+
+            m_context = new Context { element = JsonElementContext.Array, needComma = false };
+            m_writer.Write("[");
+        }
+
+        public void EndArray() {
+            if (m_context.element != JsonElementContext.Array)
+                OperationNotApplicable("EndArray");
+
+            m_context = m_contextStack.Pop();
+            if (m_contextStack.Count == 0)
+                m_context.element = JsonElementContext.Closed;
+            WriteIndent();
+            m_writer.Write("]");
+        }
+
+        void Write(bool value) {
+            m_writer.Write(value ? "true" : "false");
+        }
+
+        void FlushBuffer() {
+            if (m_bufferPos > 0) {
+                m_writer.Write(m_buffer, 0, m_bufferPos);
+                m_bufferPos = 0;
+            }
+        }
+
+        void Write(string value) {
+            if (value == null) {
+                m_writer.Write("null");
+                return;
+            }
+
+            Debug.Assert(m_bufferPos == 0);
+
+            var chars = value.ToCharArray();
+            m_buffer[m_bufferPos++] = '"';
+
+            // Analysis disable once ForCanBeConvertedToForeach
+            for (int i = 0; i < chars.Length; i++) {
+                var ch = chars[i];
+
+                char[] escapeSeq;
+
+                switch (ch) {
+                    case '\b':
+                        escapeSeq = _escapeBKS;
+                        break;
+                    case '\f':
+                        escapeSeq = _escapeFWD;
+                        break;
+                    case '\r':
+                        escapeSeq = _escapeCR;
+                        break;
+                    case '\n':
+                        escapeSeq = _escapeNL;
+                        break;
+                    case '\t':
+                        escapeSeq = _escapeTAB;
+                        break;
+                    case '\\':
+                        escapeSeq = _escapeBSLASH;
+                        break;
+                    case '"':
+                        escapeSeq = _escapeQ;
+                        break;
+                    default:
+                        if (ch < 0x20) {
+                            if (m_bufferPos + 6 > BUFFER_SIZE)
+                                FlushBuffer();
+
+                            m_buffer[m_bufferPos++] = '\\';
+                            m_buffer[m_bufferPos++] = 'u';
+                            m_buffer[m_bufferPos++] = '0';
+                            m_buffer[m_bufferPos++] = '0';
+                            m_buffer[m_bufferPos++] = _hex[ch >> 4 & 0xf];
+                            m_buffer[m_bufferPos++] = _hex[ch & 0xf];
+
+                        } else {
+                            if (m_bufferPos >= BUFFER_SIZE)
+                                FlushBuffer();
+                            m_buffer[m_bufferPos++] = ch;
+                        }
+                        continue;
+                }
+
+                if (m_bufferPos + escapeSeq.Length > BUFFER_SIZE)
+                    FlushBuffer();
+
+                Array.Copy(escapeSeq, 0, m_buffer, m_bufferPos, escapeSeq.Length);
+                m_bufferPos += escapeSeq.Length;
+
+            }
+
+            if (m_bufferPos >= BUFFER_SIZE)
+                FlushBuffer();
+            
+            m_buffer[m_bufferPos++] = '"';
+
+            FlushBuffer();
+        }
+
+        void Write(double value) {
+            if (double.IsNaN(value))
+                Write("NaN");
+            else if (double.IsNegativeInfinity(value))
+                Write("-Infinity");
+            else if (double.IsPositiveInfinity(value))
+                Write("Infinity");
+            else
+                m_writer.Write(value.ToString(CultureInfo.InvariantCulture));
+        }
+
+        void OperationNotApplicable(string opName) {
+            throw new InvalidOperationException(String.Format("The operation '{0}' isn't applicable in the context of '{1}'", opName, m_context.element ));
+        }
+        
+    }
+}