#region License
// Copyright (c) 2007 James Newton-King
//
// Permission is hereby granted, free of charge, to any person
// obtaining a copy of this software and associated documentation
// files (the "Software"), to deal in the Software without
// restriction, including without limitation the rights to use,
// copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the
// Software is furnished to do so, subject to the following
// conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
// OTHER DEALINGS IN THE SOFTWARE.
#endregion
using System;
using System.Collections.Generic;
using System.Globalization;
#if HAVE_BIG_INTEGER
using System.Numerics;
#endif
using System.Text;
using System.IO;
using System.Xml;
using LC.Newtonsoft.Json.Utilities;
namespace LC.Newtonsoft.Json
{
///
/// Represents a writer that provides a fast, non-cached, forward-only way of generating JSON data.
///
public partial class JsonTextWriter : JsonWriter
{
private const int IndentCharBufferSize = 12;
private readonly TextWriter _writer;
private Base64Encoder _base64Encoder;
private char _indentChar;
private int _indentation;
private char _quoteChar;
private bool _quoteName;
private bool[] _charEscapeFlags;
private char[] _writeBuffer;
private IArrayPool _arrayPool;
private char[] _indentChars;
private Base64Encoder Base64Encoder
{
get
{
if (_base64Encoder == null)
{
_base64Encoder = new Base64Encoder(_writer);
}
return _base64Encoder;
}
}
///
/// Gets or sets the writer's character array pool.
///
public IArrayPool ArrayPool
{
get => _arrayPool;
set
{
if (value == null)
{
throw new ArgumentNullException(nameof(value));
}
_arrayPool = value;
}
}
///
/// Gets or sets how many s to write for each level in the hierarchy when is set to .
///
public int Indentation
{
get => _indentation;
set
{
if (value < 0)
{
throw new ArgumentException("Indentation value must be greater than 0.");
}
_indentation = value;
}
}
///
/// Gets or sets which character to use to quote attribute values.
///
public char QuoteChar
{
get => _quoteChar;
set
{
if (value != '"' && value != '\'')
{
throw new ArgumentException(@"Invalid JavaScript string quote character. Valid quote characters are ' and "".");
}
_quoteChar = value;
UpdateCharEscapeFlags();
}
}
///
/// Gets or sets which character to use for indenting when is set to .
///
public char IndentChar
{
get => _indentChar;
set
{
if (value != _indentChar)
{
_indentChar = value;
_indentChars = null;
}
}
}
///
/// Gets or sets a value indicating whether object names will be surrounded with quotes.
///
public bool QuoteName
{
get => _quoteName;
set => _quoteName = value;
}
///
/// Initializes a new instance of the class using the specified .
///
/// The to write to.
public JsonTextWriter(TextWriter textWriter)
{
if (textWriter == null)
{
throw new ArgumentNullException(nameof(textWriter));
}
_writer = textWriter;
_quoteChar = '"';
_quoteName = true;
_indentChar = ' ';
_indentation = 2;
UpdateCharEscapeFlags();
#if HAVE_ASYNC
_safeAsync = GetType() == typeof(JsonTextWriter);
#endif
}
///
/// Flushes whatever is in the buffer to the underlying and also flushes the underlying .
///
public override void Flush()
{
_writer.Flush();
}
///
/// Closes this writer.
/// If is set to true, the underlying is also closed.
/// If is set to true, the JSON is auto-completed.
///
public override void Close()
{
base.Close();
CloseBufferAndWriter();
}
private void CloseBufferAndWriter()
{
if (_writeBuffer != null)
{
BufferUtils.ReturnBuffer(_arrayPool, _writeBuffer);
_writeBuffer = null;
}
if (CloseOutput)
{
#if HAVE_STREAM_READER_WRITER_CLOSE
_writer?.Close();
#else
_writer?.Dispose();
#endif
}
}
///
/// Writes the beginning of a JSON object.
///
public override void WriteStartObject()
{
InternalWriteStart(JsonToken.StartObject, JsonContainerType.Object);
_writer.Write('{');
}
///
/// Writes the beginning of a JSON array.
///
public override void WriteStartArray()
{
InternalWriteStart(JsonToken.StartArray, JsonContainerType.Array);
_writer.Write('[');
}
///
/// Writes the start of a constructor with the given name.
///
/// The name of the constructor.
public override void WriteStartConstructor(string name)
{
InternalWriteStart(JsonToken.StartConstructor, JsonContainerType.Constructor);
_writer.Write("new ");
_writer.Write(name);
_writer.Write('(');
}
///
/// Writes the specified end token.
///
/// The end token to write.
protected override void WriteEnd(JsonToken token)
{
switch (token)
{
case JsonToken.EndObject:
_writer.Write('}');
break;
case JsonToken.EndArray:
_writer.Write(']');
break;
case JsonToken.EndConstructor:
_writer.Write(')');
break;
default:
throw JsonWriterException.Create(this, "Invalid JsonToken: " + token, null);
}
}
///
/// Writes the property name of a name/value pair on a JSON object.
///
/// The name of the property.
public override void WritePropertyName(string name)
{
InternalWritePropertyName(name);
WriteEscapedString(name, _quoteName);
_writer.Write(':');
}
///
/// Writes the property name of a name/value pair on a JSON object.
///
/// The name of the property.
/// A flag to indicate whether the text should be escaped when it is written as a JSON property name.
public override void WritePropertyName(string name, bool escape)
{
InternalWritePropertyName(name);
if (escape)
{
WriteEscapedString(name, _quoteName);
}
else
{
if (_quoteName)
{
_writer.Write(_quoteChar);
}
_writer.Write(name);
if (_quoteName)
{
_writer.Write(_quoteChar);
}
}
_writer.Write(':');
}
internal override void OnStringEscapeHandlingChanged()
{
UpdateCharEscapeFlags();
}
private void UpdateCharEscapeFlags()
{
_charEscapeFlags = JavaScriptUtils.GetCharEscapeFlags(StringEscapeHandling, _quoteChar);
}
///
/// Writes indent characters.
///
protected override void WriteIndent()
{
// levels of indentation multiplied by the indent count
int currentIndentCount = Top * _indentation;
int newLineLen = SetIndentChars();
_writer.Write(_indentChars, 0, newLineLen + Math.Min(currentIndentCount, IndentCharBufferSize));
while ((currentIndentCount -= IndentCharBufferSize) > 0)
{
_writer.Write(_indentChars, newLineLen, Math.Min(currentIndentCount, IndentCharBufferSize));
}
}
private int SetIndentChars()
{
// Set _indentChars to be a newline followed by IndentCharBufferSize indent characters.
string writerNewLine = _writer.NewLine;
int newLineLen = writerNewLine.Length;
bool match = _indentChars != null && _indentChars.Length == IndentCharBufferSize + newLineLen;
if (match)
{
for (int i = 0; i != newLineLen; ++i)
{
if (writerNewLine[i] != _indentChars[i])
{
match = false;
break;
}
}
}
if (!match)
{
// If we're here, either _indentChars hasn't been set yet, or _writer.NewLine
// has been changed, or _indentChar has been changed.
_indentChars = (writerNewLine + new string(_indentChar, IndentCharBufferSize)).ToCharArray();
}
return newLineLen;
}
///
/// Writes the JSON value delimiter.
///
protected override void WriteValueDelimiter()
{
_writer.Write(',');
}
///
/// Writes an indent space.
///
protected override void WriteIndentSpace()
{
_writer.Write(' ');
}
private void WriteValueInternal(string value, JsonToken token)
{
_writer.Write(value);
}
#region WriteValue methods
///
/// Writes a value.
/// An error will raised if the value cannot be written as a single JSON token.
///
/// The value to write.
public override void WriteValue(object value)
{
#if HAVE_BIG_INTEGER
if (value is BigInteger i)
{
InternalWriteValue(JsonToken.Integer);
WriteValueInternal(i.ToString(CultureInfo.InvariantCulture), JsonToken.String);
}
else
#endif
{
base.WriteValue(value);
}
}
///
/// Writes a null value.
///
public override void WriteNull()
{
InternalWriteValue(JsonToken.Null);
WriteValueInternal(JsonConvert.Null, JsonToken.Null);
}
///
/// Writes an undefined value.
///
public override void WriteUndefined()
{
InternalWriteValue(JsonToken.Undefined);
WriteValueInternal(JsonConvert.Undefined, JsonToken.Undefined);
}
///
/// Writes raw JSON.
///
/// The raw JSON to write.
public override void WriteRaw(string json)
{
InternalWriteRaw();
_writer.Write(json);
}
///
/// Writes a value.
///
/// The value to write.
public override void WriteValue(string value)
{
InternalWriteValue(JsonToken.String);
if (value == null)
{
WriteValueInternal(JsonConvert.Null, JsonToken.Null);
}
else
{
WriteEscapedString(value, true);
}
}
private void WriteEscapedString(string value, bool quote)
{
EnsureWriteBuffer();
JavaScriptUtils.WriteEscapedJavaScriptString(_writer, value, _quoteChar, quote, _charEscapeFlags, StringEscapeHandling, _arrayPool, ref _writeBuffer);
}
///
/// Writes a value.
///
/// The value to write.
public override void WriteValue(int value)
{
InternalWriteValue(JsonToken.Integer);
WriteIntegerValue(value);
}
///
/// Writes a value.
///
/// The value to write.
[CLSCompliant(false)]
public override void WriteValue(uint value)
{
InternalWriteValue(JsonToken.Integer);
WriteIntegerValue(value);
}
///
/// Writes a value.
///
/// The value to write.
public override void WriteValue(long value)
{
InternalWriteValue(JsonToken.Integer);
WriteIntegerValue(value);
}
///
/// Writes a value.
///
/// The value to write.
[CLSCompliant(false)]
public override void WriteValue(ulong value)
{
InternalWriteValue(JsonToken.Integer);
WriteIntegerValue(value, false);
}
///
/// Writes a value.
///
/// The value to write.
public override void WriteValue(float value)
{
InternalWriteValue(JsonToken.Float);
WriteValueInternal(JsonConvert.ToString(value, FloatFormatHandling, QuoteChar, false), JsonToken.Float);
}
///
/// Writes a of value.
///
/// The of value to write.
public override void WriteValue(float? value)
{
if (value == null)
{
WriteNull();
}
else
{
InternalWriteValue(JsonToken.Float);
WriteValueInternal(JsonConvert.ToString(value.GetValueOrDefault(), FloatFormatHandling, QuoteChar, true), JsonToken.Float);
}
}
///
/// Writes a value.
///
/// The value to write.
public override void WriteValue(double value)
{
InternalWriteValue(JsonToken.Float);
WriteValueInternal(JsonConvert.ToString(value, FloatFormatHandling, QuoteChar, false), JsonToken.Float);
}
///
/// Writes a of value.
///
/// The of value to write.
public override void WriteValue(double? value)
{
if (value == null)
{
WriteNull();
}
else
{
InternalWriteValue(JsonToken.Float);
WriteValueInternal(JsonConvert.ToString(value.GetValueOrDefault(), FloatFormatHandling, QuoteChar, true), JsonToken.Float);
}
}
///
/// Writes a value.
///
/// The value to write.
public override void WriteValue(bool value)
{
InternalWriteValue(JsonToken.Boolean);
WriteValueInternal(JsonConvert.ToString(value), JsonToken.Boolean);
}
///
/// Writes a value.
///
/// The value to write.
public override void WriteValue(short value)
{
InternalWriteValue(JsonToken.Integer);
WriteIntegerValue(value);
}
///
/// Writes a value.
///
/// The value to write.
[CLSCompliant(false)]
public override void WriteValue(ushort value)
{
InternalWriteValue(JsonToken.Integer);
WriteIntegerValue(value);
}
///
/// Writes a value.
///
/// The value to write.
public override void WriteValue(char value)
{
InternalWriteValue(JsonToken.String);
WriteValueInternal(JsonConvert.ToString(value), JsonToken.String);
}
///
/// Writes a value.
///
/// The value to write.
public override void WriteValue(byte value)
{
InternalWriteValue(JsonToken.Integer);
WriteIntegerValue(value);
}
///
/// Writes a value.
///
/// The value to write.
[CLSCompliant(false)]
public override void WriteValue(sbyte value)
{
InternalWriteValue(JsonToken.Integer);
WriteIntegerValue(value);
}
///
/// Writes a value.
///
/// The value to write.
public override void WriteValue(decimal value)
{
InternalWriteValue(JsonToken.Float);
WriteValueInternal(JsonConvert.ToString(value), JsonToken.Float);
}
///
/// Writes a value.
///
/// The value to write.
public override void WriteValue(DateTime value)
{
InternalWriteValue(JsonToken.Date);
value = DateTimeUtils.EnsureDateTime(value, DateTimeZoneHandling);
if (string.IsNullOrEmpty(DateFormatString))
{
int length = WriteValueToBuffer(value);
_writer.Write(_writeBuffer, 0, length);
}
else
{
_writer.Write(_quoteChar);
_writer.Write(value.ToString(DateFormatString, Culture));
_writer.Write(_quoteChar);
}
}
private int WriteValueToBuffer(DateTime value)
{
EnsureWriteBuffer();
int pos = 0;
_writeBuffer[pos++] = _quoteChar;
pos = DateTimeUtils.WriteDateTimeString(_writeBuffer, pos, value, null, value.Kind, DateFormatHandling);
_writeBuffer[pos++] = _quoteChar;
return pos;
}
///
/// Writes a [] value.
///
/// The [] value to write.
public override void WriteValue(byte[] value)
{
if (value == null)
{
WriteNull();
}
else
{
InternalWriteValue(JsonToken.Bytes);
_writer.Write(_quoteChar);
Base64Encoder.Encode(value, 0, value.Length);
Base64Encoder.Flush();
_writer.Write(_quoteChar);
}
}
#if HAVE_DATE_TIME_OFFSET
///
/// Writes a value.
///
/// The value to write.
public override void WriteValue(DateTimeOffset value)
{
InternalWriteValue(JsonToken.Date);
if (string.IsNullOrEmpty(DateFormatString))
{
int length = WriteValueToBuffer(value);
_writer.Write(_writeBuffer, 0, length);
}
else
{
_writer.Write(_quoteChar);
_writer.Write(value.ToString(DateFormatString, Culture));
_writer.Write(_quoteChar);
}
}
private int WriteValueToBuffer(DateTimeOffset value)
{
EnsureWriteBuffer();
int pos = 0;
_writeBuffer[pos++] = _quoteChar;
pos = DateTimeUtils.WriteDateTimeString(_writeBuffer, pos, (DateFormatHandling == DateFormatHandling.IsoDateFormat) ? value.DateTime : value.UtcDateTime, value.Offset, DateTimeKind.Local, DateFormatHandling);
_writeBuffer[pos++] = _quoteChar;
return pos;
}
#endif
///
/// Writes a value.
///
/// The value to write.
public override void WriteValue(Guid value)
{
InternalWriteValue(JsonToken.String);
string text = null;
#if HAVE_CHAR_TO_STRING_WITH_CULTURE
text = value.ToString("D", CultureInfo.InvariantCulture);
#else
text = value.ToString("D");
#endif
_writer.Write(_quoteChar);
_writer.Write(text);
_writer.Write(_quoteChar);
}
///
/// Writes a value.
///
/// The value to write.
public override void WriteValue(TimeSpan value)
{
InternalWriteValue(JsonToken.String);
string text;
#if !HAVE_TIME_SPAN_TO_STRING_WITH_CULTURE
text = value.ToString();
#else
text = value.ToString(null, CultureInfo.InvariantCulture);
#endif
_writer.Write(_quoteChar);
_writer.Write(text);
_writer.Write(_quoteChar);
}
///
/// Writes a value.
///
/// The value to write.
public override void WriteValue(Uri value)
{
if (value == null)
{
WriteNull();
}
else
{
InternalWriteValue(JsonToken.String);
WriteEscapedString(value.OriginalString, true);
}
}
#endregion
///
/// Writes a comment /*...*/ containing the specified text.
///
/// Text to place inside the comment.
public override void WriteComment(string text)
{
InternalWriteComment();
_writer.Write("/*");
_writer.Write(text);
_writer.Write("*/");
}
///
/// Writes the given white space.
///
/// The string of white space characters.
public override void WriteWhitespace(string ws)
{
InternalWriteWhitespace(ws);
_writer.Write(ws);
}
private void EnsureWriteBuffer()
{
if (_writeBuffer == null)
{
// maximum buffer sized used when writing iso date
_writeBuffer = BufferUtils.RentBuffer(_arrayPool, 35);
}
}
private void WriteIntegerValue(long value)
{
if (value >= 0 && value <= 9)
{
_writer.Write((char)('0' + value));
}
else
{
bool negative = value < 0;
WriteIntegerValue(negative ? (ulong)-value : (ulong)value, negative);
}
}
private void WriteIntegerValue(ulong value, bool negative)
{
if (!negative & value <= 9)
{
_writer.Write((char)('0' + value));
}
else
{
int length = WriteNumberToBuffer(value, negative);
_writer.Write(_writeBuffer, 0, length);
}
}
private int WriteNumberToBuffer(ulong value, bool negative)
{
if (value <= uint.MaxValue)
{
// avoid the 64 bit division if possible
return WriteNumberToBuffer((uint)value, negative);
}
EnsureWriteBuffer();
int totalLength = MathUtils.IntLength(value);
if (negative)
{
totalLength++;
_writeBuffer[0] = '-';
}
int index = totalLength;
do
{
ulong quotient = value / 10;
ulong digit = value - (quotient * 10);
_writeBuffer[--index] = (char)('0' + digit);
value = quotient;
} while (value != 0);
return totalLength;
}
private void WriteIntegerValue(int value)
{
if (value >= 0 && value <= 9)
{
_writer.Write((char)('0' + value));
}
else
{
bool negative = value < 0;
WriteIntegerValue(negative ? (uint)-value : (uint)value, negative);
}
}
private void WriteIntegerValue(uint value, bool negative)
{
if (!negative & value <= 9)
{
_writer.Write((char)('0' + value));
}
else
{
int length = WriteNumberToBuffer(value, negative);
_writer.Write(_writeBuffer, 0, length);
}
}
private int WriteNumberToBuffer(uint value, bool negative)
{
EnsureWriteBuffer();
int totalLength = MathUtils.IntLength(value);
if (negative)
{
totalLength++;
_writeBuffer[0] = '-';
}
int index = totalLength;
do
{
uint quotient = value / 10;
uint digit = value - (quotient * 10);
_writeBuffer[--index] = (char)('0' + digit);
value = quotient;
} while (value != 0);
return totalLength;
}
}
}