JSONWriter.cs
319 lines
| 10.4 KiB
| text/x-csharp
|
CSharpLexer
|
|
r55 | using System; | ||
| using System.Collections.Generic; | ||||
| using System.IO; | ||||
|
|
r142 | using System.Globalization; | ||
|
|
r150 | using System.Diagnostics; | ||
|
|
r55 | |||
| namespace Implab.JSON { | ||||
| public class JSONWriter { | ||||
| struct Context { | ||||
| public bool needComma; | ||||
| public JSONElementContext element; | ||||
| } | ||||
| Stack<Context> m_contextStack = new Stack<Context>(); | ||||
| Context m_context; | ||||
|
|
r150 | const int BUFFER_SIZE = 64; | ||
|
|
r55 | TextWriter m_writer; | ||
|
|
r72 | readonly bool m_indent = true; | ||
| readonly int m_indentSize = 4; | ||||
|
|
r150 | readonly char[] m_buffer = new char[BUFFER_SIZE]; | ||
| int m_bufferPos; | ||||
|
|
r55 | |||
|
|
r150 | static readonly char [] _hex = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' }; | ||
|
|
r55 | 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; | ||||
| } | ||||
|
|
r72 | 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(' '); | ||||
| } | ||||
| } | ||||
|
|
r55 | void WriteMemberName(string name) { | ||
| Safe.ArgumentNotEmpty(name, "name"); | ||||
| if (m_context.element != JSONElementContext.Object) | ||||
| OperationNotApplicable("WriteMember"); | ||||
| if (m_context.needComma) | ||||
|
|
r72 | m_writer.Write(","); | ||
| WriteIndent(); | ||||
|
|
r55 | 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) { | ||||
|
|
r141 | 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 { | ||||
|
|
r55 | OperationNotApplicable("WriteValue"); | ||
|
|
r141 | } | ||
|
|
r55 | } | ||
| public void WriteValue(bool value) { | ||||
|
|
r141 | 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 { | ||||
|
|
r55 | OperationNotApplicable("WriteValue"); | ||
|
|
r141 | } | ||
|
|
r55 | } | ||
| public void WriteValue(double value) { | ||||
|
|
r141 | 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 { | ||||
|
|
r55 | OperationNotApplicable("WriteValue"); | ||
|
|
r141 | } | ||
|
|
r55 | } | ||
| public void BeginObject() { | ||||
| if (m_context.element != JSONElementContext.None && m_context.element != JSONElementContext.Array) | ||||
| OperationNotApplicable("BeginObject"); | ||||
| if (m_context.needComma) | ||||
|
|
r72 | m_writer.Write(","); | ||
| WriteIndent(); | ||||
|
|
r55 | m_context.needComma = true; | ||
| m_contextStack.Push(m_context); | ||||
| m_context = new Context { element = JSONElementContext.Object, needComma = false }; | ||||
|
|
r72 | m_writer.Write("{"); | ||
|
|
r55 | } | ||
| public void BeginObject(string name) { | ||||
| WriteMemberName(name); | ||||
| m_contextStack.Push(m_context); | ||||
| m_context = new Context { element = JSONElementContext.Object, needComma = false }; | ||||
|
|
r72 | m_writer.Write("{"); | ||
|
|
r55 | } | ||
| public void EndObject() { | ||||
| if (m_context.element != JSONElementContext.Object) | ||||
|
|
r141 | OperationNotApplicable("EndObject"); | ||
|
|
r72 | |||
|
|
r55 | m_context = m_contextStack.Pop(); | ||
|
|
r141 | if (m_contextStack.Count == 0) | ||
| m_context.element = JSONElementContext.Closed; | ||||
|
|
r72 | WriteIndent(); | ||
| m_writer.Write("}"); | ||||
|
|
r55 | } | ||
| public void BeginArray() { | ||||
| if (m_context.element != JSONElementContext.None && m_context.element != JSONElementContext.Array) | ||||
| throw new InvalidOperationException(); | ||||
|
|
r72 | if (m_context.needComma) { | ||
| m_writer.Write(","); | ||||
| } | ||||
|
|
r55 | m_context.needComma = true; | ||
|
|
r72 | WriteIndent(); | ||
|
|
r55 | m_contextStack.Push(m_context); | ||
| m_context = new Context { element = JSONElementContext.Array, needComma = false }; | ||||
|
|
r72 | m_writer.Write("["); | ||
|
|
r55 | } | ||
| public void BeginArray(string name) { | ||||
| WriteMemberName(name); | ||||
| m_contextStack.Push(m_context); | ||||
| m_context = new Context { element = JSONElementContext.Array, needComma = false }; | ||||
|
|
r72 | m_writer.Write("["); | ||
|
|
r55 | } | ||
| public void EndArray() { | ||||
| if (m_context.element != JSONElementContext.Array) | ||||
| OperationNotApplicable("EndArray"); | ||||
| m_context = m_contextStack.Pop(); | ||||
|
|
r141 | if (m_contextStack.Count == 0) | ||
| m_context.element = JSONElementContext.Closed; | ||||
|
|
r72 | WriteIndent(); | ||
| m_writer.Write("]"); | ||||
|
|
r55 | } | ||
| void Write(bool value) { | ||||
| m_writer.Write(value ? "true" : "false"); | ||||
| } | ||||
|
|
r150 | |||
| void FlushBuffer() { | ||||
| if (m_bufferPos > 0) { | ||||
| m_writer.Write(m_buffer, 0, m_bufferPos); | ||||
| m_bufferPos = 0; | ||||
| } | ||||
| } | ||||
|
|
r55 | |||
| void Write(string value) { | ||||
|
|
r115 | if (value == null) { | ||
|
|
r55 | m_writer.Write("null"); | ||
|
|
r115 | return; | ||
| } | ||||
|
|
r55 | |||
|
|
r150 | Debug.Assert(m_bufferPos == 0); | ||
|
|
r55 | var chars = value.ToCharArray(); | ||
|
|
r150 | m_buffer[m_bufferPos++] = '"'; | ||
|
|
r119 | // Analysis disable once ForCanBeConvertedToForeach | ||
|
|
r55 | for (int i = 0; i < chars.Length; i++) { | ||
| var ch = chars[i]; | ||||
|
|
r150 | char[] escapeSeq; | ||
|
|
r55 | switch (ch) { | ||
| case '\b': | ||||
|
|
r150 | escapeSeq = _escapeBKS; | ||
|
|
r55 | break; | ||
| case '\f': | ||||
|
|
r150 | escapeSeq = _escapeFWD; | ||
|
|
r55 | break; | ||
| case '\r': | ||||
|
|
r150 | escapeSeq = _escapeCR; | ||
|
|
r55 | break; | ||
| case '\n': | ||||
|
|
r150 | escapeSeq = _escapeNL; | ||
|
|
r55 | break; | ||
| case '\t': | ||||
|
|
r150 | escapeSeq = _escapeTAB; | ||
|
|
r55 | break; | ||
| case '\\': | ||||
|
|
r150 | escapeSeq = _escapeBSLASH; | ||
|
|
r55 | break; | ||
| case '"': | ||||
|
|
r150 | escapeSeq = _escapeQ; | ||
|
|
r55 | break; | ||
| default: | ||||
| if (ch < 0x20) { | ||||
|
|
r150 | 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]; | ||||
|
|
r55 | } else { | ||
|
|
r150 | if (m_bufferPos >= BUFFER_SIZE) | ||
| FlushBuffer(); | ||||
| m_buffer[m_bufferPos++] = ch; | ||||
|
|
r55 | } | ||
|
|
r150 | continue; | ||
|
|
r55 | } | ||
|
|
r150 | |||
| if (m_bufferPos + escapeSeq.Length > BUFFER_SIZE) | ||||
| FlushBuffer(); | ||||
| Array.Copy(escapeSeq, 0, m_buffer, m_bufferPos, escapeSeq.Length); | ||||
| m_bufferPos += escapeSeq.Length; | ||||
|
|
r55 | } | ||
|
|
r150 | if (m_bufferPos >= BUFFER_SIZE) | ||
| FlushBuffer(); | ||||
| m_buffer[m_bufferPos++] = '"'; | ||||
| FlushBuffer(); | ||||
|
|
r55 | } | ||
| void Write(double value) { | ||||
|
|
r142 | 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)); | ||||
|
|
r55 | } | ||
| void OperationNotApplicable(string opName) { | ||||
| throw new InvalidOperationException(String.Format("The operation '{0}' isn't applicable in the context of '{1}'", opName, m_context.element )); | ||||
| } | ||||
| } | ||||
| } | ||||
