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