diff --git a/Implab/Implab.csproj b/Implab/Implab.csproj
--- a/Implab/Implab.csproj
+++ b/Implab/Implab.csproj
@@ -30,6 +30,7 @@
+
@@ -60,6 +61,7 @@
+
diff --git a/Implab/JSON/JSONXmlReader.cs b/Implab/JSON/JSONXmlReader.cs
new file mode 100644
--- /dev/null
+++ b/Implab/JSON/JSONXmlReader.cs
@@ -0,0 +1,278 @@
+using Implab;
+using Implab.JSON;
+using Implab.Parsing;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using System.Xml;
+
+namespace ConsPlay {
+ public class JSONXmlReader : XmlReader {
+
+ enum ValueContext {
+ Undefined,
+ ElementStart,
+ ElementValue,
+ ElementEnd,
+ ElementEmpty
+ }
+
+ struct LocalNameContext {
+ public string localName;
+ public bool isArray;
+ }
+
+ JSONParser m_parser;
+ ValueContext m_valueContext;
+ ReadState m_state = ReadState.Initial;
+ Stack m_localNameStack = new Stack();
+ LocalNameContext m_localName;
+ string m_rootName = "json";
+ string m_prefix = String.Empty;
+ string m_namespaceUri = String.Empty;
+ bool m_flattenArrays = false;
+ NameTable m_nameTable = new NameTable();
+ int m_depthCorrection = 0;
+
+ public JSONXmlReader(JSONParser parser) {
+ Safe.ArgumentNotNull(parser, "parser");
+ m_parser = parser;
+ }
+
+ ///
+ /// Always 0, JSON doesn't support attributes
+ ///
+ public override int AttributeCount {
+ get { return 0; }
+ }
+
+ public override string BaseURI {
+ get { return String.Empty; }
+ }
+
+ public override int Depth {
+ get {
+ return m_localNameStack.Count+m_depthCorrection;
+ }
+ }
+
+ public override bool EOF {
+ get { return m_parser.EOF; }
+ }
+
+ ///
+ /// Always throws an exception
+ ///
+ ///
+ ///
+ public override string GetAttribute(int i) {
+ throw new ArgumentOutOfRangeException();
+ }
+
+ ///
+ /// Always returns empty string
+ ///
+ ///
+ ///
+ ///
+ public override string GetAttribute(string name, string namespaceURI) {
+ return String.Empty;
+ }
+
+ ///
+ /// Always returns empty string
+ ///
+ ///
+ ///
+ public override string GetAttribute(string name) {
+ return String.Empty;
+ }
+
+ public override bool IsEmptyElement {
+ get { return m_parser.ElementType == JSONElementType.Value && m_valueContext == ValueContext.ElementEmpty; }
+ }
+
+ public override string LocalName {
+ get { return m_localName.localName; }
+ }
+
+ public override string LookupNamespace(string prefix) {
+ if (String.IsNullOrEmpty(prefix) || prefix == m_prefix)
+ return m_namespaceUri;
+ else
+ return String.Empty;
+ }
+
+ public override bool MoveToAttribute(string name, string ns) {
+ return false;
+ }
+
+ public override bool MoveToAttribute(string name) {
+ return false;
+ }
+
+ public override bool MoveToElement() {
+ return false;
+ }
+
+ public override bool MoveToFirstAttribute() {
+ return false;
+ }
+
+ public override bool MoveToNextAttribute() {
+ return false;
+ }
+
+ public override XmlNameTable NameTable {
+ get { return m_nameTable; }
+ }
+
+ public override string NamespaceURI {
+ get { return m_namespaceUri; }
+ }
+
+ public override XmlNodeType NodeType {
+ get {
+ switch (m_parser.ElementType) {
+ case JSONElementType.BeginObject:
+ case JSONElementType.BeginArray:
+ return XmlNodeType.Element;
+ case JSONElementType.EndObject:
+ case JSONElementType.EndArray:
+ return XmlNodeType.EndElement;
+ case JSONElementType.Value:
+ switch (m_valueContext) {
+ case ValueContext.ElementStart:
+ case ValueContext.ElementEmpty:
+ return XmlNodeType.Element;
+ case ValueContext.ElementValue:
+ return XmlNodeType.Text;
+ case ValueContext.ElementEnd:
+ return XmlNodeType.EndElement;
+ default:
+ throw new InvalidOperationException();
+ }
+ default:
+ throw new InvalidOperationException();
+ }
+ }
+ }
+
+ public override string Prefix {
+ get { return m_prefix; }
+ }
+
+ public override bool Read() {
+ if (m_state != System.Xml.ReadState.Interactive && m_state != System.Xml.ReadState.Initial)
+ return false;
+
+ if (m_state == ReadState.Initial)
+ m_state = System.Xml.ReadState.Interactive;
+
+ try {
+ switch (m_parser.ElementType) {
+ case JSONElementType.Value:
+ switch (m_valueContext) {
+ case ValueContext.ElementStart:
+ SetLocalName(String.Empty);
+ m_valueContext = ValueContext.ElementValue;
+ return true;
+ case ValueContext.ElementValue:
+ RestoreLocalName();
+ m_valueContext = ValueContext.ElementEnd;
+ return true;
+ case ValueContext.ElementEmpty:
+ case ValueContext.ElementEnd:
+ RestoreLocalName();
+ break;
+ }
+ break;
+ case JSONElementType.EndArray:
+ case JSONElementType.EndObject:
+ RestoreLocalName();
+ break;
+ }
+ string itemName = m_parser.ElementType == JSONElementType.None ? m_rootName : m_flattenArrays ? m_localName.localName : "item";
+ while (m_parser.Read()) {
+ if (!String.IsNullOrEmpty(m_parser.ElementName))
+ itemName = m_parser.ElementName;
+
+ switch (m_parser.ElementType) {
+ case JSONElementType.BeginArray:
+ if (m_flattenArrays && !m_localName.isArray) {
+ m_depthCorrection--;
+ SetLocalName(itemName, true);
+ continue;
+ } else {
+ SetLocalName(itemName, true);
+ }
+ break;
+ case JSONElementType.BeginObject:
+ SetLocalName(itemName);
+ break;
+ case JSONElementType.EndArray:
+ if (m_flattenArrays && !m_localNameStack.Peek().isArray) {
+ RestoreLocalName();
+ m_depthCorrection++;
+ continue;
+ }
+ break;
+ case JSONElementType.EndObject:
+ break;
+ case JSONElementType.Value:
+ SetLocalName(itemName);
+ m_valueContext = m_parser.ElementValue == null ? ValueContext.ElementEmpty : ValueContext.ElementStart;
+ break;
+ default:
+ break;
+ }
+ return true;
+ }
+
+ m_state = System.Xml.ReadState.EndOfFile;
+ return false;
+ } catch {
+ m_state = System.Xml.ReadState.Error;
+ throw;
+ }
+ }
+
+ public override bool ReadAttributeValue() {
+ return false;
+ }
+
+ public override ReadState ReadState {
+ get { return m_state; }
+ }
+
+ public override void ResolveEntity() {
+ // do nothing
+ }
+
+ public override string Value {
+ get { return m_parser.ElementValue == null ? String.Empty : m_parser.ElementValue.ToString(); }
+ }
+
+ void SetLocalName(string name) {
+ m_localNameStack.Push(m_localName);
+ m_localName.localName = name;
+ m_localName.isArray = false;
+ }
+
+ void SetLocalName(string name, bool isArray) {
+ m_localNameStack.Push(m_localName);
+ m_localName.localName = name;
+ m_localName.isArray = isArray;
+ }
+
+ void RestoreLocalName() {
+ m_localName = m_localNameStack.Pop();
+ }
+
+ public override void Close() {
+ m_state = System.Xml.ReadState.EndOfFile ;
+ }
+ }
+}