using Implab; using Implab.Parsing; using System; using System.Collections.Generic; using System.Globalization; using System.IO; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Xml; namespace Implab.JSON { 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; int m_depthCorrection = 0; readonly string m_rootName; readonly string m_prefix; readonly string m_namespaceUri; readonly bool m_flattenArrays; readonly string m_arrayItemName; readonly XmlNameTable m_nameTable; JSONXmlReader(JSONParser parser, JSONXmlReaderOptions options) { m_parser = parser; if (options != null) { m_prefix = options.NodesPrefix ?? String.Empty; m_namespaceUri = options.NamespaceURI ?? String.Empty; m_rootName = options.RootName ?? "json"; m_flattenArrays = options.FlattenArrays; m_arrayItemName = options.ArrayItemName ?? "item"; m_nameTable = options.NameTable ?? new NameTable(); } else { m_prefix = String.Empty; m_namespaceUri = String.Empty; m_rootName = "json"; m_flattenArrays = false; m_arrayItemName = "item"; m_nameTable = new NameTable(); } } /// /// 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 : m_arrayItemName; 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 { if (m_parser.ElementValue == null) return String.Empty; if (Convert.GetTypeCode(m_parser.ElementValue) == TypeCode.Double) return ((double)m_parser.ElementValue).ToString(CultureInfo.InvariantCulture); else return (string)m_parser.ElementValue; } } 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() { } protected override void Dispose(bool disposing) { if (disposing) { m_parser.Dispose(); } base.Dispose(disposing); } public static JSONXmlReader Create(string file, JSONXmlReaderOptions options) { return Create(File.OpenText(file), options); } public static JSONXmlReader Create(TextReader reader, JSONXmlReaderOptions options) { return new JSONXmlReader(new JSONParser(reader, true), options); } public static JSONXmlReader Create(Stream stream, JSONXmlReaderOptions options) { Safe.ArgumentNotNull(stream, "stream"); // HACK don't dispose StreaReader to keep stream opened return Create(new StreamReader(stream), options); } } }