From b69ba0540a47c5d554612a5776835e78e1380514 Mon Sep 17 00:00:00 2001 From: "Valeriano A.R" Date: Sat, 23 May 2015 20:58:21 +0200 Subject: [PATCH] JSON: Parser and Writer --- Scrummer/Code/JSON/JSONParser.cs | 413 ++++++++++++++++++++++++++++ Scrummer/Code/JSON/JSONWriter.cs | 381 +++++++++++++++++++++++++ Scrummer/Code/JSON/ParserContext.cs | 87 ++++++ Scrummer/Scrummer.csproj | 3 + 4 files changed, 884 insertions(+) create mode 100644 Scrummer/Code/JSON/JSONParser.cs create mode 100644 Scrummer/Code/JSON/JSONWriter.cs create mode 100644 Scrummer/Code/JSON/ParserContext.cs diff --git a/Scrummer/Code/JSON/JSONParser.cs b/Scrummer/Code/JSON/JSONParser.cs new file mode 100644 index 0000000..d38703c --- /dev/null +++ b/Scrummer/Code/JSON/JSONParser.cs @@ -0,0 +1,413 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Web; + +namespace Scrummer.Code.JSON +{ + public class JSONParser + { + #region Declarations + + private ParserContext ctx; + private bool tainted = false; + + #endregion + + #region Private methods + + private int ParseHexShort() + { + int value = 0; + for (int i = 0; i < 4; i++) + { + char c = ctx.Next(); + if (Char.IsDigit(c)) + { + value = (value << 4) | (c - '0'); + } + else + { + c = Char.ToLower(c); + if (c >= 'a' && c <= 'f') + { + value = (value << 4) | ((c - 'a') + 10); + } + } + } + return value; + } + + private String ParseQuotedString() + { + StringBuilder scratch = new StringBuilder(); + char c = ctx.SkipWhite(); + if (c == '"') + { + c = ctx.Next(); + } + do + { + if (c == '\\') + { + c = ctx.Next(); + if (c == '"') + { + scratch.Append('"'); + } + else if (c == '\\') + { + scratch.Append('\\'); + } + else if (c == '/') + { + scratch.Append('/'); + } + else if (c == 'b') + { + scratch.Append('\b'); + } + else if (c == 'f') + { + scratch.Append('\f'); + } + else if (c == 'n') + { + scratch.Append('\n'); + } + else if (c == 'r') + { + scratch.Append('\r'); + } + else if (c == 't') + { + scratch.Append('\t'); + } + else if (c == 'u') + { + scratch.Append((char)ParseHexShort()); + } + c = ctx.Next(); + } + else if (c == '"') + { + break; + } + else + { + scratch.Append(c); + c = ctx.Next(); + } + } while (!ctx.AtEnd()); + if (c == '"') + { + ctx.Next(); + } + return scratch.ToString(); + } + + private String ParseString() + { + char c = ctx.SkipWhite(); + if (c == '"') + { + return ParseQuotedString(); + } + StringBuilder scratch = new StringBuilder(); + + while (!ctx.AtEnd() + && (Char.IsLetter(c) || Char.IsDigit(c) || c == '_')) + { + scratch.Append(c); + c = ctx.Next(); + } + + return scratch.ToString(); + } + + private Object ParseNumber() + { + StringBuilder scratch = new StringBuilder(); + bool isFloat = false; + int numberLenght = 0; + char c; + c = ctx.SkipWhite(); + + // Sign + if (c == '-') + { + scratch.Append('-'); + c = ctx.Next(); + } + + // Integer part + while (Char.IsDigit(c)) + { + scratch.Append(c); + c = ctx.Next(); + numberLenght++; + } + + // Decimal part + if (c == '.') + { + isFloat = true; + scratch.Append('.'); + c = ctx.Next(); + while (Char.IsDigit(c)) + { + scratch.Append(c); + c = ctx.Next(); + numberLenght++; + } + } + + if (numberLenght == 0) + { + tainted = true; + return null; + } + + // Exponential part + if (c == 'e' || c == 'E') + { + isFloat = true; + scratch.Append('E'); + c = ctx.Next(); + if (c == '+' || c == '-') + { + scratch.Append(c); + } + while (Char.IsDigit(c)) + { + scratch.Append(c); + c = ctx.Next(); + numberLenght++; + } + } + + // Build number object from the parsed string + String s = scratch.ToString(); + return isFloat ? (numberLenght < 17) ? (Object)Double.Parse(s) + : Decimal.Parse(s) : (numberLenght < 19) ? (Object)System.Int32.Parse(s) + : (Object)System.Int32.Parse(s); + } + + private List ParseArray() + { + char c = ctx.SkipWhite(); + List array = new List(); + if (c == '[') + { + ctx.Next(); + } + do + { + c = ctx.SkipWhite(); + if (c == ']') + { + ctx.Next(); + break; + } + else if (c == ',') + { + ctx.Next(); + } + else + { + array.Add(ParseValue()); + } + } while (!ctx.AtEnd()); + return array; + } + + private Dictionary ParseObject() + { + char c = ctx.SkipWhite(); + Dictionary obj = new Dictionary(); + if (c == '{') + { + ctx.Next(); + c = ctx.SkipWhite(); + } + String attributeName; + Object attributeValue; + do + { + attributeName = ParseString(); + c = ctx.SkipWhite(); + if (c == ':') + { + ctx.Next(); + attributeValue = ParseValue(); + if (attributeName.Length > 0) + { + obj.Add(attributeName, attributeValue); + } + } + else if (c == ',') + { + ctx.Next(); + c = ctx.SkipWhite(); + } + else if (c == '}') + { + ctx.Next(); + break; + } + else + { + // Unexpected character + tainted = true; + break; + } + } while (!ctx.AtEnd()); + if (obj.Count == 0) + { + return null; + } + return obj; + } + + private Object ParseValue() + { + Object token = null; + char c = ctx.SkipWhite(); + switch (c) + { + case '"': + token = ParseQuotedString(); + break; + case '{': + token = ParseObject(); + break; + case '[': + token = ParseArray(); + break; + default: + if (Char.IsDigit(c) || c == '-') + { + token = ParseNumber(); + } + else + { + String aux = ParseString(); + if (aux.CompareTo("true") == 0) + { + token = true; + } + else if (aux.CompareTo("false") == 0) + { + token = false; + } + else if (aux.CompareTo("null") == 0) + { + token = null; + } + else + { + // Unexpected string + if (aux.Length == 0) + { + ctx.Next(); + } + tainted = true; + token = null; + } + } + break; + } + return token; + } + + private String CleanIdentifier(String input) + { + int i; + char c; + i = input.Length - 1; + if (i < 0) + { + return input; + } + c = input[i]; + while (Char.IsLetter(c) || Char.IsDigit(c) || c == '_') + { + i--; + if (i < 0) + { + break; + } + c = input[i]; + } + return input.Substring(i + 1); + } + + #endregion + + #region Public methods + + public Object Parse(String text) + { + // Get the first object + ctx = new ParserContext(text); + tainted = false; + ctx.Mark(); + Object obj = ParseValue(); + if (ctx.AtEnd()) + { + return obj; + } + + // "But wait, there is more!" + int idx = 0; + String name = ""; + String strInvalidPrev = ""; + Dictionary superObject = new Dictionary(); + do + { + // Add the object to the superObject + if (!tainted && name.Length > 0 && obj != null) + { + if (name.Length == 0) + { + name = String.Format("{0:D2}", idx); + } + superObject.Add(name, obj); + idx++; + name = ""; + } + else + { + String strInvalid = ctx.GetMarked(); + strInvalid = strInvalid.Trim(); + if (strInvalidPrev.Length > 0 + && "=".CompareTo(strInvalid) == 0) + { + name = CleanIdentifier(strInvalidPrev); + } + else + { + name = ""; + } + strInvalidPrev = strInvalid; + } + + // Check end + if (ctx.AtEnd()) + { + break; + } + + // Get next object + tainted = false; + ctx.Mark(); + obj = ParseValue(); + + } while (true); + return superObject; + } + + #endregion + } +} \ No newline at end of file diff --git a/Scrummer/Code/JSON/JSONWriter.cs b/Scrummer/Code/JSON/JSONWriter.cs new file mode 100644 index 0000000..acfa824 --- /dev/null +++ b/Scrummer/Code/JSON/JSONWriter.cs @@ -0,0 +1,381 @@ +using System; +using System.Collections.Generic; +using System.Reflection; +using System.Text; + +namespace Scrummer.Code.JSON +{ + public class JSONWriter + { + #region Declarations + + private bool indent = false; + private bool useTabForIndent = false; + private int indentChars = 4; + private int indentThresold = 3; + + #endregion + + #region Creator + + public JSONWriter() { } + + public JSONWriter(int indentChars) + { + this.indent = true; + this.indentChars = indentChars; + this.useTabForIndent = false; + } + + public JSONWriter(bool useTabForIndent) + { + this.indent = true; + this.useTabForIndent = useTabForIndent; + } + + #endregion + + #region Private methods + + private bool IsValue(Object obj) + { + if (obj == null) + { + return true; + } + if ((obj is float) || (obj is double) || + (obj is System.Int16) || (obj is System.Int32) || (obj is System.Int64) + || (obj is String) || (obj is Boolean)) + { + return true; + } + return false; + } + + private void WriteIndent(StringBuilder sbOutput, int level) + { + if (!indent) + { + return; + } + sbOutput.Append('\n'); + if (useTabForIndent) + { + for (int i = 0; i < level; i++) { sbOutput.Append('\t'); } + } + else + { + int n = level * indentChars; + for (int i = 0; i < n; i++) { sbOutput.Append(' '); } + } + } + + private void WriteString(StringBuilder sbOutput, String str) + { + sbOutput.Append('"'); + char c; + int n = str.Length; + for (int i = 0; i < n; i++) + { + c = str[i]; + if (c == '"') { sbOutput.Append("\\\""); } + else if (c == '\\') { sbOutput.Append("\\\\"); } + else if (c == '/') { sbOutput.Append("\\/"); } + else if (c == '\b') { sbOutput.Append("\\b"); } + else if (c == '\f') { sbOutput.Append("\\f"); } + else if (c == '\n') { sbOutput.Append("\\n"); } + else if (c == '\r') { sbOutput.Append("\\r"); } + else if (c == '\t') { sbOutput.Append("\\t"); } + else { sbOutput.Append(c); } + // FIXME: Unicode characters + } + sbOutput.Append('"'); + } + + private void WriteValue(StringBuilder sbOutput, Object obj, int level, bool useReflection) + { + if (obj == null) + { + // NULL + sbOutput.Append("null"); + } + else if (obj is List) + { + // Array (List) + WriteList(sbOutput, obj, level); + } + else if (obj is Array) + { + // Array (Array) + WriteArray(sbOutput, obj, level); + } + else if ((obj is float) || (obj is double) || + (obj is System.Int16) || (obj is System.Int32) || (obj is System.Int64)) + { + // Numbers + sbOutput.Append(obj.ToString()); + } + else if (obj is String) + { + // Strings + WriteString(sbOutput, (String)obj); + } + else if (obj is Boolean) + { + // Booleans + sbOutput.Append(((Boolean)obj) ? "true" : "false"); + } + else if (obj is Dictionary) + { + // Objects + WriteObject(sbOutput, obj, level); + } + else + { + if (useReflection) + { + // Reflected object + WriteReflectedObject(sbOutput, obj, level); + } + else + { + WriteString(sbOutput, Convert.ToString(obj)); + } + } + } + + private void WriteList(StringBuilder sbOutput, Object obj, int level) + { + List list = (List)obj; + int n = list.Count; + + // Empty + if (n == 0) + { + sbOutput.Append("[ ]"); + return; + } + + // Check if it is a leaf object + bool isLeaf = true; + foreach (object childObj in list) + { + if (!IsValue(childObj)) + { + isLeaf = false; + break; + } + } + + // Write array + bool first = true; + sbOutput.Append("[ "); + if (!isLeaf || n > indentThresold) + { + WriteIndent(sbOutput, level + 1); + } + foreach (object childObj in list) + { + if (!first) + { + sbOutput.Append(", "); + if (!isLeaf || n > indentThresold) + { + WriteIndent(sbOutput, level + 1); + } + } + first = false; + WriteValue(sbOutput, childObj, level + 1, false); + } + if (!isLeaf || n > indentThresold) + { + WriteIndent(sbOutput, level); + } + sbOutput.Append(" ]"); + } + + private void WriteArray(StringBuilder sbOutput, Object obj, int level) + { + object[] list = (object[])obj; + int n = list.Length; + + // Empty + if (n == 0) + { + sbOutput.Append("[ ]"); + return; + } + + // Check if it is a leaf object + bool isLeaf = true; + foreach (object childObj in list) + { + if (!IsValue(childObj)) + { + isLeaf = false; + break; + } + } + + // Write array + bool first = true; + sbOutput.Append("[ "); + if (!isLeaf || n > indentThresold) + { + WriteIndent(sbOutput, level + 1); + } + foreach (object childObj in list) + { + if (!first) + { + sbOutput.Append(", "); + if (!isLeaf || n > indentThresold) + { + WriteIndent(sbOutput, level + 1); + } + } + first = false; + WriteValue(sbOutput, childObj, level + 1, false); + } + if (!isLeaf || n > indentThresold) + { + WriteIndent(sbOutput, level); + } + sbOutput.Append(" ]"); + } + + private void WriteObject(StringBuilder sbOutput, Object obj, int level) + { + Dictionary map = (Dictionary)obj; + int n = map.Count; + + // Empty + if (map.Count == 0) + { + sbOutput.Append("{ }"); + return; + } + + // Check if it is a leaf object + bool isLeaf = true; + foreach (KeyValuePair entry in map) + { + if (!IsValue(entry.Value)) + { + isLeaf = false; + break; + } + } + + // Write object + bool first = true; + sbOutput.Append("{ "); + if (!isLeaf || n > indentThresold) + { + WriteIndent(sbOutput, level + 1); + } + foreach (KeyValuePair entry in map) + { + if (!first) + { + sbOutput.Append(", "); + if (!isLeaf || n > indentThresold) + { + WriteIndent(sbOutput, level + 1); + } + } + first = false; + WriteString(sbOutput, (String)entry.Key); + sbOutput.Append(": "); + WriteValue(sbOutput, entry.Value, level + 1, false); + } + if (!isLeaf || n > indentThresold) + { + WriteIndent(sbOutput, level); + } + sbOutput.Append(" }"); + } + + private void WriteReflectedObject(StringBuilder sbOutput, Object obj, int level) + { + Type type = obj.GetType(); + PropertyInfo[] rawProperties = type.GetProperties(BindingFlags.Public | BindingFlags.Instance); + List properties = new List(); + foreach (PropertyInfo property in rawProperties) + { + if (property.CanRead) + { + properties.Add(property); + } + } + int n = properties.Count; + + // Empty + if (n == 0) + { + sbOutput.Append("{ }"); + return; + } + + // Check if it is a leaf object + bool isLeaf = true; + foreach (PropertyInfo property in properties) + { + object value = property.GetValue(obj, null); + if (!IsValue(value)) + { + isLeaf = false; + break; + } + } + + // Write object + bool first = true; + sbOutput.Append("{ "); + if (!isLeaf || n > indentThresold) + { + WriteIndent(sbOutput, level + 1); + } + foreach (PropertyInfo property in properties) + { + object value=null; + MethodInfo getMethod = property.GetMethod; + ParameterInfo[] parameters =getMethod.GetParameters(); + if (parameters.Length == 0) + { + value = property.GetValue(obj, null); + } + if (!first) + { + sbOutput.Append(", "); + if (!isLeaf || n > indentThresold) + { + WriteIndent(sbOutput, level + 1); + } + } + first = false; + WriteString(sbOutput, property.Name); + sbOutput.Append(": "); + WriteValue(sbOutput, value, level + 1, false); + } + if (!isLeaf || n > indentThresold) + { + WriteIndent(sbOutput, level); + } + sbOutput.Append(" }"); + } + + #endregion + + #region Public methods + + public String Write(Object obj) + { + StringBuilder sbOutput = new StringBuilder(); + WriteValue(sbOutput, obj, 0, true); + return sbOutput.ToString(); + } + + #endregion + } +} \ No newline at end of file diff --git a/Scrummer/Code/JSON/ParserContext.cs b/Scrummer/Code/JSON/ParserContext.cs new file mode 100644 index 0000000..334d8bb --- /dev/null +++ b/Scrummer/Code/JSON/ParserContext.cs @@ -0,0 +1,87 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Web; + +namespace Scrummer.Code.JSON +{ + public class ParserContext + { + #region Declarations + + private String text; + private int length; + private int i; + private int markStart; + + #endregion + + #region Creator + + public ParserContext(String text) + { + this.text = text; + this.length = text.Length; + this.i = 0; + this.markStart = 0; + } + + #endregion + + #region Public methods + + public char SkipWhite() + { + while (i < length && Char.IsWhiteSpace(text[i])) + { + i++; + } + if (AtEnd()) + { + return (char)0; + } + return text[i]; + } + + public char Next() + { + i++; + if (AtEnd()) + { + return (char)0; + } + return text[i]; + } + + public bool AtEnd() + { + return i >= length; + } + + public void Mark() + { + markStart = this.i; + } + + public String GetMarked() + { + if (i < length && markStart < length) + { + return text.Substring(markStart, i); + } + else + { + if (markStart < length) + { + return text.Substring(markStart, length); + } + else + { + return string.Empty; + } + } + } + + #endregion + } +} \ No newline at end of file diff --git a/Scrummer/Scrummer.csproj b/Scrummer/Scrummer.csproj index 7c2e170..2bc3178 100644 --- a/Scrummer/Scrummer.csproj +++ b/Scrummer/Scrummer.csproj @@ -71,7 +71,10 @@ + + +