You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
541 lines
16 KiB
541 lines
16 KiB
/* |
|
* This is a customized parser, based on "SimpleJson.cs", for JSONNode |
|
* |
|
* SimpleJson.cs is open source software and this entire file may be dealt with as described below: |
|
*/ |
|
//----------------------------------------------------------------------- |
|
// <copyright file="SimpleJson.cs" company="The Outercurve Foundation"> |
|
// Copyright (c) 2011, The Outercurve Foundation, 2015 Zen Fulcrum LLC |
|
// |
|
// Licensed under the MIT License (the "License"); |
|
// you may not use this file except in compliance with the License. |
|
// You may obtain a copy of the License at |
|
// http://www.opensource.org/licenses/mit-license.php |
|
// |
|
// Unless required by applicable law or agreed to in writing, software |
|
// distributed under the License is distributed on an "AS IS" BASIS, |
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|
// See the License for the specific language governing permissions and |
|
// limitations under the License. |
|
// </copyright> |
|
// <author>Nathan Totten (ntotten.com), Jim Zimmerman (jimzimmerman.com) and Prabir Shrestha (prabir.me)</author> |
|
// <website>https://github.com/facebook-csharp-sdk/simple-json</website> |
|
//----------------------------------------------------------------------- |
|
|
|
// ReSharper disable InconsistentNaming |
|
|
|
using System; |
|
using System.Collections; |
|
using System.Collections.Generic; |
|
using System.Globalization; |
|
using System.Runtime.Serialization; |
|
using System.Text; |
|
|
|
namespace ZenFulcrum.EmbeddedBrowser { |
|
/// <summary> |
|
/// This class encodes and decodes JSON strings. |
|
/// Spec. details, see http://www.json.org/ |
|
/// |
|
/// JSON uses Arrays and Objects. These correspond here to the datatypes JsonArray(IList<object>) and JsonObject(IDictionary<string,object>). |
|
/// All numbers are parsed to doubles. |
|
/// </summary> |
|
internal static class JSONParser { |
|
private const int TOKEN_NONE = 0; |
|
private const int TOKEN_CURLY_OPEN = 1; |
|
private const int TOKEN_CURLY_CLOSE = 2; |
|
private const int TOKEN_SQUARED_OPEN = 3; |
|
private const int TOKEN_SQUARED_CLOSE = 4; |
|
private const int TOKEN_COLON = 5; |
|
private const int TOKEN_COMMA = 6; |
|
private const int TOKEN_STRING = 7; |
|
private const int TOKEN_NUMBER = 8; |
|
private const int TOKEN_TRUE = 9; |
|
private const int TOKEN_FALSE = 10; |
|
private const int TOKEN_NULL = 11; |
|
private const int BUILDER_CAPACITY = 2000; |
|
private static readonly char[] EscapeTable; |
|
private static readonly char[] EscapeCharacters = new char[] { '"', '\\', '\b', '\f', '\n', '\r', '\t' }; |
|
// private static readonly string EscapeCharactersString = new string(EscapeCharacters); |
|
static JSONParser() { |
|
EscapeTable = new char[93]; |
|
EscapeTable['"'] = '"'; |
|
EscapeTable['\\'] = '\\'; |
|
EscapeTable['\b'] = 'b'; |
|
EscapeTable['\f'] = 'f'; |
|
EscapeTable['\n'] = 'n'; |
|
EscapeTable['\r'] = 'r'; |
|
EscapeTable['\t'] = 't'; |
|
} |
|
|
|
/// <summary> |
|
/// Parses the string json into a value |
|
/// </summary> |
|
/// <param name="json">A JSON string.</param> |
|
/// <returns>An IList<object>, a IDictionary<string,object>, a double, a string, null, true, or false</returns> |
|
public static JSONNode Parse(string json) { |
|
JSONNode obj; |
|
if (TryDeserializeObject(json, out obj)) |
|
return obj; |
|
throw new SerializationException("Invalid JSON string"); |
|
} |
|
/// <summary> |
|
/// Try parsing the json string into a value. |
|
/// </summary> |
|
/// <param name="json"> |
|
/// A JSON string. |
|
/// </param> |
|
/// <param name="obj"> |
|
/// The object. |
|
/// </param> |
|
/// <returns> |
|
/// Returns true if successfull otherwise false. |
|
/// </returns> |
|
public static bool TryDeserializeObject(string json, out JSONNode obj) { |
|
bool success = true; |
|
if (json != null) { |
|
char[] charArray = json.ToCharArray(); |
|
int index = 0; |
|
obj = ParseValue(charArray, ref index, ref success); |
|
} else |
|
obj = null; |
|
return success; |
|
} |
|
|
|
public static string EscapeToJavascriptString(string jsonString) { |
|
if (string.IsNullOrEmpty(jsonString)) |
|
return jsonString; |
|
StringBuilder sb = new StringBuilder(); |
|
char c; |
|
for (int i = 0; i < jsonString.Length; ) { |
|
c = jsonString[i++]; |
|
if (c == '\\') { |
|
int remainingLength = jsonString.Length - i; |
|
if (remainingLength >= 2) { |
|
char lookahead = jsonString[i]; |
|
if (lookahead == '\\') { |
|
sb.Append('\\'); |
|
++i; |
|
} else if (lookahead == '"') { |
|
sb.Append("\""); |
|
++i; |
|
} else if (lookahead == 't') { |
|
sb.Append('\t'); |
|
++i; |
|
} else if (lookahead == 'b') { |
|
sb.Append('\b'); |
|
++i; |
|
} else if (lookahead == 'n') { |
|
sb.Append('\n'); |
|
++i; |
|
} else if (lookahead == 'r') { |
|
sb.Append('\r'); |
|
++i; |
|
} |
|
} |
|
} else { |
|
sb.Append(c); |
|
} |
|
} |
|
return sb.ToString(); |
|
} |
|
|
|
static JSONNode ParseObject(char[] json, ref int index, ref bool success) { |
|
JSONNode table = new JSONNode(JSONNode.NodeType.Object); |
|
int token; |
|
// { |
|
NextToken(json, ref index); |
|
bool done = false; |
|
while (!done) { |
|
token = LookAhead(json, index); |
|
if (token == TOKEN_NONE) { |
|
success = false; |
|
return null; |
|
} else if (token == TOKEN_COMMA) |
|
NextToken(json, ref index); |
|
else if (token == TOKEN_CURLY_CLOSE) { |
|
NextToken(json, ref index); |
|
return table; |
|
} else { |
|
// name |
|
string name = ParseString(json, ref index, ref success); |
|
if (!success) { |
|
success = false; |
|
return null; |
|
} |
|
// : |
|
token = NextToken(json, ref index); |
|
if (token != TOKEN_COLON) { |
|
success = false; |
|
return null; |
|
} |
|
// value |
|
JSONNode value = ParseValue(json, ref index, ref success); |
|
if (!success) { |
|
success = false; |
|
return null; |
|
} |
|
table[name] = value; |
|
} |
|
} |
|
return table; |
|
} |
|
|
|
static JSONNode ParseArray(char[] json, ref int index, ref bool success) { |
|
JSONNode array = new JSONNode(JSONNode.NodeType.Array); |
|
// [ |
|
NextToken(json, ref index); |
|
bool done = false; |
|
while (!done) { |
|
int token = LookAhead(json, index); |
|
if (token == TOKEN_NONE) { |
|
success = false; |
|
return null; |
|
} else if (token == TOKEN_COMMA) |
|
NextToken(json, ref index); |
|
else if (token == TOKEN_SQUARED_CLOSE) { |
|
NextToken(json, ref index); |
|
break; |
|
} else { |
|
JSONNode value = ParseValue(json, ref index, ref success); |
|
if (!success) |
|
return null; |
|
array.Add(value); |
|
} |
|
} |
|
return array; |
|
} |
|
|
|
static JSONNode ParseValue(char[] json, ref int index, ref bool success) { |
|
switch (LookAhead(json, index)) { |
|
case TOKEN_STRING: |
|
return ParseString(json, ref index, ref success); |
|
case TOKEN_NUMBER: |
|
return ParseNumber(json, ref index, ref success); |
|
case TOKEN_CURLY_OPEN: |
|
return ParseObject(json, ref index, ref success); |
|
case TOKEN_SQUARED_OPEN: |
|
return ParseArray(json, ref index, ref success); |
|
case TOKEN_TRUE: |
|
NextToken(json, ref index); |
|
return true; |
|
case TOKEN_FALSE: |
|
NextToken(json, ref index); |
|
return false; |
|
case TOKEN_NULL: |
|
NextToken(json, ref index); |
|
return JSONNode.NullNode; |
|
case TOKEN_NONE: |
|
break; |
|
} |
|
success = false; |
|
return JSONNode.InvalidNode; |
|
} |
|
|
|
static JSONNode ParseString(char[] json, ref int index, ref bool success) { |
|
StringBuilder s = new StringBuilder(BUILDER_CAPACITY); |
|
char c; |
|
EatWhitespace(json, ref index); |
|
// " |
|
c = json[index++]; |
|
bool complete = false; |
|
while (!complete) { |
|
if (index == json.Length) |
|
break; |
|
c = json[index++]; |
|
if (c == '"') { |
|
complete = true; |
|
break; |
|
} else if (c == '\\') { |
|
if (index == json.Length) |
|
break; |
|
c = json[index++]; |
|
if (c == '"') |
|
s.Append('"'); |
|
else if (c == '\\') |
|
s.Append('\\'); |
|
else if (c == '/') |
|
s.Append('/'); |
|
else if (c == 'b') |
|
s.Append('\b'); |
|
else if (c == 'f') |
|
s.Append('\f'); |
|
else if (c == 'n') |
|
s.Append('\n'); |
|
else if (c == 'r') |
|
s.Append('\r'); |
|
else if (c == 't') |
|
s.Append('\t'); |
|
else if (c == 'u') { |
|
int remainingLength = json.Length - index; |
|
if (remainingLength >= 4) { |
|
// parse the 32 bit hex into an integer codepoint |
|
uint codePoint; |
|
if (!(success = UInt32.TryParse(new string(json, index, 4), NumberStyles.HexNumber, CultureInfo.InvariantCulture, out codePoint))) |
|
return ""; |
|
// convert the integer codepoint to a unicode char and add to string |
|
if (0xD800 <= codePoint && codePoint <= 0xDBFF) // if high surrogate |
|
{ |
|
index += 4; // skip 4 chars |
|
remainingLength = json.Length - index; |
|
if (remainingLength >= 6) { |
|
uint lowCodePoint; |
|
if (new string(json, index, 2) == "\\u" && UInt32.TryParse(new string(json, index + 2, 4), NumberStyles.HexNumber, CultureInfo.InvariantCulture, out lowCodePoint)) { |
|
if (0xDC00 <= lowCodePoint && lowCodePoint <= 0xDFFF) // if low surrogate |
|
{ |
|
s.Append((char)codePoint); |
|
s.Append((char)lowCodePoint); |
|
index += 6; // skip 6 chars |
|
continue; |
|
} |
|
} |
|
} |
|
success = false; // invalid surrogate pair |
|
return ""; |
|
} |
|
s.Append(ConvertFromUtf32((int)codePoint)); |
|
// skip 4 chars |
|
index += 4; |
|
} else |
|
break; |
|
} |
|
} else |
|
s.Append(c); |
|
} |
|
if (!complete) { |
|
success = false; |
|
return null; |
|
} |
|
return s.ToString(); |
|
} |
|
|
|
private static string ConvertFromUtf32(int utf32) { |
|
// http://www.java2s.com/Open-Source/CSharp/2.6.4-mono-.net-core/System/System/Char.cs.htm |
|
if (utf32 < 0 || utf32 > 0x10FFFF) |
|
throw new ArgumentOutOfRangeException("utf32", "The argument must be from 0 to 0x10FFFF."); |
|
if (0xD800 <= utf32 && utf32 <= 0xDFFF) |
|
throw new ArgumentOutOfRangeException("utf32", "The argument must not be in surrogate pair range."); |
|
if (utf32 < 0x10000) |
|
return new string((char)utf32, 1); |
|
utf32 -= 0x10000; |
|
return new string(new char[] { (char)((utf32 >> 10) + 0xD800), (char)(utf32 % 0x0400 + 0xDC00) }); |
|
} |
|
|
|
static JSONNode ParseNumber(char[] json, ref int index, ref bool success) { |
|
EatWhitespace(json, ref index); |
|
int lastIndex = GetLastIndexOfNumber(json, index); |
|
int charLength = (lastIndex - index) + 1; |
|
JSONNode returnNumber; |
|
string str = new string(json, index, charLength); |
|
if (str.IndexOf(".", StringComparison.OrdinalIgnoreCase) != -1 || str.IndexOf("e", StringComparison.OrdinalIgnoreCase) != -1) { |
|
double number; |
|
success = double.TryParse(new string(json, index, charLength), NumberStyles.Any, CultureInfo.InvariantCulture, out number); |
|
returnNumber = number; |
|
} else { |
|
long number; |
|
success = long.TryParse(new string(json, index, charLength), NumberStyles.Any, CultureInfo.InvariantCulture, out number); |
|
returnNumber = number; |
|
} |
|
index = lastIndex + 1; |
|
return returnNumber; |
|
} |
|
|
|
static int GetLastIndexOfNumber(char[] json, int index) { |
|
int lastIndex; |
|
for (lastIndex = index; lastIndex < json.Length; lastIndex++) |
|
if ("0123456789+-.eE".IndexOf(json[lastIndex]) == -1) break; |
|
return lastIndex - 1; |
|
} |
|
|
|
static void EatWhitespace(char[] json, ref int index) { |
|
for (; index < json.Length; index++) |
|
if (" \t\n\r\b\f".IndexOf(json[index]) == -1) break; |
|
} |
|
static int LookAhead(char[] json, int index) { |
|
int saveIndex = index; |
|
return NextToken(json, ref saveIndex); |
|
} |
|
|
|
static int NextToken(char[] json, ref int index) { |
|
EatWhitespace(json, ref index); |
|
if (index == json.Length) |
|
return TOKEN_NONE; |
|
char c = json[index]; |
|
index++; |
|
switch (c) { |
|
case '{': |
|
return TOKEN_CURLY_OPEN; |
|
case '}': |
|
return TOKEN_CURLY_CLOSE; |
|
case '[': |
|
return TOKEN_SQUARED_OPEN; |
|
case ']': |
|
return TOKEN_SQUARED_CLOSE; |
|
case ',': |
|
return TOKEN_COMMA; |
|
case '"': |
|
return TOKEN_STRING; |
|
case '0': |
|
case '1': |
|
case '2': |
|
case '3': |
|
case '4': |
|
case '5': |
|
case '6': |
|
case '7': |
|
case '8': |
|
case '9': |
|
case '-': |
|
return TOKEN_NUMBER; |
|
case ':': |
|
return TOKEN_COLON; |
|
} |
|
index--; |
|
int remainingLength = json.Length - index; |
|
// false |
|
if (remainingLength >= 5) { |
|
if (json[index] == 'f' && json[index + 1] == 'a' && json[index + 2] == 'l' && json[index + 3] == 's' && json[index + 4] == 'e') { |
|
index += 5; |
|
return TOKEN_FALSE; |
|
} |
|
} |
|
// true |
|
if (remainingLength >= 4) { |
|
if (json[index] == 't' && json[index + 1] == 'r' && json[index + 2] == 'u' && json[index + 3] == 'e') { |
|
index += 4; |
|
return TOKEN_TRUE; |
|
} |
|
} |
|
// null |
|
if (remainingLength >= 4) { |
|
if (json[index] == 'n' && json[index + 1] == 'u' && json[index + 2] == 'l' && json[index + 3] == 'l') { |
|
index += 4; |
|
return TOKEN_NULL; |
|
} |
|
} |
|
return TOKEN_NONE; |
|
} |
|
|
|
public static string Serialize(JSONNode node) { |
|
StringBuilder sb = new StringBuilder(); |
|
var success = SerializeValue(node, sb); |
|
if (!success) throw new SerializationException("Failed to serialize JSON"); |
|
return sb.ToString(); |
|
} |
|
|
|
static bool SerializeValue(JSONNode value, StringBuilder builder) { |
|
bool success = true; |
|
|
|
// if (value == null) { |
|
// builder.Append("null"); |
|
// return success; |
|
// } |
|
|
|
switch (value.Type) { |
|
case JSONNode.NodeType.String: |
|
success = SerializeString(value, builder); |
|
break; |
|
case JSONNode.NodeType.Object: { |
|
Dictionary<String, JSONNode> dict = value; |
|
success = SerializeObject(dict.Keys, dict.Values, builder); |
|
break; |
|
} |
|
case JSONNode.NodeType.Array: |
|
success = SerializeArray((List<JSONNode>)value, builder); |
|
break; |
|
case JSONNode.NodeType.Number: |
|
success = SerializeNumber(value, builder); |
|
break; |
|
case JSONNode.NodeType.Bool: |
|
builder.Append(value ? "true" : "false"); |
|
break; |
|
case JSONNode.NodeType.Null: |
|
builder.Append("null"); |
|
break; |
|
case JSONNode.NodeType.Invalid: |
|
throw new SerializationException("Cannot serialize invalid JSONNode"); |
|
default: |
|
throw new SerializationException("Unknown JSONNode type"); |
|
} |
|
return success; |
|
} |
|
|
|
static bool SerializeObject(IEnumerable<string> keys, IEnumerable<JSONNode> values, StringBuilder builder) { |
|
builder.Append("{"); |
|
var ke = keys.GetEnumerator(); |
|
var ve = values.GetEnumerator(); |
|
bool first = true; |
|
while (ke.MoveNext() && ve.MoveNext()) { |
|
var key = ke.Current; |
|
var value = ve.Current; |
|
if (!first) |
|
builder.Append(","); |
|
string stringKey = key; |
|
if (stringKey != null) |
|
SerializeString(stringKey, builder); |
|
else |
|
if (!SerializeValue(value, builder)) return false; |
|
builder.Append(":"); |
|
if (!SerializeValue(value, builder)) |
|
return false; |
|
first = false; |
|
} |
|
builder.Append("}"); |
|
return true; |
|
} |
|
|
|
static bool SerializeArray(IEnumerable<JSONNode> anArray, StringBuilder builder) { |
|
builder.Append("["); |
|
bool first = true; |
|
foreach (var value in anArray) { |
|
if (!first) |
|
builder.Append(","); |
|
if (!SerializeValue(value, builder)) |
|
return false; |
|
first = false; |
|
} |
|
builder.Append("]"); |
|
return true; |
|
} |
|
|
|
static bool SerializeString(string aString, StringBuilder builder) { |
|
// Happy path if there's nothing to be escaped. IndexOfAny is highly optimized (and unmanaged) |
|
if (aString.IndexOfAny(EscapeCharacters) == -1) { |
|
builder.Append('"'); |
|
builder.Append(aString); |
|
builder.Append('"'); |
|
return true; |
|
} |
|
builder.Append('"'); |
|
int safeCharacterCount = 0; |
|
char[] charArray = aString.ToCharArray(); |
|
for (int i = 0; i < charArray.Length; i++) { |
|
char c = charArray[i]; |
|
// Non ascii characters are fine, buffer them up and send them to the builder |
|
// in larger chunks if possible. The escape table is a 1:1 translation table |
|
// with \0 [default(char)] denoting a safe character. |
|
if (c >= EscapeTable.Length || EscapeTable[c] == default(char)) { |
|
safeCharacterCount++; |
|
} else { |
|
if (safeCharacterCount > 0) { |
|
builder.Append(charArray, i - safeCharacterCount, safeCharacterCount); |
|
safeCharacterCount = 0; |
|
} |
|
builder.Append('\\'); |
|
builder.Append(EscapeTable[c]); |
|
} |
|
} |
|
if (safeCharacterCount > 0) { |
|
builder.Append(charArray, charArray.Length - safeCharacterCount, safeCharacterCount); |
|
} |
|
builder.Append('"'); |
|
return true; |
|
} |
|
|
|
static bool SerializeNumber(double number, StringBuilder builder) { |
|
builder.Append(Convert.ToDouble(number, CultureInfo.InvariantCulture).ToString("r", CultureInfo.InvariantCulture)); |
|
return true; |
|
} |
|
|
|
} |
|
|
|
}
|
|
|