Files
libremetaverse/LibreMetaverse.StructuredData/JSON/JsonWriter.cs

438 lines
11 KiB
C#
Raw Normal View History

#region Header
/*
* JsonWriter.cs
* Stream-like facility to output JSON text.
*
* The authors disclaim copyright to this source code. For more details, see
* the COPYING file included with this distribution.
*/
#endregion
using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Text;
namespace LitJson
{
internal enum Condition
{
InArray,
InObject,
NotAProperty,
Property,
Value
}
internal class WriterContext
{
public int Count;
public bool InArray;
public bool InObject;
public bool ExpectingValue;
public int Padding;
}
public class JsonWriter
{
#region Fields
private static NumberFormatInfo number_format;
private WriterContext context;
private Stack<WriterContext> ctx_stack;
private bool has_reached_end;
private char[] hex_seq;
private int indentation;
private int indent_value;
private StringBuilder inst_string_builder;
2017-03-12 09:51:41 -05:00
#endregion
#region Properties
public int IndentValue {
get { return indent_value; }
set {
indentation = (indentation / indent_value) * value;
indent_value = value;
}
}
2017-03-12 09:51:41 -05:00
public bool PrettyPrint { get; set; }
public TextWriter TextWriter { get; }
public bool Validate { get; set; }
#endregion
#region Constructors
static JsonWriter ()
{
number_format = NumberFormatInfo.InvariantInfo;
}
public JsonWriter ()
{
inst_string_builder = new StringBuilder ();
2017-03-12 09:51:41 -05:00
TextWriter = new StringWriter (inst_string_builder);
Init ();
}
public JsonWriter (StringBuilder sb) :
this (new StringWriter (sb))
{
}
public JsonWriter (TextWriter writer)
{
2019-06-08 17:58:54 -05:00
this.TextWriter = writer ?? throw new ArgumentNullException (nameof(writer));
Init ();
}
#endregion
#region Private Methods
private void DoValidation (Condition cond)
{
if (! context.ExpectingValue)
context.Count++;
2017-03-12 09:51:41 -05:00
if (! Validate)
return;
if (has_reached_end)
throw new JsonException (
"A complete JSON symbol has already been written");
switch (cond) {
case Condition.InArray:
if (! context.InArray)
throw new JsonException (
"Can't close an array here");
break;
case Condition.InObject:
if (! context.InObject || context.ExpectingValue)
throw new JsonException (
"Can't close an object here");
break;
case Condition.NotAProperty:
if (context.InObject && ! context.ExpectingValue)
throw new JsonException (
"Expected a property");
break;
case Condition.Property:
if (! context.InObject || context.ExpectingValue)
throw new JsonException (
"Can't add a property here");
break;
case Condition.Value:
if (! context.InArray &&
(! context.InObject || ! context.ExpectingValue))
throw new JsonException (
"Can't add a value here");
break;
}
}
private void Init ()
{
has_reached_end = false;
hex_seq = new char[4];
indentation = 0;
indent_value = 4;
2017-03-12 09:51:41 -05:00
PrettyPrint = false;
Validate = true;
ctx_stack = new Stack<WriterContext> ();
context = new WriterContext ();
ctx_stack.Push (context);
}
private static void IntToHex (int n, char[] hex)
{
for (int i = 0; i < 4; i++) {
2017-03-12 09:51:41 -05:00
var num = n % 16;
if (num < 10)
hex[3 - i] = (char) ('0' + num);
else
hex[3 - i] = (char) ('A' + (num - 10));
n >>= 4;
}
}
private void Indent ()
{
2017-03-12 09:51:41 -05:00
if (PrettyPrint)
indentation += indent_value;
}
private void Put (string str)
{
2017-03-12 09:51:41 -05:00
if (PrettyPrint && ! context.ExpectingValue)
for (int i = 0; i < indentation; i++)
2017-03-12 09:51:41 -05:00
TextWriter.Write (' ');
2017-03-12 09:51:41 -05:00
TextWriter.Write (str);
}
2017-03-12 09:51:41 -05:00
private void PutNewline (bool add_comma = true)
{
if (add_comma && ! context.ExpectingValue &&
context.Count > 1)
2017-03-12 09:51:41 -05:00
TextWriter.Write (',');
2017-03-12 09:51:41 -05:00
if (PrettyPrint && ! context.ExpectingValue)
TextWriter.Write ('\n');
}
private void PutString (string str)
{
2017-03-12 09:51:41 -05:00
Put (string.Empty);
2017-03-12 09:51:41 -05:00
TextWriter.Write ('"');
int n = str.Length;
for (int i = 0; i < n; i++) {
switch (str[i]) {
case '\n':
2017-03-12 09:51:41 -05:00
TextWriter.Write ("\\n");
continue;
case '\r':
2017-03-12 09:51:41 -05:00
TextWriter.Write ("\\r");
continue;
case '\t':
2017-03-12 09:51:41 -05:00
TextWriter.Write ("\\t");
continue;
case '"':
case '\\':
2017-03-12 09:51:41 -05:00
TextWriter.Write ('\\');
TextWriter.Write (str[i]);
continue;
case '\f':
2017-03-12 09:51:41 -05:00
TextWriter.Write ("\\f");
continue;
case '\b':
2017-03-12 09:51:41 -05:00
TextWriter.Write ("\\b");
continue;
}
2017-03-12 09:51:41 -05:00
if (str[i] >= 32 && str[i] <= 126) {
TextWriter.Write (str[i]);
continue;
}
// Default, turn into a \uXXXX sequence
IntToHex ((int) str[i], hex_seq);
2017-03-12 09:51:41 -05:00
TextWriter.Write ("\\u");
TextWriter.Write (hex_seq);
}
2017-03-12 09:51:41 -05:00
TextWriter.Write ('"');
}
private void Unindent ()
{
2017-03-12 09:51:41 -05:00
if (PrettyPrint)
indentation -= indent_value;
}
#endregion
public override string ToString ()
{
2017-03-12 09:51:41 -05:00
return inst_string_builder?.ToString () ?? string.Empty;
}
public void Reset ()
{
has_reached_end = false;
ctx_stack.Clear ();
context = new WriterContext ();
ctx_stack.Push (context);
2017-03-12 09:51:41 -05:00
inst_string_builder?.Remove(0, inst_string_builder.Length);
}
public void Write (bool boolean)
{
DoValidation (Condition.Value);
PutNewline ();
Put (boolean ? "true" : "false");
context.ExpectingValue = false;
}
public void Write (decimal number)
{
DoValidation (Condition.Value);
PutNewline ();
Put (Convert.ToString (number, number_format));
context.ExpectingValue = false;
}
public void Write (double number)
{
DoValidation (Condition.Value);
PutNewline ();
if (double.IsNaN(number) || double.IsInfinity(number))
Put("null");
else
{
string str = Convert.ToString(number, number_format);
Put(str);
if (str.IndexOf('.') == -1 && str.IndexOf('E') == -1)
2017-03-12 09:51:41 -05:00
TextWriter.Write(".0");
}
context.ExpectingValue = false;
}
public void Write (int number)
{
DoValidation (Condition.Value);
PutNewline ();
Put (Convert.ToString (number, number_format));
context.ExpectingValue = false;
}
public void Write (long number)
{
DoValidation (Condition.Value);
PutNewline ();
Put (Convert.ToString (number, number_format));
context.ExpectingValue = false;
}
public void Write (string str)
{
DoValidation (Condition.Value);
PutNewline ();
if (str == null)
Put ("null");
else
PutString (str);
context.ExpectingValue = false;
}
public void Write (ulong number)
{
DoValidation (Condition.Value);
PutNewline ();
Put (Convert.ToString (number, number_format));
context.ExpectingValue = false;
}
public void WriteArrayEnd ()
{
DoValidation (Condition.InArray);
PutNewline (false);
ctx_stack.Pop ();
if (ctx_stack.Count == 1)
has_reached_end = true;
else {
context = ctx_stack.Peek ();
context.ExpectingValue = false;
}
Unindent ();
Put ("]");
}
public void WriteArrayStart ()
{
DoValidation (Condition.NotAProperty);
PutNewline ();
Put ("[");
2017-03-12 09:51:41 -05:00
context = new WriterContext {InArray = true};
ctx_stack.Push (context);
Indent ();
}
public void WriteObjectEnd ()
{
DoValidation (Condition.InObject);
PutNewline (false);
ctx_stack.Pop ();
if (ctx_stack.Count == 1)
has_reached_end = true;
else {
context = ctx_stack.Peek ();
context.ExpectingValue = false;
}
Unindent ();
Put ("}");
}
public void WriteObjectStart ()
{
DoValidation (Condition.NotAProperty);
PutNewline ();
Put ("{");
2017-03-12 09:51:41 -05:00
context = new WriterContext {InObject = true};
ctx_stack.Push (context);
Indent ();
}
public void WritePropertyName (string property_name)
{
DoValidation (Condition.Property);
PutNewline ();
PutString (property_name);
2017-03-12 09:51:41 -05:00
if (PrettyPrint) {
if (property_name.Length > context.Padding)
context.Padding = property_name.Length;
for (int i = context.Padding - property_name.Length;
i >= 0; i--)
2017-03-12 09:51:41 -05:00
TextWriter.Write (' ');
2017-03-12 09:51:41 -05:00
TextWriter.Write (": ");
} else
2017-03-12 09:51:41 -05:00
TextWriter.Write (':');
context.ExpectingValue = true;
}
}
}