##// END OF EJS Templates
Fixed promise rejection when there is not specified error handler in the reaction....
Fixed promise rejection when there is not specified error handler in the reaction. FIXED SPELLING IN THE XML CONTAINER CONFIGURATION signleton->singleton Code cleanup Update tests make them working on dotnet core

File last commit:

r295:28af686e24f7 default
r295:28af686e24f7 default
Show More
JsonXmlReader.cs
663 lines | 24.4 KiB | text/x-csharp | CSharpLexer
using Implab.Formats.Json;
using System;
using System.Collections.Generic;
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
}
}