|
|
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<LocalNameContext> m_localNameStack = new Stack<LocalNameContext>();
|
|
|
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();
|
|
|
}
|
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
|
/// Always 0, JSON doesn't support attributes
|
|
|
/// </summary>
|
|
|
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; }
|
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
|
/// Always throws an exception
|
|
|
/// </summary>
|
|
|
/// <param name="i"></param>
|
|
|
/// <returns></returns>
|
|
|
public override string GetAttribute(int i) {
|
|
|
throw new ArgumentOutOfRangeException();
|
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
|
/// Always returns empty string
|
|
|
/// </summary>
|
|
|
/// <param name="name"></param>
|
|
|
/// <param name="namespaceURI"></param>
|
|
|
/// <returns></returns>
|
|
|
public override string GetAttribute(string name, string namespaceURI) {
|
|
|
return String.Empty;
|
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
|
/// Always returns empty string
|
|
|
/// </summary>
|
|
|
/// <param name="name"></param>
|
|
|
/// <returns></returns>
|
|
|
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 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() {
|
|
|
|
|
|
}
|
|
|
|
|
|
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);
|
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
|
/// Creates the XmlReader for the specified text stream with JSON data.
|
|
|
/// </summary>
|
|
|
/// <param name="reader">Text reader.</param>
|
|
|
/// <param name="options">Options.</param>
|
|
|
/// <remarks>
|
|
|
/// The reader will be disposed when the XmlReader is disposed.
|
|
|
/// </remarks>
|
|
|
public static JSONXmlReader Create(TextReader reader, JSONXmlReaderOptions options) {
|
|
|
return new JSONXmlReader(new JSONParser(reader, true), options);
|
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
|
/// Creates the XmlReader for the specified stream with JSON data.
|
|
|
/// </summary>
|
|
|
/// <param name="stream">Stream.</param>
|
|
|
/// <param name="options">Options.</param>
|
|
|
/// <remarks>
|
|
|
/// The stream will be disposed when the XmlReader is disposed.
|
|
|
/// </remarks>
|
|
|
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);
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
|