|
|
using Implab.Formats.Json;
|
|
|
using System;
|
|
|
using System.Collections.Generic;
|
|
|
using System.Globalization;
|
|
|
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;
|
|
|
|
|
|
|
|
|
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);
|
|
|
|
|
|
// 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, AttributeCount - 1, 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(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;
|
|
|
}
|
|
|
|
|
|
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}";
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
|