JsonWriter.cs
319 lines
| 10.4 KiB
| text/x-csharp
|
CSharpLexer
cin
|
r289 | 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 )); | ||||
} | ||||
} | ||||
} | ||||