##// END OF EJS Templates
Closing branch: `v3`
Closing branch: `v3`

File last commit:

r289:95896f882995 v3.0.14 v3
r293:bb6f69f90c6c v3
Show More
JsonXmlReader.cs
664 lines | 24.4 KiB | text/x-csharp | CSharpLexer
using Implab.Formats.Json;
using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Xml;
namespace Implab.Xml {
public class JsonXmlReader : XmlReader {
struct JsonContext {
public string localName;
public bool skip;
}
JsonReader m_parser;
JsonXmlReaderOptions m_options;
JsonXmlReaderPosition m_position = JsonXmlReaderPosition.Initial;
XmlNameTable m_nameTable;
readonly string m_jsonRootName;
readonly string m_jsonNamespace;
readonly string m_jsonPrefix;
readonly bool m_jsonFlattenArrays;
readonly string m_jsonArrayItemName;
string m_jsonLocalName;
string m_jsonValueName;
bool m_jsonSkip; // indicates wheather to generate closing tag for objects or arrays
readonly Stack<JsonContext> m_jsonNameStack = new Stack<JsonContext>();
XmlQualifiedName m_elementQName;
string m_elementPrefix;
int m_elementDepth;
bool m_elementIsEmpty;
XmlQualifiedName m_qName;
string m_prefix;
int m_xmlDepth;
XmlSimpleAttribute[] m_attributes;
string m_value;
bool m_isEmpty;
XmlNodeType m_nodeType = XmlNodeType.None;
bool m_isAttribute; // indicates that we are reading attribute nodes
int m_currentAttribute;
bool m_currentAttributeRead;
XmlNameContext m_context;
readonly string m_xmlnsPrefix;
readonly string m_xmlnsNamespace;
readonly string m_xsiPrefix;
readonly string m_xsiNamespace;
readonly JsonXmlCaseTransform m_caseTransform;
public JsonXmlReader(JsonReader parser, JsonXmlReaderOptions options) {
Safe.ArgumentNotNull(parser, nameof(parser));
m_parser = parser;
m_options = options ?? new JsonXmlReaderOptions();
m_jsonFlattenArrays = m_options.FlattenArrays;
m_nameTable = m_options.NameTable ?? new NameTable();
m_jsonRootName = m_nameTable.Add(string.IsNullOrEmpty(m_options.RootName) ? "data" : m_options.RootName);
m_jsonArrayItemName = m_nameTable.Add(string.IsNullOrEmpty(m_options.ArrayItemName) ? "item" : m_options.ArrayItemName);
m_jsonNamespace = m_nameTable.Add(m_options.NamespaceUri ?? string.Empty);
m_jsonPrefix = m_nameTable.Add(m_options.NodesPrefix ?? string.Empty);
m_xmlnsPrefix = m_nameTable.Add(XmlNameContext.XmlnsPrefix);
m_xmlnsNamespace = m_nameTable.Add(XmlNameContext.XmlnsNamespace);
m_xsiPrefix = m_nameTable.Add(XmlNameContext.XsiPrefix);
m_xsiNamespace = m_nameTable.Add(XmlNameContext.XsiNamespace);
m_caseTransform = m_options.CaseTransform;
// TODO validate m_jsonRootName, m_jsonArrayItemName
m_context = new XmlNameContext(null, 0);
}
public override int AttributeCount {
get {
return m_attributes == null ? 0 : m_attributes.Length;
}
}
public override string BaseURI {
get {
return string.Empty;
}
}
public override int Depth {
get {
return m_xmlDepth;
}
}
public override bool EOF {
get {
return m_position == JsonXmlReaderPosition.Eof;
}
}
public override bool IsEmptyElement {
get { return m_isEmpty; }
}
public override string LocalName {
get {
return m_qName.Name;
}
}
public override string NamespaceURI {
get {
return m_qName.Namespace;
}
}
public override XmlNameTable NameTable {
get {
return m_nameTable;
}
}
public override XmlNodeType NodeType {
get {
return m_nodeType;
}
}
public override string Prefix {
get {
return m_prefix;
}
}
public override ReadState ReadState {
get {
switch (m_position) {
case JsonXmlReaderPosition.Initial:
return ReadState.Initial;
case JsonXmlReaderPosition.Eof:
return ReadState.EndOfFile;
case JsonXmlReaderPosition.Closed:
return ReadState.Closed;
case JsonXmlReaderPosition.Error:
return ReadState.Error;
default:
return ReadState.Interactive;
};
}
}
public override string Value {
get {
return m_value;
}
}
public override string GetAttribute(int i) {
Safe.ArgumentInRange(i >= 0 && i < AttributeCount, nameof(i));
return m_attributes[i].Value;
}
public override string GetAttribute(string name) {
if (m_attributes == null)
return null;
var qName = m_context.Resolve(name);
var attr = Array.Find(m_attributes, x => x.QName == qName);
var value = attr?.Value;
return value == string.Empty ? null : value;
}
public override string GetAttribute(string name, string namespaceURI) {
if (m_attributes == null)
return null;
var qName = new XmlQualifiedName(name, namespaceURI);
var attr = Array.Find(m_attributes, x => x.QName == qName);
var value = attr?.Value;
return value == string.Empty ? null : value;
}
public override string LookupNamespace(string prefix) {
return m_context.ResolvePrefix(prefix);
}
public override bool MoveToAttribute(string name) {
if (m_attributes == null || m_attributes.Length == 0)
return false;
var qName = m_context.Resolve(name);
var index = Array.FindIndex(m_attributes, x => x.QName == qName);
if (index >= 0) {
MoveToAttributeImpl(index);
return true;
}
return false;
}
public override bool MoveToAttribute(string name, string ns) {
if (m_attributes == null || m_attributes.Length == 0)
return false;
var qName = m_context.Resolve(name);
var index = Array.FindIndex(m_attributes, x => x.QName == qName);
if (index >= 0) {
MoveToAttributeImpl(index);
return true;
}
return false;
}
void MoveToAttributeImpl(int i) {
if (!m_isAttribute) {
m_elementQName = m_qName;
m_elementDepth = m_xmlDepth;
m_elementPrefix = m_prefix;
m_elementIsEmpty = m_isEmpty;
m_isAttribute = true;
}
var attr = m_attributes[i];
m_currentAttribute = i;
m_currentAttributeRead = false;
m_nodeType = XmlNodeType.Attribute;
m_xmlDepth = m_elementDepth + 1;
m_qName = attr.QName;
m_value = attr.Value;
m_prefix = attr.Prefix;
}
public override bool MoveToElement() {
if (m_isAttribute) {
m_value = null;
m_nodeType = XmlNodeType.Element;
m_xmlDepth = m_elementDepth;
m_prefix = m_elementPrefix;
m_qName = m_elementQName;
m_isEmpty = m_elementIsEmpty;
m_isAttribute = false;
return true;
}
return false;
}
public override bool MoveToFirstAttribute() {
if (m_attributes != null && m_attributes.Length > 0) {
MoveToAttributeImpl(0);
return true;
}
return false;
}
public override bool MoveToNextAttribute() {
if (m_isAttribute) {
var next = m_currentAttribute + 1;
if (next < AttributeCount) {
MoveToAttributeImpl(next);
return true;
}
return false;
} else {
return MoveToFirstAttribute();
}
}
public override bool ReadAttributeValue() {
if (!m_isAttribute || m_currentAttributeRead)
return false;
ValueNode(m_attributes[m_currentAttribute].Value);
m_currentAttributeRead = true;
return true;
}
public override void ResolveEntity() {
/* do nothing */
}
/// <summary>
/// Determines do we need to increase depth after the current node
/// </summary>
/// <returns></returns>
public bool IsSibling() {
switch (m_nodeType) {
case XmlNodeType.None: // start document
case XmlNodeType.Attribute: // after attribute only it's content can be iterated with ReadAttributeValue method
return false;
case XmlNodeType.Element:
// if the elemnt is empty the next element will be it's sibling
return m_isEmpty;
default:
return true;
}
}
void ValueNode(string value) {
if (!IsSibling()) // the node is nested
m_xmlDepth++;
m_qName = XmlQualifiedName.Empty;
m_nodeType = XmlNodeType.Text;
m_prefix = string.Empty;
m_value = value;
m_isEmpty = false;
m_attributes = null;
}
void ElementNode(string name, string ns, XmlSimpleAttribute[] attrs, bool empty) {
if (!IsSibling()) // the node is nested
m_xmlDepth++;
var context = m_context;
List<XmlSimpleAttribute> definedAttrs = null;
// define new namespaces
if (attrs != null) {
foreach (var attr in attrs) {
if (attr.QName.Name == "xmlns") {
if (context == m_context)
context = new XmlNameContext(m_context, m_xmlDepth);
context.DefinePrefix(attr.Value, string.Empty);
} else if (attr.Prefix == m_xmlnsPrefix) {
if (context == m_context)
context = new XmlNameContext(m_context, m_xmlDepth);
context.DefinePrefix(attr.Value, attr.QName.Name);
} else {
string attrPrefix;
if (string.IsNullOrEmpty(attr.QName.Namespace))
continue;
// auto-define prefixes
if (!context.LookupNamespacePrefix(attr.QName.Namespace, out attrPrefix) || string.IsNullOrEmpty(attrPrefix)) {
// new namespace prefix added
attrPrefix = context.CreateNamespacePrefix(attr.QName.Namespace);
attr.Prefix = attrPrefix;
if (definedAttrs == null)
definedAttrs = new List<XmlSimpleAttribute>();
definedAttrs.Add(new XmlSimpleAttribute(attrPrefix, m_xmlnsNamespace, m_xmlnsPrefix, attr.QName.Namespace));
}
}
}
}
string p;
// auto-define prefixes
if (!context.LookupNamespacePrefix(ns, out p)) {
if (context == m_context)
context = new XmlNameContext(m_context, m_xmlDepth);
p = context.CreateNamespacePrefix(ns);
if (definedAttrs == null)
definedAttrs = new List<XmlSimpleAttribute>();
definedAttrs.Add(new XmlSimpleAttribute(p, m_xmlnsNamespace, m_xmlnsPrefix, ns));
}
if (definedAttrs != null) {
if (attrs != null)
definedAttrs.AddRange(attrs);
attrs = definedAttrs.ToArray();
}
if (!empty)
m_context = context;
m_nodeType = XmlNodeType.Element;
m_qName = new XmlQualifiedName(name, ns);
m_prefix = p;
m_value = null;
m_isEmpty = empty;
m_attributes = attrs;
}
void EndElementNode(string name, string ns) {
if (IsSibling()) {
// closing the element which has children
m_xmlDepth--;
}
string p;
if (!m_context.LookupNamespacePrefix(ns, out p))
throw new Exception($"Failed to lookup namespace '{ns}'");
if (m_context.Depth == m_xmlDepth)
m_context = m_context.ParentContext;
m_nodeType = XmlNodeType.EndElement;
m_prefix = p;
m_qName = new XmlQualifiedName(name, ns);
m_value = null;
m_attributes = null;
m_isEmpty = false;
}
void XmlDeclaration() {
if (!IsSibling()) // the node is nested
m_xmlDepth++;
m_nodeType = XmlNodeType.XmlDeclaration;
m_qName = new XmlQualifiedName("xml");
m_value = "version='1.0'";
m_prefix = string.Empty;
m_attributes = null;
m_isEmpty = false;
}
public override bool Read() {
try {
string elementName;
XmlSimpleAttribute[] elementAttrs = null;
MoveToElement();
switch (m_position) {
case JsonXmlReaderPosition.Initial:
m_jsonLocalName = m_jsonRootName;
m_jsonSkip = false;
XmlDeclaration();
m_position = JsonXmlReaderPosition.Declaration;
return true;
case JsonXmlReaderPosition.Declaration:
elementAttrs = new[] {
new XmlSimpleAttribute(m_xsiPrefix, m_xmlnsNamespace, m_xmlnsPrefix, m_xsiNamespace),
string.IsNullOrEmpty(m_jsonPrefix) ?
new XmlSimpleAttribute(m_xmlnsPrefix, string.Empty, string.Empty, m_jsonNamespace) :
new XmlSimpleAttribute(m_jsonPrefix, m_xmlnsNamespace, m_xmlnsPrefix, m_jsonNamespace)
};
break;
case JsonXmlReaderPosition.ValueElement:
if (!m_isEmpty) {
if (m_parser.ElementValue != null && !m_parser.ElementValue.Equals(string.Empty))
ValueNode(m_parser.ElementValue);
else
goto case JsonXmlReaderPosition.ValueContent;
m_position = JsonXmlReaderPosition.ValueContent;
return true;
} else {
m_position = JsonXmlReaderPosition.ValueEndElement;
break;
}
case JsonXmlReaderPosition.ValueContent:
EndElementNode(m_jsonValueName, m_jsonNamespace);
m_position = JsonXmlReaderPosition.ValueEndElement;
return true;
case JsonXmlReaderPosition.Eof:
case JsonXmlReaderPosition.Closed:
case JsonXmlReaderPosition.Error:
return false;
}
while (m_parser.Read()) {
var jsonName = m_nameTable.Add(TransformJsonName(m_parser.ElementName));
switch (m_parser.ElementType) {
case JsonElementType.BeginObject:
if (!EnterJsonObject(jsonName, out elementName))
continue;
m_position = JsonXmlReaderPosition.BeginObject;
ElementNode(elementName, m_jsonNamespace, elementAttrs, false);
break;
case JsonElementType.EndObject:
if (!LeaveJsonScope(out elementName))
continue;
m_position = JsonXmlReaderPosition.EndObject;
EndElementNode(elementName, m_jsonNamespace);
break;
case JsonElementType.BeginArray:
if (!EnterJsonArray(jsonName, out elementName))
continue;
m_position = JsonXmlReaderPosition.BeginArray;
ElementNode(elementName, m_jsonNamespace, elementAttrs, false);
break;
case JsonElementType.EndArray:
if (!LeaveJsonScope(out elementName))
continue;
m_position = JsonXmlReaderPosition.EndArray;
EndElementNode(elementName, m_jsonNamespace);
break;
case JsonElementType.Value:
if (!VisitJsonValue(jsonName, out m_jsonValueName))
continue;
m_position = JsonXmlReaderPosition.ValueElement;
if (m_parser.ElementValue == null)
// generate empty element with xsi:nil="true" attribute
ElementNode(
m_jsonValueName,
m_jsonNamespace,
new[] {
new XmlSimpleAttribute("nil", m_xsiNamespace, m_xsiPrefix, "true")
},
true
);
else
ElementNode(m_jsonValueName, m_jsonNamespace, elementAttrs, m_parser.ElementValue.Equals(string.Empty));
break;
default:
throw new Exception($"Unexpected JSON element {m_parser.ElementType}: {m_parser.ElementName}");
}
return true;
}
m_position = JsonXmlReaderPosition.Eof;
return false;
} catch {
m_position = JsonXmlReaderPosition.Error;
throw;
}
}
void SaveJsonName() {
m_jsonNameStack.Push(new JsonContext {
skip = m_jsonSkip,
localName = m_jsonLocalName
});
}
bool EnterJsonObject(string name, out string elementName) {
SaveJsonName();
m_jsonSkip = false;
if (string.IsNullOrEmpty(name)) {
if (m_jsonNameStack.Count != 1 && !m_jsonFlattenArrays)
m_jsonLocalName = m_jsonArrayItemName;
} else {
m_jsonLocalName = name;
}
elementName = m_jsonLocalName;
return true;
}
/// <summary>
/// Called when JSON parser visits BeginArray ('[') element.
/// </summary>
/// <param name="name">Optional property name if the array is the member of an object</param>
/// <returns>true if element should be emited, false otherwise</returns>
bool EnterJsonArray(string name, out string elementName) {
SaveJsonName();
if (string.IsNullOrEmpty(name)) {
// m_jsonNameStack.Count == 1 means the root node
if (m_jsonNameStack.Count != 1 && !m_jsonFlattenArrays)
m_jsonLocalName = m_jsonArrayItemName;
m_jsonSkip = false; // we should not flatten arrays inside arrays or in the document root
} else {
m_jsonLocalName = name;
m_jsonSkip = m_jsonFlattenArrays;
}
elementName = m_jsonLocalName;
return !m_jsonSkip;
}
bool VisitJsonValue(string name, out string elementName) {
if (string.IsNullOrEmpty(name)) {
// m_jsonNameStack.Count == 0 means that JSON document consists from simple value
elementName = (m_jsonNameStack.Count == 0 || m_jsonFlattenArrays) ? m_jsonLocalName : m_jsonArrayItemName;
} else {
elementName = name;
}
return true;
}
bool LeaveJsonScope(out string elementName) {
elementName = m_jsonLocalName;
var skip = m_jsonSkip;
var prev = m_jsonNameStack.Pop();
m_jsonLocalName = prev.localName;
m_jsonSkip = prev.skip;
return !skip;
}
private string TransformJsonName(string name) {
if (m_caseTransform == JsonXmlCaseTransform.None || string.IsNullOrEmpty(name)) {
return name;
} else if (m_caseTransform == JsonXmlCaseTransform.UcFirst) {
return JsonXmlReader.UppercaseFirst(name);
} else {
return JsonXmlReader.LowercaseFirst(name);
}
}
protected override void Dispose(bool disposing) {
if (disposing)
Safe.Dispose(m_parser);
base.Dispose(true);
}
public override string ToString() {
switch (NodeType) {
case XmlNodeType.Element:
return $"<{Name} {string.Join(" ", (m_attributes ?? new XmlSimpleAttribute[0]).Select(x => $"{x.Prefix}{(string.IsNullOrEmpty(x.Prefix) ? "" : ":")}{x.QName.Name}='{x.Value}'"))} {(IsEmptyElement ? "/" : "")}>";
case XmlNodeType.Attribute:
return $"@{Name}";
case XmlNodeType.Text:
return $"{Value}";
case XmlNodeType.CDATA:
return $"<![CDATA[{Value}]]>";
case XmlNodeType.EntityReference:
return $"&{Name};";
case XmlNodeType.EndElement:
return $"</{Name}>";
default:
return $".{NodeType} {Name} {Value}";
}
}
#region static methods
//
// Static Methods
//
private static string LowercaseFirst(string s) {
char[] array = s.ToCharArray();
array[0] = char.ToLower(array[0]);
return new string(array);
}
private static string UppercaseFirst(string s) {
char[] array = s.ToCharArray();
array[0] = char.ToUpper(array[0]);
return new string(array);
}
public static JsonXmlReader CreateJsonXmlReader(TextReader textReader, JsonXmlReaderOptions options = null) {
var jsonReader = JsonReader.Create(textReader);
return new JsonXmlReader(jsonReader, options);
}
public static JsonXmlReader CreateJsonXmlReader(Stream stream, JsonXmlReaderOptions options = null) {
var jsonReader = JsonReader.Create(stream);
return new JsonXmlReader(jsonReader, options);
}
public static JsonXmlReader CreateJsonXmlReader(string file, JsonXmlReaderOptions options = null) {
var jsonReader = JsonReader.Create(file);
return new JsonXmlReader(jsonReader, options);
}
#endregion
}
}