XmlNodeConverter
Converts XML to and from JSON.
using Newtonsoft.Json.Utilities;
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Xml;
using System.Xml.Linq;
namespace Newtonsoft.Json.Converters
{
public class XmlNodeConverter : JsonConverter
{
private const string TextName = "#text";
private const string CommentName = "#comment";
private const string CDataName = "#cdata-section";
private const string WhitespaceName = "#whitespace";
private const string SignificantWhitespaceName = "#significant-whitespace";
private const string DeclarationName = "?xml";
private const string JsonNamespaceUri = "http://james.newtonking.com/projects/json";
public string DeserializeRootElementName { get; set; }
public bool WriteArrayAttribute { get; set; }
public bool OmitRootObject { get; set; }
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
IXmlNode node = WrapXml(value);
XmlNamespaceManager manager = new XmlNamespaceManager(new NameTable());
PushParentNamespaces(node, manager);
if (!OmitRootObject)
writer.WriteStartObject();
SerializeNode(writer, node, manager, !OmitRootObject);
if (!OmitRootObject)
writer.WriteEndObject();
}
private IXmlNode WrapXml(object value)
{
if (value is XObject)
return XContainerWrapper.WrapNode((XObject)value);
if (value is XmlNode)
return new XmlNodeWrapper((XmlNode)value);
throw new ArgumentException("Value must be an XML object.", "value");
}
private void PushParentNamespaces(IXmlNode node, XmlNamespaceManager manager)
{
List<IXmlNode> list = null;
IXmlNode xmlNode = node;
while ((xmlNode = xmlNode.ParentNode) != null) {
if (xmlNode.NodeType == XmlNodeType.Element) {
if (list == null)
list = new List<IXmlNode>();
list.Add(xmlNode);
}
}
if (list != null) {
list.Reverse();
foreach (IXmlNode item in list) {
manager.PushScope();
foreach (IXmlNode attribute in item.Attributes) {
if (attribute.NamespaceUri == "http://www.w3.org/2000/xmlns/" && attribute.LocalName != "xmlns")
manager.AddNamespace(attribute.LocalName, attribute.Value);
}
}
}
}
private string ResolveFullName(IXmlNode node, XmlNamespaceManager manager)
{
string text = (node.NamespaceUri == null || (node.LocalName == "xmlns" && node.NamespaceUri == "http://www.w3.org/2000/xmlns/")) ? null : manager.LookupPrefix(node.NamespaceUri);
if (!string.IsNullOrEmpty(text))
return text + ":" + node.LocalName;
return node.LocalName;
}
private string GetPropertyName(IXmlNode node, XmlNamespaceManager manager)
{
switch (node.NodeType) {
case XmlNodeType.Attribute:
if (node.NamespaceUri == "http://james.newtonking.com/projects/json")
return "$" + node.LocalName;
return "@" + ResolveFullName(node, manager);
case XmlNodeType.CDATA:
return "#cdata-section";
case XmlNodeType.Comment:
return "#comment";
case XmlNodeType.Element:
return ResolveFullName(node, manager);
case XmlNodeType.ProcessingInstruction:
return "?" + ResolveFullName(node, manager);
case XmlNodeType.XmlDeclaration:
return "?xml";
case XmlNodeType.SignificantWhitespace:
return "#significant-whitespace";
case XmlNodeType.Text:
return "#text";
case XmlNodeType.Whitespace:
return "#whitespace";
default:
throw new JsonSerializationException("Unexpected XmlNodeType when getting node name: " + node.NodeType);
}
}
private bool IsArray(IXmlNode node)
{
IXmlNode xmlNode = (node.Attributes != null) ? node.Attributes.SingleOrDefault(delegate(IXmlNode a) {
if (a.LocalName == "Array")
return a.NamespaceUri == "http://james.newtonking.com/projects/json";
return false;
}) : null;
if (xmlNode != null)
return XmlConvert.ToBoolean(xmlNode.Value);
return false;
}
private void SerializeGroupedNodes(JsonWriter writer, IXmlNode node, XmlNamespaceManager manager, bool writePropertyName)
{
Dictionary<string, List<IXmlNode>> dictionary = new Dictionary<string, List<IXmlNode>>();
for (int i = 0; i < node.ChildNodes.Count; i++) {
IXmlNode xmlNode = node.ChildNodes[i];
string propertyName = GetPropertyName(xmlNode, manager);
if (!dictionary.TryGetValue(propertyName, out List<IXmlNode> value)) {
value = new List<IXmlNode>();
dictionary.Add(propertyName, value);
}
value.Add(xmlNode);
}
foreach (KeyValuePair<string, List<IXmlNode>> item in dictionary) {
List<IXmlNode> value2 = item.Value;
if (value2.Count == 1 && !IsArray(value2[0]))
SerializeNode(writer, value2[0], manager, writePropertyName);
else {
string key = item.Key;
if (writePropertyName)
writer.WritePropertyName(key);
writer.WriteStartArray();
for (int j = 0; j < value2.Count; j++) {
SerializeNode(writer, value2[j], manager, false);
}
writer.WriteEndArray();
}
}
}
private void SerializeNode(JsonWriter writer, IXmlNode node, XmlNamespaceManager manager, bool writePropertyName)
{
switch (node.NodeType) {
case XmlNodeType.Document:
case XmlNodeType.DocumentFragment:
SerializeGroupedNodes(writer, node, manager, writePropertyName);
break;
case XmlNodeType.Element:
if (IsArray(node) && node.ChildNodes.All((IXmlNode n) => n.LocalName == node.LocalName) && node.ChildNodes.Count > 0)
SerializeGroupedNodes(writer, node, manager, false);
else {
manager.PushScope();
foreach (IXmlNode attribute in node.Attributes) {
if (attribute.NamespaceUri == "http://www.w3.org/2000/xmlns/") {
string prefix = (attribute.LocalName != "xmlns") ? attribute.LocalName : string.Empty;
string value = attribute.Value;
manager.AddNamespace(prefix, value);
}
}
if (writePropertyName)
writer.WritePropertyName(GetPropertyName(node, manager));
if (!ValueAttributes(node.Attributes).Any() && node.ChildNodes.Count == 1 && node.ChildNodes[0].NodeType == XmlNodeType.Text)
writer.WriteValue(node.ChildNodes[0].Value);
else if (node.ChildNodes.Count == 0 && CollectionUtils.IsNullOrEmpty(node.Attributes)) {
writer.WriteNull();
} else {
writer.WriteStartObject();
for (int i = 0; i < node.Attributes.Count; i++) {
SerializeNode(writer, node.Attributes[i], manager, true);
}
SerializeGroupedNodes(writer, node, manager, true);
writer.WriteEndObject();
}
manager.PopScope();
}
break;
case XmlNodeType.Comment:
if (writePropertyName)
writer.WriteComment(node.Value);
break;
case XmlNodeType.Attribute:
case XmlNodeType.Text:
case XmlNodeType.CDATA:
case XmlNodeType.ProcessingInstruction:
case XmlNodeType.Whitespace:
case XmlNodeType.SignificantWhitespace:
if ((!(node.NamespaceUri == "http://www.w3.org/2000/xmlns/") || !(node.Value == "http://james.newtonking.com/projects/json")) && (!(node.NamespaceUri == "http://james.newtonking.com/projects/json") || !(node.LocalName == "Array"))) {
if (writePropertyName)
writer.WritePropertyName(GetPropertyName(node, manager));
writer.WriteValue(node.Value);
}
break;
case XmlNodeType.XmlDeclaration: {
IXmlDeclaration xmlDeclaration = (IXmlDeclaration)node;
writer.WritePropertyName(GetPropertyName(node, manager));
writer.WriteStartObject();
if (!string.IsNullOrEmpty(xmlDeclaration.Version)) {
writer.WritePropertyName("@version");
writer.WriteValue(xmlDeclaration.Version);
}
if (!string.IsNullOrEmpty(xmlDeclaration.Encoding)) {
writer.WritePropertyName("@encoding");
writer.WriteValue(xmlDeclaration.Encoding);
}
if (!string.IsNullOrEmpty(xmlDeclaration.Standalone)) {
writer.WritePropertyName("@standalone");
writer.WriteValue(xmlDeclaration.Standalone);
}
writer.WriteEndObject();
break;
}
default:
throw new JsonSerializationException("Unexpected XmlNodeType when serializing nodes: " + node.NodeType);
}
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
if (reader.TokenType == JsonToken.Null)
return null;
XmlNamespaceManager manager = new XmlNamespaceManager(new NameTable());
IXmlDocument xmlDocument = null;
IXmlNode xmlNode = null;
if (typeof(XObject).IsAssignableFrom(objectType)) {
if (objectType != typeof(XDocument) && objectType != typeof(XElement))
throw new JsonSerializationException("XmlNodeConverter only supports deserializing XDocument or XElement.");
XDocument document = new XDocument();
xmlDocument = new XDocumentWrapper(document);
xmlNode = xmlDocument;
}
if (typeof(XmlNode).IsAssignableFrom(objectType)) {
if (objectType != typeof(XmlDocument))
throw new JsonSerializationException("XmlNodeConverter only supports deserializing XmlDocuments");
XmlDocument document2 = new XmlDocument();
xmlDocument = new XmlDocumentWrapper(document2);
xmlNode = xmlDocument;
}
if (xmlDocument == null || xmlNode == null)
throw new JsonSerializationException("Unexpected type when converting XML: " + objectType);
if (reader.TokenType != JsonToken.StartObject)
throw new JsonSerializationException("XmlNodeConverter can only convert JSON that begins with an object.");
if (!string.IsNullOrEmpty(DeserializeRootElementName))
ReadElement(reader, xmlDocument, xmlNode, DeserializeRootElementName, manager);
else {
reader.Read();
DeserializeNode(reader, xmlDocument, manager, xmlNode);
}
if (objectType == typeof(XElement)) {
XElement xElement = (XElement)xmlDocument.DocumentElement.WrappedNode;
xElement.Remove();
return xElement;
}
return xmlDocument.WrappedNode;
}
private void DeserializeValue(JsonReader reader, IXmlDocument document, XmlNamespaceManager manager, string propertyName, IXmlNode currentNode)
{
switch (propertyName) {
case "#text":
currentNode.AppendChild(document.CreateTextNode(reader.Value.ToString()));
break;
case "#cdata-section":
currentNode.AppendChild(document.CreateCDataSection(reader.Value.ToString()));
break;
case "#whitespace":
currentNode.AppendChild(document.CreateWhitespace(reader.Value.ToString()));
break;
case "#significant-whitespace":
currentNode.AppendChild(document.CreateSignificantWhitespace(reader.Value.ToString()));
break;
default:
if (!string.IsNullOrEmpty(propertyName) && propertyName[0] == '?')
CreateInstruction(reader, document, currentNode, propertyName);
else if (reader.TokenType == JsonToken.StartArray) {
ReadArrayElements(reader, document, propertyName, currentNode, manager);
} else {
ReadElement(reader, document, currentNode, propertyName, manager);
}
break;
}
}
private void ReadElement(JsonReader reader, IXmlDocument document, IXmlNode currentNode, string propertyName, XmlNamespaceManager manager)
{
if (string.IsNullOrEmpty(propertyName))
throw new JsonSerializationException("XmlNodeConverter cannot convert JSON with an empty property name to XML.");
Dictionary<string, string> dictionary = ReadAttributeElements(reader, manager);
string prefix = MiscellaneousUtils.GetPrefix(propertyName);
if (propertyName.StartsWith("@")) {
string text = propertyName.Substring(1);
string value = reader.Value.ToString();
string prefix2 = MiscellaneousUtils.GetPrefix(text);
IXmlNode attributeNode = (!string.IsNullOrEmpty(prefix2)) ? document.CreateAttribute(text, manager.LookupNamespace(prefix2), value) : document.CreateAttribute(text, value);
((IXmlElement)currentNode).SetAttributeNode(attributeNode);
} else {
IXmlElement xmlElement = CreateElement(propertyName, document, prefix, manager);
currentNode.AppendChild(xmlElement);
foreach (KeyValuePair<string, string> item in dictionary) {
string prefix3 = MiscellaneousUtils.GetPrefix(item.Key);
IXmlNode attributeNode2 = (!string.IsNullOrEmpty(prefix3)) ? document.CreateAttribute(item.Key, manager.LookupNamespace(prefix3), item.Value) : document.CreateAttribute(item.Key, item.Value);
xmlElement.SetAttributeNode(attributeNode2);
}
if (reader.TokenType == JsonToken.String || reader.TokenType == JsonToken.Integer || reader.TokenType == JsonToken.Float || reader.TokenType == JsonToken.Boolean || reader.TokenType == JsonToken.Date)
xmlElement.AppendChild(document.CreateTextNode(ConvertTokenToXmlValue(reader)));
else if (reader.TokenType != JsonToken.Null && reader.TokenType != JsonToken.EndObject) {
manager.PushScope();
DeserializeNode(reader, document, manager, xmlElement);
manager.PopScope();
}
}
}
private string ConvertTokenToXmlValue(JsonReader reader)
{
if (reader.TokenType == JsonToken.String)
return reader.Value.ToString();
if (reader.TokenType == JsonToken.Integer)
return XmlConvert.ToString(Convert.ToInt64(reader.Value, CultureInfo.InvariantCulture));
if (reader.TokenType == JsonToken.Float)
return XmlConvert.ToString(Convert.ToDouble(reader.Value, CultureInfo.InvariantCulture));
if (reader.TokenType == JsonToken.Boolean)
return XmlConvert.ToString(Convert.ToBoolean(reader.Value, CultureInfo.InvariantCulture));
if (reader.TokenType == JsonToken.Date) {
DateTime value = Convert.ToDateTime(reader.Value, CultureInfo.InvariantCulture);
return XmlConvert.ToString(value, DateTimeUtils.ToSerializationMode(value.Kind));
}
if (reader.TokenType == JsonToken.Null)
return null;
throw JsonSerializationException.Create(reader, "Cannot get an XML string value from token type '{0}'.".FormatWith(CultureInfo.InvariantCulture, reader.TokenType));
}
private void ReadArrayElements(JsonReader reader, IXmlDocument document, string propertyName, IXmlNode currentNode, XmlNamespaceManager manager)
{
string prefix = MiscellaneousUtils.GetPrefix(propertyName);
IXmlElement xmlElement = CreateElement(propertyName, document, prefix, manager);
currentNode.AppendChild(xmlElement);
int num = 0;
while (reader.Read() && reader.TokenType != JsonToken.EndArray) {
DeserializeValue(reader, document, manager, propertyName, xmlElement);
num++;
}
if (WriteArrayAttribute)
AddJsonArrayAttribute(xmlElement, document);
if (num == 1 && WriteArrayAttribute) {
IXmlElement element = xmlElement.ChildNodes.OfType<IXmlElement>().Single((IXmlElement n) => n.LocalName == propertyName);
AddJsonArrayAttribute(element, document);
}
}
private void AddJsonArrayAttribute(IXmlElement element, IXmlDocument document)
{
element.SetAttributeNode(document.CreateAttribute("json:Array", "http://james.newtonking.com/projects/json", "true"));
if (element is XElementWrapper && element.GetPrefixOfNamespace("http://james.newtonking.com/projects/json") == null)
element.SetAttributeNode(document.CreateAttribute("xmlns:json", "http://www.w3.org/2000/xmlns/", "http://james.newtonking.com/projects/json"));
}
private Dictionary<string, string> ReadAttributeElements(JsonReader reader, XmlNamespaceManager manager)
{
Dictionary<string, string> dictionary = new Dictionary<string, string>();
bool flag = false;
bool flag2 = false;
if (reader.TokenType != JsonToken.String && reader.TokenType != JsonToken.Null && reader.TokenType != JsonToken.Boolean && reader.TokenType != JsonToken.Integer && reader.TokenType != JsonToken.Float && reader.TokenType != JsonToken.Date && reader.TokenType != JsonToken.StartConstructor) {
while (!flag && !flag2 && reader.Read()) {
switch (reader.TokenType) {
case JsonToken.PropertyName: {
string text = reader.Value.ToString();
if (!string.IsNullOrEmpty(text)) {
switch (text[0]) {
case '@': {
text = text.Substring(1);
reader.Read();
string value = ConvertTokenToXmlValue(reader);
dictionary.Add(text, value);
if (IsNamespaceAttribute(text, out string prefix))
manager.AddNamespace(prefix, value);
break;
}
case '$': {
text = text.Substring(1);
reader.Read();
string value = reader.Value.ToString();
string text2 = manager.LookupPrefix("http://james.newtonking.com/projects/json");
if (text2 == null) {
int? nullable = null;
while (manager.LookupNamespace("json" + nullable) != null) {
nullable = nullable.GetValueOrDefault() + 1;
}
text2 = "json" + nullable;
dictionary.Add("xmlns:" + text2, "http://james.newtonking.com/projects/json");
manager.AddNamespace(text2, "http://james.newtonking.com/projects/json");
}
dictionary.Add(text2 + ":" + text, value);
break;
}
default:
flag = true;
break;
}
} else
flag = true;
break;
}
case JsonToken.EndObject:
flag2 = true;
break;
default:
throw new JsonSerializationException("Unexpected JsonToken: " + reader.TokenType);
}
}
}
return dictionary;
}
private void CreateInstruction(JsonReader reader, IXmlDocument document, IXmlNode currentNode, string propertyName)
{
if (propertyName == "?xml") {
string version = null;
string encoding = null;
string standalone = null;
while (reader.Read() && reader.TokenType != JsonToken.EndObject) {
switch (reader.Value.ToString()) {
case "@version":
reader.Read();
version = reader.Value.ToString();
break;
case "@encoding":
reader.Read();
encoding = reader.Value.ToString();
break;
case "@standalone":
reader.Read();
standalone = reader.Value.ToString();
break;
default:
throw new JsonSerializationException("Unexpected property name encountered while deserializing XmlDeclaration: " + reader.Value);
}
}
IXmlNode newChild = document.CreateXmlDeclaration(version, encoding, standalone);
currentNode.AppendChild(newChild);
} else {
IXmlNode newChild2 = document.CreateProcessingInstruction(propertyName.Substring(1), reader.Value.ToString());
currentNode.AppendChild(newChild2);
}
}
private IXmlElement CreateElement(string elementName, IXmlDocument document, string elementPrefix, XmlNamespaceManager manager)
{
string text = string.IsNullOrEmpty(elementPrefix) ? manager.DefaultNamespace : manager.LookupNamespace(elementPrefix);
return (!string.IsNullOrEmpty(text)) ? document.CreateElement(elementName, text) : document.CreateElement(elementName);
}
private void DeserializeNode(JsonReader reader, IXmlDocument document, XmlNamespaceManager manager, IXmlNode currentNode)
{
do {
switch (reader.TokenType) {
case JsonToken.EndObject:
case JsonToken.EndArray:
return;
case JsonToken.PropertyName: {
if (currentNode.NodeType == XmlNodeType.Document && document.DocumentElement != null)
throw new JsonSerializationException("JSON root object has multiple properties. The root object must have a single property in order to create a valid XML document. Consider specifing a DeserializeRootElementName.");
string propertyName = reader.Value.ToString();
reader.Read();
if (reader.TokenType == JsonToken.StartArray) {
int num = 0;
while (reader.Read() && reader.TokenType != JsonToken.EndArray) {
DeserializeValue(reader, document, manager, propertyName, currentNode);
num++;
}
if (num == 1 && WriteArrayAttribute) {
IXmlElement element = currentNode.ChildNodes.OfType<IXmlElement>().Single((IXmlElement n) => n.LocalName == propertyName);
AddJsonArrayAttribute(element, document);
}
} else
DeserializeValue(reader, document, manager, propertyName, currentNode);
break;
}
case JsonToken.StartConstructor: {
string propertyName2 = reader.Value.ToString();
while (reader.Read() && reader.TokenType != JsonToken.EndConstructor) {
DeserializeValue(reader, document, manager, propertyName2, currentNode);
}
break;
}
case JsonToken.Comment:
currentNode.AppendChild(document.CreateComment((string)reader.Value));
break;
default:
throw new JsonSerializationException("Unexpected JsonToken when deserializing node: " + reader.TokenType);
}
} while (reader.TokenType == JsonToken.PropertyName || reader.Read());
}
private bool IsNamespaceAttribute(string attributeName, out string prefix)
{
if (attributeName.StartsWith("xmlns", StringComparison.Ordinal)) {
if (attributeName.Length == 5) {
prefix = string.Empty;
return true;
}
if (attributeName[5] == ':') {
prefix = attributeName.Substring(6, attributeName.Length - 6);
return true;
}
}
prefix = null;
return false;
}
private IEnumerable<IXmlNode> ValueAttributes(IEnumerable<IXmlNode> c)
{
return from a in c
where a.NamespaceUri != "http://james.newtonking.com/projects/json"
select a;
}
public override bool CanConvert(Type valueType)
{
if (typeof(XObject).IsAssignableFrom(valueType))
return true;
if (typeof(XmlNode).IsAssignableFrom(valueType))
return true;
return false;
}
}
}