diff --git a/Src/Newtonsoft.Json.Tests/Converters/XmlNodeConverterTest.cs b/Src/Newtonsoft.Json.Tests/Converters/XmlNodeConverterTest.cs
index 9d9789ba..b935e471 100644
--- a/Src/Newtonsoft.Json.Tests/Converters/XmlNodeConverterTest.cs
+++ b/Src/Newtonsoft.Json.Tests/Converters/XmlNodeConverterTest.cs
@@ -159,6 +159,116 @@ namespace Newtonsoft.Json.Tests.Converters
Assert.AreEqual("", xmlNode.OuterXml);
}
+
+ [Test]
+ public void XmlNode_Roundtrip_PropertyNameWithColon()
+ {
+ const string initialJson = @"{""Be:fore:After!"":""Value!""}";
+
+ XmlDocument xmlNode = JsonConvert.DeserializeXmlNode(initialJson, null, false, true);
+
+ Assert.AreEqual("Value!", xmlNode.OuterXml);
+
+ string json = JsonConvert.SerializeXmlNode(xmlNode);
+
+ Assert.AreEqual(initialJson, json);
+ }
+
+ [Test]
+ public void XmlNode_Roundtrip_PropertyNameWithEscapedValue()
+ {
+ const string initialJson = @"{""BeforeAfter!"":""Value!""}";
+
+ XmlDocument xmlNode = JsonConvert.DeserializeXmlNode(initialJson);
+
+ Assert.AreEqual("Value!", xmlNode.OuterXml);
+
+ string json = JsonConvert.SerializeXmlNode(xmlNode);
+
+ Assert.AreEqual(initialJson, json);
+ }
+
+ [Test]
+ public void XmlNode_EncodeSpecialCharacters()
+ {
+ string initialJson = @"{
+ ""?xml"": {
+ ""@version"": ""1.0"",
+ ""@standalone"": ""no""
+ },
+ ""?xml-stylesheet"": ""href=\""classic.xsl\"" type=\""text/xml\"""",
+ ""span"": {
+ ""@class"": ""vevent"",
+ ""a"": {
+ ""@class"": ""url"",
+ ""@href"": ""http://www.web2con.com/"",
+ ""span"": [
+ {
+ ""@class"": ""summary"",
+ ""#text"": ""Web 2.0 Conference"",
+ ""#cdata-section"": ""my escaped text""
+ },
+ {
+ ""@class"": ""location"",
+ ""#text"": ""Argent Hotel, San Francisco, CA""
+ }
+ ],
+ ""abbr"": [
+ {
+ ""@class"": ""dtstart"",
+ ""@title"": ""2005-10-05"",
+ ""#text"": ""October 5""
+ },
+ {
+ ""@class"": ""dtend"",
+ ""@title"": ""2005-10-08"",
+ ""#text"": ""7""
+ }
+ ]
+ }
+ }
+}";
+
+ XmlDocument xmlNode = JsonConvert.DeserializeXmlNode(initialJson, "root", false, true);
+
+ StringAssert.AreEqual(@"
+ <_x003F_xml>
+ <_x0040_version>1.0
+ <_x0040_standalone>no
+
+ <_x003F_xml-stylesheet>href=""classic.xsl"" type=""text/xml""
+
+ <_x0040_class>vevent
+
+ <_x0040_class>url
+ <_x0040_href>http://www.web2con.com/
+
+ <_x0040_class>summary
+ <_x0023_text>Web 2.0 Conference
+ <_x0023_cdata-section>my escaped text
+
+
+ <_x0040_class>location
+ <_x0023_text>Argent Hotel, San Francisco, CA
+
+
+ <_x0040_class>dtstart
+ <_x0040_title>2005-10-05
+ <_x0023_text>October 5
+
+
+ <_x0040_class>dtend
+ <_x0040_title>2005-10-08
+ <_x0023_text>7
+
+
+
+", IndentXml(xmlNode.OuterXml));
+
+ string json = JsonConvert.SerializeXmlNode(xmlNode, Formatting.Indented, true);
+
+ Assert.AreEqual(initialJson, json);
+ }
#endif
#if !NET20
@@ -184,6 +294,146 @@ namespace Newtonsoft.Json.Tests.Converters
}
#if !NET20
+ [Test]
+ public void XNode_Roundtrip_PropertyNameWithColon()
+ {
+ const string initialJson = @"{""Be:fore:After!"":""Value!""}";
+
+ XDocument xmlNode = JsonConvert.DeserializeXNode(initialJson, null, false, true);
+
+ Assert.AreEqual("Value!", xmlNode.ToString());
+
+ string json = JsonConvert.SerializeXNode(xmlNode);
+
+ Assert.AreEqual(initialJson, json);
+ }
+
+ [Test]
+ public void XNode_Roundtrip_PropertyNameWithEscapedValue()
+ {
+ const string initialJson = @"{""BeforeAfter!"":""Value!""}";
+
+ XDocument xmlNode = JsonConvert.DeserializeXNode(initialJson);
+
+ Assert.AreEqual("Value!", xmlNode.ToString());
+
+ string json = JsonConvert.SerializeXNode(xmlNode);
+
+ Assert.AreEqual(initialJson, json);
+ }
+
+ [Test]
+ public void XNode_EncodeSpecialCharacters()
+ {
+ string initialJson = @"{
+ ""?xml"": {
+ ""@version"": ""1.0"",
+ ""@standalone"": ""no""
+ },
+ ""?xml-stylesheet"": ""href=\""classic.xsl\"" type=\""text/xml\"""",
+ ""span"": {
+ ""@class"": ""vevent"",
+ ""a"": {
+ ""@class"": ""url"",
+ ""@href"": ""http://www.web2con.com/"",
+ ""span"": [
+ {
+ ""@class"": ""summary"",
+ ""#text"": ""Web 2.0 Conference"",
+ ""#cdata-section"": ""my escaped text""
+ },
+ {
+ ""@class"": ""location"",
+ ""#text"": ""Argent Hotel, San Francisco, CA""
+ }
+ ],
+ ""abbr"": [
+ {
+ ""@class"": ""dtstart"",
+ ""@title"": ""2005-10-05"",
+ ""#text"": ""October 5""
+ },
+ {
+ ""@class"": ""dtend"",
+ ""@title"": ""2005-10-08"",
+ ""#text"": ""7""
+ }
+ ]
+ }
+ }
+}";
+
+ XDocument xmlNode = JsonConvert.DeserializeXNode(initialJson, "root", false, true);
+
+ StringAssert.AreEqual(@"
+ <_x003F_xml>
+ <_x0040_version>1.0
+ <_x0040_standalone>no
+
+ <_x003F_xml-stylesheet>href=""classic.xsl"" type=""text/xml""
+
+ <_x0040_class>vevent
+
+ <_x0040_class>url
+ <_x0040_href>http://www.web2con.com/
+
+ <_x0040_class>summary
+ <_x0023_text>Web 2.0 Conference
+ <_x0023_cdata-section>my escaped text
+
+
+ <_x0040_class>location
+ <_x0023_text>Argent Hotel, San Francisco, CA
+
+
+ <_x0040_class>dtstart
+ <_x0040_title>2005-10-05
+ <_x0023_text>October 5
+
+
+ <_x0040_class>dtend
+ <_x0040_title>2005-10-08
+ <_x0023_text>7
+
+
+
+", xmlNode.ToString());
+
+ string json = JsonConvert.SerializeXNode(xmlNode, Formatting.Indented, true);
+
+ Assert.AreEqual(initialJson, json);
+ }
+
+ [Test]
+ public void XNode_MetadataArray_EncodeSpecialCharacters()
+ {
+ string initialJson = @"{
+ ""$id"": ""1"",
+ ""$values"": [
+ ""1"",
+ ""2"",
+ ""3"",
+ ""4"",
+ ""5""
+ ]
+}";
+
+ XDocument xmlNode = JsonConvert.DeserializeXNode(initialJson, "root", false, true);
+
+ StringAssert.AreEqual(@"
+ <_x0024_id>1
+ <_x0024_values>1
+ <_x0024_values>2
+ <_x0024_values>3
+ <_x0024_values>4
+ <_x0024_values>5
+", xmlNode.ToString());
+
+ string json = JsonConvert.SerializeXNode(xmlNode, Formatting.Indented, true);
+
+ Assert.AreEqual(initialJson, json);
+ }
+
[Test]
public void SerializeDollarProperty()
{
diff --git a/Src/Newtonsoft.Json/Converters/XmlNodeConverter.cs b/Src/Newtonsoft.Json/Converters/XmlNodeConverter.cs
index 4229f1da..ac598929 100644
--- a/Src/Newtonsoft.Json/Converters/XmlNodeConverter.cs
+++ b/Src/Newtonsoft.Json/Converters/XmlNodeConverter.cs
@@ -957,7 +957,7 @@ namespace Newtonsoft.Json.Converters
public string DeserializeRootElementName { get; set; }
///
- /// Gets or sets a flag to indicate whether to write the Json.NET array attribute.
+ /// Gets or sets a value to indicate whether to write the Json.NET array attribute.
/// This attribute helps preserve arrays when converting the written XML back to JSON.
///
/// true if the array attribute is written to the XML; otherwise, false.
@@ -969,7 +969,16 @@ namespace Newtonsoft.Json.Converters
/// true if the JSON root object is omitted; otherwise, false.
public bool OmitRootObject { get; set; }
-#region Writing
+ ///
+ /// Gets or sets a value indicating whether to encode special characters when converting JSON to XML.
+ /// If true, special characters like ':', '@', '?', '#' and '$' in JSON property names aren't used to specify
+ /// XML namespaces, attributes or processing directives. Instead special characters are encoded and written
+ /// as part of the XML element name.
+ ///
+ /// true if special characters are encoded; otherwise, false.
+ public bool EncodeSpecialCharacters { get; set; }
+
+ #region Writing
///
/// Writes the JSON representation of the object.
///
@@ -1554,47 +1563,50 @@ namespace Newtonsoft.Json.Converters
private void DeserializeValue(JsonReader reader, IXmlDocument document, XmlNamespaceManager manager, string propertyName, IXmlNode currentNode)
{
- switch (propertyName)
+ if (!EncodeSpecialCharacters)
{
- case TextName:
- currentNode.AppendChild(document.CreateTextNode(ConvertTokenToXmlValue(reader)));
- break;
- case CDataName:
- currentNode.AppendChild(document.CreateCDataSection(ConvertTokenToXmlValue(reader)));
- break;
- case WhitespaceName:
- currentNode.AppendChild(document.CreateWhitespace(ConvertTokenToXmlValue(reader)));
- break;
- case SignificantWhitespaceName:
- currentNode.AppendChild(document.CreateSignificantWhitespace(ConvertTokenToXmlValue(reader)));
- break;
- default:
- // processing instructions and the xml declaration start with ?
- if (!string.IsNullOrEmpty(propertyName) && propertyName[0] == '?')
- {
- CreateInstruction(reader, document, currentNode, propertyName);
- }
-#if HAVE_XML_DOCUMENT_TYPE
- else if (string.Equals(propertyName, "!DOCTYPE", StringComparison.OrdinalIgnoreCase))
- {
- CreateDocumentType(reader, document, currentNode);
- }
-#endif
- else
- {
- if (reader.TokenType == JsonToken.StartArray)
+ switch (propertyName)
+ {
+ case TextName:
+ currentNode.AppendChild(document.CreateTextNode(ConvertTokenToXmlValue(reader)));
+ return;
+ case CDataName:
+ currentNode.AppendChild(document.CreateCDataSection(ConvertTokenToXmlValue(reader)));
+ return;
+ case WhitespaceName:
+ currentNode.AppendChild(document.CreateWhitespace(ConvertTokenToXmlValue(reader)));
+ return;
+ case SignificantWhitespaceName:
+ currentNode.AppendChild(document.CreateSignificantWhitespace(ConvertTokenToXmlValue(reader)));
+ return;
+ default:
+ // processing instructions and the xml declaration start with ?
+ if (!string.IsNullOrEmpty(propertyName) && propertyName[0] == '?')
{
- // handle nested arrays
- ReadArrayElements(reader, document, propertyName, currentNode, manager);
+ CreateInstruction(reader, document, currentNode, propertyName);
return;
}
-
- // have to wait until attributes have been parsed before creating element
- // attributes may contain namespace info used by the element
- ReadElement(reader, document, currentNode, propertyName, manager);
- }
- break;
+#if HAVE_XML_DOCUMENT_TYPE
+ else if (string.Equals(propertyName, "!DOCTYPE", StringComparison.OrdinalIgnoreCase))
+ {
+ CreateDocumentType(reader, document, currentNode);
+ return;
+ }
+#endif
+ break;
+ }
}
+
+ if (reader.TokenType == JsonToken.StartArray)
+ {
+ // handle nested arrays
+ ReadArrayElements(reader, document, propertyName, currentNode, manager);
+ return;
+ }
+
+ // have to wait until attributes have been parsed before creating element
+ // attributes may contain namespace info used by the element
+ ReadElement(reader, document, currentNode, propertyName, manager);
}
private void ReadElement(JsonReader reader, IXmlDocument document, IXmlNode currentNode, string propertyName, XmlNamespaceManager manager)
@@ -1604,36 +1616,50 @@ namespace Newtonsoft.Json.Converters
throw JsonSerializationException.Create(reader, "XmlNodeConverter cannot convert JSON with an empty property name to XML.");
}
- Dictionary attributeNameValues = ReadAttributeElements(reader, manager);
+ Dictionary attributeNameValues = null;
+ string elementPrefix = null;
- string elementPrefix = MiscellaneousUtils.GetPrefix(propertyName);
-
- if (propertyName.StartsWith('@'))
+ if (!EncodeSpecialCharacters)
{
- string attributeName = propertyName.Substring(1);
- string attributePrefix = MiscellaneousUtils.GetPrefix(attributeName);
+ attributeNameValues = ShouldReadInto(reader)
+ ? ReadAttributeElements(reader, manager)
+ : null;
+ elementPrefix = MiscellaneousUtils.GetPrefix(propertyName);
- AddAttribute(reader, document, currentNode, propertyName, attributeName, manager, attributePrefix);
- return;
- }
-
- if (propertyName.StartsWith('$'))
- {
- switch (propertyName)
+ if (propertyName.StartsWith('@'))
{
- case JsonTypeReflector.ArrayValuesPropertyName:
- propertyName = propertyName.Substring(1);
- elementPrefix = manager.LookupPrefix(JsonNamespaceUri);
- CreateElement(reader, document, currentNode, propertyName, manager, elementPrefix, attributeNameValues);
- return;
- case JsonTypeReflector.IdPropertyName:
- case JsonTypeReflector.RefPropertyName:
- case JsonTypeReflector.TypePropertyName:
- case JsonTypeReflector.ValuePropertyName:
- string attributeName = propertyName.Substring(1);
- string attributePrefix = manager.LookupPrefix(JsonNamespaceUri);
- AddAttribute(reader, document, currentNode, propertyName, attributeName, manager, attributePrefix);
- return;
+ string attributeName = propertyName.Substring(1);
+ string attributePrefix = MiscellaneousUtils.GetPrefix(attributeName);
+
+ AddAttribute(reader, document, currentNode, propertyName, attributeName, manager, attributePrefix);
+ return;
+ }
+
+ if (propertyName.StartsWith('$'))
+ {
+ switch (propertyName)
+ {
+ case JsonTypeReflector.ArrayValuesPropertyName:
+ propertyName = propertyName.Substring(1);
+ elementPrefix = manager.LookupPrefix(JsonNamespaceUri);
+ CreateElement(reader, document, currentNode, propertyName, manager, elementPrefix, attributeNameValues);
+ return;
+ case JsonTypeReflector.IdPropertyName:
+ case JsonTypeReflector.RefPropertyName:
+ case JsonTypeReflector.TypePropertyName:
+ case JsonTypeReflector.ValuePropertyName:
+ string attributeName = propertyName.Substring(1);
+ string attributePrefix = manager.LookupPrefix(JsonNamespaceUri);
+ AddAttribute(reader, document, currentNode, propertyName, attributeName, manager, attributePrefix);
+ return;
+ }
+ }
+ }
+ else
+ {
+ if (ShouldReadInto(reader))
+ {
+ reader.ReadAndAssert();
}
}
@@ -1813,7 +1839,7 @@ namespace Newtonsoft.Json.Converters
#endif
}
- private Dictionary ReadAttributeElements(JsonReader reader, XmlNamespaceManager manager)
+ private bool ShouldReadInto(JsonReader reader)
{
// a string token means the element only has a single text child
switch (reader.TokenType)
@@ -1826,9 +1852,14 @@ namespace Newtonsoft.Json.Converters
case JsonToken.Date:
case JsonToken.Bytes:
case JsonToken.StartConstructor:
- return null;
+ return false;
}
+ return true;
+ }
+
+ private Dictionary ReadAttributeElements(JsonReader reader, XmlNamespaceManager manager)
+ {
Dictionary attributeNameValues = null;
bool finished = false;
@@ -2021,7 +2052,7 @@ namespace Newtonsoft.Json.Converters
private IXmlElement CreateElement(string elementName, IXmlDocument document, string elementPrefix, XmlNamespaceManager manager)
{
- string encodeName = XmlConvert.EncodeName(elementName);
+ string encodeName = EncodeSpecialCharacters ? XmlConvert.EncodeLocalName(elementName) : XmlConvert.EncodeName(elementName);
string ns = string.IsNullOrEmpty(elementPrefix) ? manager.DefaultNamespace : manager.LookupNamespace(elementPrefix);
IXmlElement element = (!string.IsNullOrEmpty(ns)) ? document.CreateElement(encodeName, ns) : document.CreateElement(encodeName);
diff --git a/Src/Newtonsoft.Json/JsonConvert.cs b/Src/Newtonsoft.Json/JsonConvert.cs
index 2e5935d7..fa6c8484 100644
--- a/Src/Newtonsoft.Json/JsonConvert.cs
+++ b/Src/Newtonsoft.Json/JsonConvert.cs
@@ -942,15 +942,38 @@ namespace Newtonsoft.Json
/// The JSON string.
/// The name of the root element to append when deserializing.
///
- /// A flag to indicate whether to write the Json.NET array attribute.
+ /// A value to indicate whether to write the Json.NET array attribute.
/// This attribute helps preserve arrays when converting the written XML back to JSON.
///
/// The deserialized .
public static XmlDocument DeserializeXmlNode(string value, string deserializeRootElementName, bool writeArrayAttribute)
+ {
+ return DeserializeXmlNode(value, deserializeRootElementName, writeArrayAttribute, false);
+ }
+
+ ///
+ /// Deserializes the from a JSON string nested in a root element specified by ,
+ /// writes a Json.NET array attribute for collections, and encodes special characters.
+ ///
+ /// The JSON string.
+ /// The name of the root element to append when deserializing.
+ ///
+ /// A value to indicate whether to write the Json.NET array attribute.
+ /// This attribute helps preserve arrays when converting the written XML back to JSON.
+ ///
+ ///
+ /// A value to indicate whether to encode special characters when converting JSON to XML.
+ /// If true, special characters like ':', '@', '?', '#' and '$' in JSON property names aren't used to specify
+ /// XML namespaces, attributes or processing directives. Instead special characters are encoded and written
+ /// as part of the XML element name.
+ ///
+ /// The deserialized .
+ public static XmlDocument DeserializeXmlNode(string value, string deserializeRootElementName, bool writeArrayAttribute, bool encodeSpecialCharacters)
{
XmlNodeConverter converter = new XmlNodeConverter();
converter.DeserializeRootElementName = deserializeRootElementName;
converter.WriteArrayAttribute = writeArrayAttribute;
+ converter.EncodeSpecialCharacters = encodeSpecialCharacters;
return (XmlDocument)DeserializeObject(value, typeof(XmlDocument), converter);
}
@@ -1020,15 +1043,38 @@ namespace Newtonsoft.Json
/// The JSON string.
/// The name of the root element to append when deserializing.
///
- /// A flag to indicate whether to write the Json.NET array attribute.
+ /// A value to indicate whether to write the Json.NET array attribute.
/// This attribute helps preserve arrays when converting the written XML back to JSON.
///
/// The deserialized .
public static XDocument DeserializeXNode(string value, string deserializeRootElementName, bool writeArrayAttribute)
+ {
+ return DeserializeXNode(value, deserializeRootElementName, writeArrayAttribute, false);
+ }
+
+ ///
+ /// Deserializes the from a JSON string nested in a root element specified by ,
+ /// writes a Json.NET array attribute for collections, and encodes special characters.
+ ///
+ /// The JSON string.
+ /// The name of the root element to append when deserializing.
+ ///
+ /// A value to indicate whether to write the Json.NET array attribute.
+ /// This attribute helps preserve arrays when converting the written XML back to JSON.
+ ///
+ ///
+ /// A value to indicate whether to encode special characters when converting JSON to XML.
+ /// If true, special characters like ':', '@', '?', '#' and '$' in JSON property names aren't used to specify
+ /// XML namespaces, attributes or processing directives. Instead special characters are encoded and written
+ /// as part of the XML element name.
+ ///
+ /// The deserialized .
+ public static XDocument DeserializeXNode(string value, string deserializeRootElementName, bool writeArrayAttribute, bool encodeSpecialCharacters)
{
XmlNodeConverter converter = new XmlNodeConverter();
converter.DeserializeRootElementName = deserializeRootElementName;
converter.WriteArrayAttribute = writeArrayAttribute;
+ converter.EncodeSpecialCharacters = encodeSpecialCharacters;
return (XDocument)DeserializeObject(value, typeof(XDocument), converter);
}