From a139d7bf20971b660283ac087be0473cb98f02ec Mon Sep 17 00:00:00 2001 From: James Newton-King Date: Sat, 31 Mar 2012 15:39:53 +1300 Subject: [PATCH] -Fixed XmlNodeConverter not turning all attribute properties into attributes -Added .gitattributes file --- .gitattributes | 49 + Build/build.ps1 | 2 +- .../Converters/XmlNodeConverterTest.cs | 3610 +++++++++-------- .../Converters/XmlNodeConverter.cs | 3116 +++++++------- .../Properties/AssemblyInfo.cs | 188 +- 5 files changed, 3520 insertions(+), 3445 deletions(-) create mode 100644 .gitattributes diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 00000000..7b90091e --- /dev/null +++ b/.gitattributes @@ -0,0 +1,49 @@ +*.doc diff=astextplain +*.DOC diff=astextplain +*.docx diff=astextplain +*.DOCX diff=astextplain +*.dot diff=astextplain +*.DOT diff=astextplain +*.pdf diff=astextplain +*.PDF diff=astextplain +*.rtf diff=astextplain +*.RTF diff=astextplain + +*.jpg binary +*.png binary +*.gif binary + +*.cs text diff=csharp +*.vb text +*.c text +*.cpp text +*.cxx text +*.h text +*.hxx text +*.py text +*.rb text +*.java text +*.html text +*.htm text +*.css text +*.scss text +*.sass text +*.less text +*.js text +*.lisp text +*.clj text +*.sql text +*.php text +*.lua text +*.m text +*.asm text +*.erl text +*.fs text +*.fsx text +*.hs text + +*.csproj text merge=union +*.vbproj text merge=union +*.fsproj text merge=union +*.dbproj text merge=union +*.sln text eol=crlf merge=union diff --git a/Build/build.ps1 b/Build/build.ps1 index fc673f04..faaed2b8 100644 --- a/Build/build.ps1 +++ b/Build/build.ps1 @@ -17,7 +17,7 @@ $workingDir = "$baseDir\Working" $builds = @( @{Name = "Newtonsoft.Json"; TestsName = "Newtonsoft.Json.Tests"; Constants=""; FinalDir="Net40"; NuGetDir = "net40"; Framework="net-4.0"; Sign=$true}, - @{Name = "Newtonsoft.Json.Metro"; TestsName = $null; Constants="NETFX_CORE"; FinalDir="Metro"; NuGetDir = "winrt45"; Framework="net-4.5"; Sign=$true}, + #@{Name = "Newtonsoft.Json.Metro"; TestsName = $null; Constants="NETFX_CORE"; FinalDir="Metro"; NuGetDir = "winrt45"; Framework="net-4.5"; Sign=$true}, # unsigned SL/WP @{Name = "Newtonsoft.Json.WindowsPhone"; TestsName = $null; Constants="SILVERLIGHT;WINDOWS_PHONE"; FinalDir="WindowsPhone"; NuGetDir = "sl3-wp,sl4-windowsphone71"; Framework="net-4.0"; Sign=$false}, diff --git a/Src/Newtonsoft.Json.Tests/Converters/XmlNodeConverterTest.cs b/Src/Newtonsoft.Json.Tests/Converters/XmlNodeConverterTest.cs index c4e4c93d..baea14c8 100644 --- a/Src/Newtonsoft.Json.Tests/Converters/XmlNodeConverterTest.cs +++ b/Src/Newtonsoft.Json.Tests/Converters/XmlNodeConverterTest.cs @@ -1,1801 +1,1811 @@ -#region License -// Copyright (c) 2007 James Newton-King -// -// Permission is hereby granted, free of charge, to any person -// obtaining a copy of this software and associated documentation -// files (the "Software"), to deal in the Software without -// restriction, including without limitation the rights to use, -// copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following -// conditions: -// -// The above copyright notice and this permission notice shall be -// included in all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES -// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT -// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, -// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR -// OTHER DEALINGS IN THE SOFTWARE. -#endregion - -#if !SILVERLIGHT && !NETFX_CORE -using System; -using System.Collections.Generic; -using Newtonsoft.Json.Tests.Serialization; -using Newtonsoft.Json.Tests.TestObjects; -#if !NETFX_CORE -using NUnit.Framework; -#else -using Microsoft.VisualStudio.TestTools.UnitTesting; -using TestFixture = Microsoft.VisualStudio.TestTools.UnitTesting.TestClassAttribute; -using Test = Microsoft.VisualStudio.TestTools.UnitTesting.TestMethodAttribute; -#endif -using Newtonsoft.Json; -using System.IO; -using System.Xml; -using Newtonsoft.Json.Converters; -using Newtonsoft.Json.Utilities; -using Newtonsoft.Json.Linq; -#if !NET20 -using System.Xml.Linq; -#endif - -namespace Newtonsoft.Json.Tests.Converters -{ - [TestFixture] - public class XmlNodeConverterTest : TestFixtureBase - { - private string SerializeXmlNode(XmlNode node) - { - string json = JsonConvert.SerializeXmlNode(node, Formatting.Indented); - XmlNodeReader reader = new XmlNodeReader(node); - -#if !NET20 - XObject xNode; - if (node is XmlDocument) - { - xNode = XDocument.Load(reader); - } - else if (node is XmlAttribute) - { - XmlAttribute attribute = (XmlAttribute) node; - xNode = new XAttribute(XName.Get(attribute.LocalName, attribute.NamespaceURI), attribute.Value); - } - else - { - reader.MoveToContent(); - xNode = XNode.ReadFrom(reader); - } - - string linqJson = JsonConvert.SerializeXNode(xNode, Formatting.Indented); - - Assert.AreEqual(json, linqJson); -#endif - - return json; - } - - private XmlNode DeserializeXmlNode(string json) - { - return DeserializeXmlNode(json, null); - } - - private XmlNode DeserializeXmlNode(string json, string deserializeRootElementName) - { - JsonTextReader reader; - - reader = new JsonTextReader(new StringReader(json)); - reader.Read(); - XmlNodeConverter converter = new XmlNodeConverter(); - if (deserializeRootElementName != null) - converter.DeserializeRootElementName = deserializeRootElementName; - - XmlNode node = (XmlNode)converter.ReadJson(reader, typeof (XmlDocument), null, new JsonSerializer()); - -#if !NET20 - string xmlText = node.OuterXml; - - reader = new JsonTextReader(new StringReader(json)); - reader.Read(); - XDocument d = (XDocument) converter.ReadJson(reader, typeof (XDocument), null, new JsonSerializer()); - - string linqXmlText = d.ToString(SaveOptions.DisableFormatting); - if (d.Declaration != null) - linqXmlText = d.Declaration + linqXmlText; - - Assert.AreEqual(xmlText, linqXmlText); -#endif - - return node; - } - - [Test] - public void DocumentSerializeIndented() - { - string xml = @" - - - - Web 2.0 Conference - October 5 - 7 - Argent Hotel, San Francisco, CA - -"; - XmlDocument doc = new XmlDocument(); - doc.LoadXml(xml); - - string jsonText = SerializeXmlNode(doc); - string expected = @"{ - ""?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"" - } - ] - } - } -}"; - - Assert.AreEqual(expected, jsonText); - - Console.WriteLine("DocumentSerializeIndented"); - Console.WriteLine(jsonText); - Console.WriteLine(); - } - - [Test] - public void SerializeNodeTypes() - { - XmlDocument doc = new XmlDocument(); - string jsonText; - - Console.WriteLine("SerializeNodeTypes"); - - string xml = @" - - - -"; - - XmlDocument document = new XmlDocument(); - document.LoadXml(xml); - - // XmlAttribute - XmlAttribute attribute = document.DocumentElement.ChildNodes[0].Attributes["IsDataSet", "urn:schemas-microsoft-com:xml-msdata"]; - attribute.Value = "true"; - - jsonText = JsonConvert.SerializeXmlNode(attribute); - - Console.WriteLine(jsonText); - Assert.AreEqual(@"{""@msdata:IsDataSet"":""true""}", jsonText); - -#if !NET20 - XDocument d = XDocument.Parse(xml); - XAttribute a = d.Root.Element("{http://www.w3.org/2001/XMLSchema}element").Attribute("{urn:schemas-microsoft-com:xml-msdata}IsDataSet"); - - jsonText = JsonConvert.SerializeXNode(a); - - Assert.AreEqual(@"{""@msdata:IsDataSet"":""true""}", jsonText); -#endif - - // XmlProcessingInstruction - XmlProcessingInstruction instruction = doc.CreateProcessingInstruction("xml-stylesheet", @"href=""classic.xsl"" type=""text/xml"""); - - jsonText = JsonConvert.SerializeXmlNode(instruction); - - Console.WriteLine(jsonText); - Assert.AreEqual(@"{""?xml-stylesheet"":""href=\""classic.xsl\"" type=\""text/xml\""""}", jsonText); - - - // XmlProcessingInstruction - XmlCDataSection cDataSection = doc.CreateCDataSection("true"); - - jsonText = JsonConvert.SerializeXmlNode(cDataSection); - - Console.WriteLine(jsonText); - Assert.AreEqual(@"{""#cdata-section"":""true""}", jsonText); - - - // XmlElement - XmlElement element = doc.CreateElement("xs", "Choice", "http://www.w3.org/2001/XMLSchema"); - element.SetAttributeNode(doc.CreateAttribute("msdata", "IsDataSet", "urn:schemas-microsoft-com:xml-msdata")); - - XmlAttribute aa = doc.CreateAttribute(@"xmlns", "xs", "http://www.w3.org/2000/xmlns/"); - aa.Value = "http://www.w3.org/2001/XMLSchema"; - element.SetAttributeNode(aa); - - aa = doc.CreateAttribute(@"xmlns", "msdata", "http://www.w3.org/2000/xmlns/"); - aa.Value = "urn:schemas-microsoft-com:xml-msdata"; - element.SetAttributeNode(aa); - - element.AppendChild(instruction); - element.AppendChild(cDataSection); - - doc.AppendChild(element); - - jsonText = JsonConvert.SerializeXmlNode(element, Formatting.Indented); - - Assert.AreEqual(@"{ - ""xs:Choice"": { - ""@msdata:IsDataSet"": """", - ""@xmlns:xs"": ""http://www.w3.org/2001/XMLSchema"", - ""@xmlns:msdata"": ""urn:schemas-microsoft-com:xml-msdata"", - ""?xml-stylesheet"": ""href=\""classic.xsl\"" type=\""text/xml\"""", - ""#cdata-section"": ""true"" - } -}", jsonText); - } - - [Test] - public void DocumentFragmentSerialize() - { - XmlDocument doc = new XmlDocument(); - - XmlDocumentFragment fragement = doc.CreateDocumentFragment(); - - fragement.InnerXml = "widgetwidget"; - - string jsonText = JsonConvert.SerializeXmlNode(fragement); - - string expected = @"{""Item"":[""widget"",""widget""]}"; - - Assert.AreEqual(expected, jsonText); - - Console.WriteLine("DocumentFragmentSerialize"); - Console.WriteLine(jsonText); - Console.WriteLine(); - } - - [Test] - public void NamespaceSerializeDeserialize() - { - string xml = @" - - - - - - - - - - - - - - - - -"; - - XmlDocument doc = new XmlDocument(); - doc.LoadXml(xml); - - string jsonText = SerializeXmlNode(doc); - - string expected = @"{ - ""?xml"": { - ""@version"": ""1.0"", - ""@encoding"": ""utf-8"" - }, - ""xs:schema"": { - ""@xs:id"": ""SomeID"", - ""@xmlns"": """", - ""@xmlns:xs"": ""http://www.w3.org/2001/XMLSchema"", - ""@xmlns:msdata"": ""urn:schemas-microsoft-com:xml-msdata"", - ""xs:element"": { - ""@name"": ""MyDataSet"", - ""@msdata:IsDataSet"": ""true"", - ""xs:complexType"": { - ""xs:choice"": { - ""@maxOccurs"": ""unbounded"", - ""xs:element"": { - ""@name"": ""customers"", - ""xs:complexType"": { - ""xs:sequence"": { - ""xs:element"": [ - { - ""@name"": ""CustomerID"", - ""@type"": ""xs:integer"", - ""@minOccurs"": ""0"" - }, - { - ""@name"": ""CompanyName"", - ""@type"": ""xs:string"", - ""@minOccurs"": ""0"" - }, - { - ""@name"": ""Phone"", - ""@type"": ""xs:string"" - } - ] - } - } - } - } - } - } - } -}"; - - Assert.AreEqual(expected, jsonText); - - XmlDocument deserializedDoc = (XmlDocument)DeserializeXmlNode(jsonText); - - Assert.AreEqual(doc.InnerXml, deserializedDoc.InnerXml); - - Console.WriteLine("NamespaceSerializeDeserialize"); - Console.WriteLine(jsonText); - Console.WriteLine(deserializedDoc.InnerXml); - Console.WriteLine(); - } - - [Test] - public void DocumentDeserialize() - { - string jsonText = @"{ - ""?xml"": { - ""@version"": ""1.0"", - ""@standalone"": ""no"" - }, - ""span"": { - ""@class"": ""vevent"", - ""a"": { - ""@class"": ""url"", - ""@href"": ""http://www.web2con.com/"", - ""span"": { - ""@class"": ""summary"", - ""#text"": ""Web 2.0 Conference"", - ""#cdata-section"": ""my escaped text"" - } - } - } -}"; - - XmlDocument doc = (XmlDocument)DeserializeXmlNode(jsonText); - - string expected = @" - - - Web 2.0 Conference - -"; - - string formattedXml = GetIndentedInnerXml(doc); - - Console.WriteLine("DocumentDeserialize"); - Console.WriteLine(formattedXml); - Console.WriteLine(); - - Assert.AreEqual(expected, formattedXml); - } - - private string GetIndentedInnerXml(XmlNode node) - { - XmlWriterSettings settings = new XmlWriterSettings(); - settings.Indent = true; - - StringWriter sw = new StringWriter(); - - using (XmlWriter writer = XmlWriter.Create(sw, settings)) - { - node.WriteTo(writer); - } - - return sw.ToString(); - } - - [Test] - public void SingleTextNode() - { - string xml = @" - - - Alan - http://www.google.com - - - Louis - http://www.yahoo.com - - "; - - XmlDocument doc = new XmlDocument(); - doc.LoadXml(xml); - - string jsonText = SerializeXmlNode(doc); - - XmlDocument newDoc = (XmlDocument)DeserializeXmlNode(jsonText); - - Assert.AreEqual(doc.InnerXml, newDoc.InnerXml); - } - - [Test] - public void EmptyNode() - { - string xml = @" - - - Alan - - - - Louis - http://www.yahoo.com - - "; - - XmlDocument doc = new XmlDocument(); - doc.LoadXml(xml); - - string jsonText = SerializeXmlNode(doc); - - Console.WriteLine(jsonText); - - XmlDocument newDoc = (XmlDocument)DeserializeXmlNode(jsonText); - - Assert.AreEqual(doc.InnerXml, newDoc.InnerXml); - } - - [Test] - public void OtherElementDataTypes() - { - string jsonText = @"{""?xml"":{""@version"":""1.0"",""@standalone"":""no""},""root"":{""person"":[{""@id"":""1"",""Float"":2.5,""Integer"":99},{""@id"":""2"",""Boolean"":true,""date"":""\/Date(954374400000)\/""}]}}"; - - XmlDocument newDoc = (XmlDocument)DeserializeXmlNode(jsonText); - - string expected = @"2.599true2000-03-30T00:00:00Z"; - - Assert.AreEqual(expected, newDoc.InnerXml); - } - - [Test] - [ExpectedException(typeof(JsonSerializationException), ExpectedMessage = "XmlNodeConverter can only convert JSON that begins with an object.")] - public void NoRootObject() - { - XmlDocument newDoc = (XmlDocument)JsonConvert.DeserializeXmlNode(@"[1]"); - } - - [Test] - [ExpectedException(typeof(JsonSerializationException), ExpectedMessage = "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.")] - public void RootObjectMultipleProperties() - { - XmlDocument newDoc = (XmlDocument)JsonConvert.DeserializeXmlNode(@"{Prop1:1,Prop2:2}"); - } - - [Test] - public void JavaScriptConstructor() - { - string jsonText = @"{root:{r:new Date(34343, 55)}}"; - - XmlDocument newDoc = (XmlDocument)DeserializeXmlNode(jsonText); - - string expected = @"3434355"; - - Assert.AreEqual(expected, newDoc.InnerXml); - - string json = SerializeXmlNode(newDoc); - expected = @"{ - ""root"": { - ""r"": { - ""Date"": [ - ""34343"", - ""55"" - ] - } - } -}"; - - Assert.AreEqual(expected, json); - } - - [Test] - public void ForceJsonArray() - { - string arrayXml = @" - - Alan - http://www.google.com - Admin - - "; - - XmlDocument arrayDoc = new XmlDocument(); - arrayDoc.LoadXml(arrayXml); - - string arrayJsonText = SerializeXmlNode(arrayDoc); - string expected = @"{ - ""root"": { - ""person"": { - ""@id"": ""1"", - ""name"": ""Alan"", - ""url"": ""http://www.google.com"", - ""role"": [ - ""Admin"" - ] - } - } -}"; - Assert.AreEqual(expected, arrayJsonText); - - arrayXml = @" - - Alan - http://www.google.com - Admin1 - Admin2 - - "; - - arrayDoc = new XmlDocument(); - arrayDoc.LoadXml(arrayXml); - - arrayJsonText = SerializeXmlNode(arrayDoc); - expected = @"{ - ""root"": { - ""person"": { - ""@id"": ""1"", - ""name"": ""Alan"", - ""url"": ""http://www.google.com"", - ""role"": [ - ""Admin1"", - ""Admin2"" - ] - } - } -}"; - Assert.AreEqual(expected, arrayJsonText); - - arrayXml = @" - - Alan - http://www.google.com - Admin1 - - "; - - arrayDoc = new XmlDocument(); - arrayDoc.LoadXml(arrayXml); - - arrayJsonText = SerializeXmlNode(arrayDoc); - expected = @"{ - ""root"": { - ""person"": { - ""@id"": ""1"", - ""name"": ""Alan"", - ""url"": ""http://www.google.com"", - ""role"": ""Admin1"" - } - } -}"; - Assert.AreEqual(expected, arrayJsonText); - } - - [Test] - [ExpectedException(typeof(JsonSerializationException), ExpectedMessage = "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.")] - public void MultipleRootPropertiesXmlDocument() - { - string json = @"{""count"": 773840,""photos"": null}"; - - JsonConvert.DeserializeXmlNode(json); - } - -#if !NET20 - [Test] - [ExpectedException(typeof(JsonSerializationException), ExpectedMessage = "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.")] - public void MultipleRootPropertiesXDocument() - { - string json = @"{""count"": 773840,""photos"": null}"; - - JsonConvert.DeserializeXNode(json); - } -#endif - - [Test] - public void MultipleRootPropertiesAddRootElement() - { - string json = @"{""count"": 773840,""photos"": 773840}"; - - XmlDocument newDoc = JsonConvert.DeserializeXmlNode(json, "myRoot"); - - Assert.AreEqual(@"773840773840", newDoc.InnerXml); - -#if !NET20 - XDocument newXDoc = JsonConvert.DeserializeXNode(json, "myRoot"); - - Assert.AreEqual(@"773840773840", newXDoc.ToString(SaveOptions.DisableFormatting)); -#endif - } - - [Test] - public void NestedArrays() - { - string json = @"{ - ""available_sizes"": [ - [ - ""assets/images/resized/0001/1070/11070v1-max-150x150.jpg"", - ""assets/images/resized/0001/1070/11070v1-max-150x150.jpg"" - ], - [ - ""assets/images/resized/0001/1070/11070v1-max-250x250.jpg"", - ""assets/images/resized/0001/1070/11070v1-max-250x250.jpg"" - ], - [ - ""assets/images/resized/0001/1070/11070v1-max-250x250.jpg"" - ] - ] -}"; - - XmlDocument newDoc = JsonConvert.DeserializeXmlNode(json, "myRoot"); - - string xml = IndentXml(newDoc.InnerXml); - - Assert.AreEqual(@" - - assets/images/resized/0001/1070/11070v1-max-150x150.jpg - assets/images/resized/0001/1070/11070v1-max-150x150.jpg - - - assets/images/resized/0001/1070/11070v1-max-250x250.jpg - assets/images/resized/0001/1070/11070v1-max-250x250.jpg - - - assets/images/resized/0001/1070/11070v1-max-250x250.jpg - -", IndentXml(newDoc.InnerXml)); - -#if !NET20 - XDocument newXDoc = JsonConvert.DeserializeXNode(json, "myRoot"); - - Assert.AreEqual(@" - - assets/images/resized/0001/1070/11070v1-max-150x150.jpg - assets/images/resized/0001/1070/11070v1-max-150x150.jpg - - - assets/images/resized/0001/1070/11070v1-max-250x250.jpg - assets/images/resized/0001/1070/11070v1-max-250x250.jpg - - - assets/images/resized/0001/1070/11070v1-max-250x250.jpg - -", IndentXml(newXDoc.ToString(SaveOptions.DisableFormatting))); -#endif - - string newJson = JsonConvert.SerializeXmlNode(newDoc, Formatting.Indented); - Console.WriteLine(newJson); - } - - [Test] - public void RoundTripNestedArrays() - { - string json = @"{ - ""available_sizes"": [ - [ - ""assets/images/resized/0001/1070/11070v1-max-150x150.jpg"", - ""assets/images/resized/0001/1070/11070v1-max-150x150.jpg"" - ], - [ - ""assets/images/resized/0001/1070/11070v1-max-250x250.jpg"", - ""assets/images/resized/0001/1070/11070v1-max-250x250.jpg"" - ], - [ - ""assets/images/resized/0001/1070/11070v1-max-250x250.jpg"" - ] - ] -}"; - - XmlDocument newDoc = JsonConvert.DeserializeXmlNode(json, "myRoot", true); - - Assert.AreEqual(@" - - assets/images/resized/0001/1070/11070v1-max-150x150.jpg - assets/images/resized/0001/1070/11070v1-max-150x150.jpg - - - assets/images/resized/0001/1070/11070v1-max-250x250.jpg - assets/images/resized/0001/1070/11070v1-max-250x250.jpg - - - assets/images/resized/0001/1070/11070v1-max-250x250.jpg - -", IndentXml(newDoc.InnerXml)); - -#if !NET20 - XDocument newXDoc = JsonConvert.DeserializeXNode(json, "myRoot", true); - - Console.WriteLine(IndentXml(newXDoc.ToString(SaveOptions.DisableFormatting))); - - Assert.AreEqual(@" - - assets/images/resized/0001/1070/11070v1-max-150x150.jpg - assets/images/resized/0001/1070/11070v1-max-150x150.jpg - - - assets/images/resized/0001/1070/11070v1-max-250x250.jpg - assets/images/resized/0001/1070/11070v1-max-250x250.jpg - - - assets/images/resized/0001/1070/11070v1-max-250x250.jpg - -", IndentXml(newXDoc.ToString(SaveOptions.DisableFormatting))); -#endif - - string newJson = JsonConvert.SerializeXmlNode(newDoc, Formatting.Indented, true); - Assert.AreEqual(json, newJson); - } - - [Test] - public void MultipleNestedArraysToXml() - { - string json = @"{ - ""available_sizes"": [ - [ - [113, 150], - ""assets/images/resized/0001/1070/11070v1-max-150x150.jpg"" - ], - [ - [189, 250], - ""assets/images/resized/0001/1070/11070v1-max-250x250.jpg"" - ], - [ - [341, 450], - ""assets/images/resized/0001/1070/11070v1-max-450x450.jpg"" - ] - ] -}"; - - XmlDocument newDoc = JsonConvert.DeserializeXmlNode(json, "myRoot"); - - Assert.AreEqual(@"113150assets/images/resized/0001/1070/11070v1-max-150x150.jpg189250assets/images/resized/0001/1070/11070v1-max-250x250.jpg341450assets/images/resized/0001/1070/11070v1-max-450x450.jpg", newDoc.InnerXml); - -#if !NET20 - XDocument newXDoc = JsonConvert.DeserializeXNode(json, "myRoot"); - - Assert.AreEqual(@"113150assets/images/resized/0001/1070/11070v1-max-150x150.jpg189250assets/images/resized/0001/1070/11070v1-max-250x250.jpg341450assets/images/resized/0001/1070/11070v1-max-450x450.jpg", newXDoc.ToString(SaveOptions.DisableFormatting)); -#endif - } - - [Test] - public void Encoding() - { - XmlDocument doc = new XmlDocument(); - - doc.LoadXml(@"O""Connor"); // i use "" so it will be easier to see the problem - - string json = SerializeXmlNode(doc); - Assert.AreEqual(@"{ - ""name"": ""O\""Connor"" -}", json); - } - - [Test] - public void SerializeComment() - { - string xml = @" - Text -"; - XmlDocument doc = new XmlDocument(); - doc.LoadXml(xml); - - string jsonText = SerializeXmlNode(doc); - - string expected = @"{ - ""span"": { - ""@class"": ""vevent"", - ""a"": { - ""@class"": ""url"", - ""@href"": ""http://www.web2con.com/"", - ""#text"": ""Text"" - }/* Hi! */ - } -}"; - - Assert.AreEqual(expected, jsonText); - - XmlDocument newDoc = (XmlDocument)DeserializeXmlNode(jsonText); - Assert.AreEqual(@"Text", newDoc.InnerXml); - } - - [Test] - public void SerializeExample() - { - string xml = @" - - - Alan - http://www.google.com - - - Louis - http://www.yahoo.com - - "; - - XmlDocument doc = new XmlDocument(); - doc.LoadXml(xml); - - string jsonText = SerializeXmlNode(doc); - // { - // "?xml": { - // "@version": "1.0", - // "@standalone": "no" - // }, - // "root": { - // "person": [ - // { - // "@id": "1", - // "name": "Alan", - // "url": "http://www.google.com" - // }, - // { - // "@id": "2", - // "name": "Louis", - // "url": "http://www.yahoo.com" - // } - // ] - // } - // } - - // format - jsonText = JObject.Parse(jsonText).ToString(); - - Assert.AreEqual(@"{ - ""?xml"": { - ""@version"": ""1.0"", - ""@standalone"": ""no"" - }, - ""root"": { - ""person"": [ - { - ""@id"": ""1"", - ""name"": ""Alan"", - ""url"": ""http://www.google.com"" - }, - { - ""@id"": ""2"", - ""name"": ""Louis"", - ""url"": ""http://www.yahoo.com"" - } - ] - } -}", jsonText); - - XmlDocument newDoc = (XmlDocument)DeserializeXmlNode(jsonText); - - Assert.AreEqual(doc.InnerXml, newDoc.InnerXml); - } - - [Test] - public void DeserializeExample() - { - string json = @"{ - ""?xml"": { - ""@version"": ""1.0"", - ""@standalone"": ""no"" - }, - ""root"": { - ""person"": [ - { - ""@id"": ""1"", - ""name"": ""Alan"", - ""url"": ""http://www.google.com"" - }, - { - ""@id"": ""2"", - ""name"": ""Louis"", - ""url"": ""http://www.yahoo.com"" - } - ] - } - }"; - - XmlDocument doc = (XmlDocument)DeserializeXmlNode(json); - // - // - // - // Alan - // http://www.google.com - // - // - // Louis - // http://www.yahoo.com - // - // - - Assert.AreEqual(@" - - -Alan -http://www.google.com - - -Louis -http://www.yahoo.com - -".Replace(Environment.NewLine, string.Empty), doc.InnerXml); - } - - [Test] - public void SerializeDeserializeSpecialProperties() - { - PreserveReferencesHandlingTests.CircularDictionary circularDictionary = new PreserveReferencesHandlingTests.CircularDictionary(); - circularDictionary.Add("other", new PreserveReferencesHandlingTests.CircularDictionary { { "blah", null } }); - circularDictionary.Add("self", circularDictionary); - - string json = JsonConvert.SerializeObject(circularDictionary, Formatting.Indented, - new JsonSerializerSettings { PreserveReferencesHandling = PreserveReferencesHandling.All }); - - Assert.AreEqual(@"{ - ""$id"": ""1"", - ""other"": { - ""$id"": ""2"", - ""blah"": null - }, - ""self"": { - ""$ref"": ""1"" - } -}", json); - - XmlNode node = DeserializeXmlNode(json, "root"); - string xml = GetIndentedInnerXml(node); - string expected = @" - - - - - -"; - - Assert.AreEqual(expected, xml); - - string xmlJson = SerializeXmlNode(node); - string expectedXmlJson = @"{ - ""root"": { - ""$id"": ""1"", - ""other"": { - ""$id"": ""2"", - ""blah"": null - }, - ""self"": { - ""$ref"": ""1"" - } - } -}"; - - Assert.AreEqual(expectedXmlJson, xmlJson); - } - - [Test] - [ExpectedException(typeof(JsonSerializationException), ExpectedMessage = "XmlNodeConverter cannot convert JSON with an empty property name to XML.")] - public void EmptyPropertyName() - { - string json = @"{ - ""8452309520V2"": { - """": { - ""CLIENT"": { - ""ID_EXPIRATION_1"": { - ""VALUE"": ""12/12/2000"", - ""DATATYPE"": ""D"", - ""MSG"": ""Missing Identification Exp. Date 1"" - }, - ""ID_ISSUEDATE_1"": { - ""VALUE"": """", - ""DATATYPE"": ""D"", - ""MSG"": ""Missing Identification Issue Date 1"" - } - } - }, - ""457463534534"": { - ""ACCOUNT"": { - ""FUNDING_SOURCE"": { - ""VALUE"": ""FS0"", - ""DATATYPE"": ""L"", - ""MSG"": ""Missing Source of Funds"" - } - } - } - } -}{ - ""34534634535345"": { - """": { - ""CLIENT"": { - ""ID_NUMBER_1"": { - ""VALUE"": """", - ""DATATYPE"": ""S"", - ""MSG"": ""Missing Picture ID"" - }, - ""ID_EXPIRATION_1"": { - ""VALUE"": ""12/12/2000"", - ""DATATYPE"": ""D"", - ""MSG"": ""Missing Picture ID"" - }, - ""WALK_IN"": { - ""VALUE"": """", - ""DATATYPE"": ""L"", - ""MSG"": ""Missing Walk in"" - }, - ""PERSONAL_MEETING"": { - ""VALUE"": ""PM1"", - ""DATATYPE"": ""L"", - ""MSG"": ""Missing Met Client in Person"" - }, - ""ID_ISSUEDATE_1"": { - ""VALUE"": """", - ""DATATYPE"": ""D"", - ""MSG"": ""Missing Picture ID"" - }, - ""PHOTO_ID"": { - ""VALUE"": """", - ""DATATYPE"": ""L"", - ""MSG"": ""Missing Picture ID"" - }, - ""ID_TYPE_1"": { - ""VALUE"": """", - ""DATATYPE"": ""L"", - ""MSG"": ""Missing Picture ID"" - } - } - }, - ""45635624523"": { - ""ACCOUNT"": { - ""FUNDING_SOURCE"": { - ""VALUE"": ""FS1"", - ""DATATYPE"": ""L"", - ""MSG"": ""Missing Source of Funds"" - } - } - } - } -}"; - - DeserializeXmlNode(json); - } - - [Test] - public void SingleItemArrayPropertySerialization() - { - Product product = new Product(); - - product.Name = "Apple"; - product.ExpiryDate = new DateTime(2008, 12, 28, 0, 0, 0, DateTimeKind.Utc); - product.Price = 3.99M; - product.Sizes = new string[] { "Small" }; - - string output = JsonConvert.SerializeObject(product, new IsoDateTimeConverter()); - - XmlDocument xmlProduct = JsonConvert.DeserializeXmlNode(output, "product", true); - - Assert.AreEqual(@" - Apple - 2008-12-28T00:00:00Z - 3.99 - Small -", IndentXml(xmlProduct.InnerXml)); - - string output2 = JsonConvert.SerializeXmlNode(xmlProduct.DocumentElement, Formatting.Indented); - - Assert.AreEqual(@"{ - ""product"": { - ""Name"": ""Apple"", - ""ExpiryDate"": ""2008-12-28T00:00:00Z"", - ""Price"": ""3.99"", - ""Sizes"": [ - ""Small"" - ] - } -}", output2); - } - - public class TestComplexArrayClass - { - public string Name { get; set; } - public IList Products { get; set; } - } - - [Test] - public void ComplexSingleItemArrayPropertySerialization() - { - TestComplexArrayClass o = new TestComplexArrayClass - { - Name = "Hi", - Products = new List - { - new Product { Name = "First" } - } - }; - - string output = JsonConvert.SerializeObject(o, new IsoDateTimeConverter()); - - XmlDocument xmlProduct = JsonConvert.DeserializeXmlNode(output, "test", true); - - Assert.AreEqual(@" - Hi - - First - 2000-01-01T00:00:00Z - 0 - - -", IndentXml(xmlProduct.InnerXml)); - - string output2 = JsonConvert.SerializeXmlNode(xmlProduct.DocumentElement, Formatting.Indented, true); - - Assert.AreEqual(@"{ - ""Name"": ""Hi"", - ""Products"": [ - { - ""Name"": ""First"", - ""ExpiryDate"": ""2000-01-01T00:00:00Z"", - ""Price"": ""0"", - ""Sizes"": null - } - ] -}", output2); - } - - private string IndentXml(string xml) - { - XmlReader reader = XmlReader.Create(new StringReader(xml)); - - StringWriter sw = new StringWriter(); - XmlWriter writer = XmlWriter.Create(sw, new XmlWriterSettings { Indent = true, OmitXmlDeclaration = true }); - - while (reader.Read()) - { - writer.WriteNode(reader, false); - } - - writer.Flush(); - - return sw.ToString(); - } - - [Test] - public void OmitRootObject() - { - string xml = @" - Hi - Hi - - First - 2000-01-01T00:00:00Z - 0 - - -"; - - XmlDocument d = new XmlDocument(); - d.LoadXml(xml); - - string output = JsonConvert.SerializeXmlNode(d, Formatting.Indented, true); - - Assert.AreEqual(@"{ - ""Name"": [ - ""Hi"", - ""Hi"" - ], - ""Products"": [ - { - ""Name"": ""First"", - ""ExpiryDate"": ""2000-01-01T00:00:00Z"", - ""Price"": ""0"", - ""Sizes"": null - } - ] -}", output); - } - - [Test] - public void EmtpyElementWithArrayAttributeShouldWriteAttributes() - { - string xml = @" - - - - - - -"; - - XmlDocument d = new XmlDocument(); - d.LoadXml(xml); - - string json = JsonConvert.SerializeXmlNode(d, Formatting.Indented); - - Assert.AreEqual(@"{ - ""?xml"": { - ""@version"": ""1.0"", - ""@encoding"": ""utf-8"" - }, - ""root"": { - ""A"": { - ""B"": [ - { - ""@name"": ""sample"" - } - ], - ""C"": [ - null, - null - ] - } - } -}", json); - } - - [Test] - public void DeserializeNonInt64IntegerValues() - { - var dict = new Dictionary { { "Int16", (short)1 }, { "Float", 2f }, { "Int32", 3 } }; - var obj = JObject.FromObject(dict); - var serializer = JsonSerializer.Create(new JsonSerializerSettings { Converters = { new XmlNodeConverter() { DeserializeRootElementName = "root" } } }); - using (var reader = obj.CreateReader()) - { - var value = (XmlDocument)serializer.Deserialize(reader, typeof(XmlDocument)); - - Assert.AreEqual(@"123", value.InnerXml); - } - } - - [Test] - public void DeserializingBooleanValues() - { - MemoryStream ms = new MemoryStream(System.Text.Encoding.UTF8.GetBytes(@"{root:{""@booleanType"":true}}")); - MemoryStream xml = new MemoryStream(); - - JsonBodyToSoapXml(ms, xml); - - string xmlString = System.Text.Encoding.UTF8.GetString(xml.ToArray()); - - Assert.AreEqual(@"", xmlString); - } - - private static void JsonBodyToSoapXml(Stream json, Stream xml) - { - Newtonsoft.Json.JsonSerializerSettings settings = new Newtonsoft.Json.JsonSerializerSettings(); - settings.Converters.Add(new Newtonsoft.Json.Converters.XmlNodeConverter()); - Newtonsoft.Json.JsonSerializer serializer = Newtonsoft.Json.JsonSerializer.Create(settings); - using (Newtonsoft.Json.JsonTextReader reader = new Newtonsoft.Json.JsonTextReader(new System.IO.StreamReader(json))) - { - XmlDocument doc = (XmlDocument)serializer.Deserialize(reader, typeof(XmlDocument)); - if (reader.Read() && reader.TokenType != JsonToken.Comment) - throw new JsonSerializationException("Additional text found in JSON string after finishing deserializing object."); - using (XmlWriter writer = XmlWriter.Create(xml)) - { - doc.Save(writer); - } - } - } - -#if !NET20 - [Test] - public void DeserializeXNodeDefaultNamespace() - { - string xaml = @" - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -"; - - string json = JsonConvert.SerializeXNode(XDocument.Parse(xaml), Formatting.Indented); - - string expectedJson = @"{ - ""Grid"": { - ""@xmlns"": ""http://schemas.microsoft.com/winfx/2006/xaml/presentation"", - ""@xmlns:x"": ""http://schemas.microsoft.com/winfx/2006/xaml"", - ""@xmlns:toolkit"": ""clr-namespace:Microsoft.Phone.Controls;assembly=Microsoft.Phone.Controls.Toolkit"", - ""@Style"": ""{StaticResource trimFormGrid}"", - ""@x:Name"": ""TrimObjectForm"", - ""Grid.ColumnDefinitions"": { - ""ColumnDefinition"": [ - { - ""@Width"": ""63*"" - }, - { - ""@Width"": ""320*"" - } - ] - }, - ""Grid.RowDefinitions"": { - ""@xmlns"": """", - ""RowDefinition"": [ - null, - null, - null, - null, - null, - null, - null, - null - ] - }, - ""TextBox"": [ - { - ""@Style"": ""{StaticResource trimFormGrid_TB}"", - ""@Text"": ""{Binding TypedTitle, Converter={StaticResource trimPropertyConverter}}"", - ""@Name"": ""RecordTypedTitle"", - ""@Grid.Column"": ""1"", - ""@Grid.Row"": ""0"", - ""@xmlns"": """" - }, - { - ""@Style"": ""{StaticResource trimFormGrid_TB}"", - ""@Text"": ""{Binding ExternalReference, Converter={StaticResource trimPropertyConverter}}"", - ""@Name"": ""RecordExternalReference"", - ""@Grid.Column"": ""1"", - ""@Grid.Row"": ""1"", - ""@xmlns"": """" - }, - { - ""@Style"": ""{StaticResource trimFormGrid_TB}"", - ""@Text"": ""{Binding Author, Converter={StaticResource trimPropertyConverter}}"", - ""@Name"": ""RecordAuthor"", - ""@Grid.Column"": ""1"", - ""@Grid.Row"": ""4"", - ""@xmlns"": """" - }, - { - ""@Style"": ""{StaticResource trimFormGrid_TB}"", - ""@Text"": ""{Binding Container, Converter={StaticResource trimPropertyConverter}}"", - ""@Name"": ""RecordContainer"", - ""@Grid.Column"": ""1"", - ""@Grid.Row"": ""5"", - ""@xmlns"": """" - }, - { - ""@Style"": ""{StaticResource trimFormGrid_TB}"", - ""@Text"": ""{Binding IsEnclosed, Converter={StaticResource trimPropertyConverter}}"", - ""@Name"": ""RecordIsEnclosed"", - ""@Grid.Column"": ""1"", - ""@Grid.Row"": ""6"", - ""@xmlns"": """" - }, - { - ""@Style"": ""{StaticResource trimFormGrid_TB}"", - ""@Text"": ""{Binding Assignee, Converter={StaticResource trimPropertyConverter}}"", - ""@Name"": ""RecordAssignee"", - ""@Grid.Column"": ""1"", - ""@Grid.Row"": ""7"", - ""@xmlns"": """" - } - ], - ""toolkit:DatePicker"": [ - { - ""@Style"": ""{StaticResource trimFormGrid_DP}"", - ""@Value"": ""{Binding DateCreated, Converter={StaticResource trimPropertyConverter}}"", - ""@Name"": ""RecordDateCreated"", - ""@Grid.Column"": ""1"", - ""@Grid.Row"": ""2"" - }, - { - ""@Style"": ""{StaticResource trimFormGrid_DP}"", - ""@Value"": ""{Binding DateDue, Converter={StaticResource trimPropertyConverter}}"", - ""@Name"": ""RecordDateDue"", - ""@Grid.Column"": ""1"", - ""@Grid.Row"": ""3"" - } - ], - ""TextBlock"": [ - { - ""@Grid.Column"": ""0"", - ""@Text"": ""Title (Free Text Part)"", - ""@Style"": ""{StaticResource trimFormGrid_LBL}"", - ""@Grid.Row"": ""0"", - ""@xmlns"": """" - }, - { - ""@Grid.Column"": ""0"", - ""@Text"": ""External ID"", - ""@Style"": ""{StaticResource trimFormGrid_LBL}"", - ""@Grid.Row"": ""1"", - ""@xmlns"": """" - }, - { - ""@Grid.Column"": ""0"", - ""@Text"": ""Date Created"", - ""@Style"": ""{StaticResource trimFormGrid_LBL}"", - ""@Grid.Row"": ""2"", - ""@xmlns"": """" - }, - { - ""@Grid.Column"": ""0"", - ""@Text"": ""Date Due"", - ""@Style"": ""{StaticResource trimFormGrid_LBL}"", - ""@Grid.Row"": ""3"", - ""@xmlns"": """" - }, - { - ""@Grid.Column"": ""0"", - ""@Text"": ""Author"", - ""@Style"": ""{StaticResource trimFormGrid_LBL}"", - ""@Grid.Row"": ""4"", - ""@xmlns"": """" - }, - { - ""@Grid.Column"": ""0"", - ""@Text"": ""Container"", - ""@Style"": ""{StaticResource trimFormGrid_LBL}"", - ""@Grid.Row"": ""5"", - ""@xmlns"": """" - }, - { - ""@Grid.Column"": ""0"", - ""@Text"": ""Enclosed?"", - ""@Style"": ""{StaticResource trimFormGrid_LBL}"", - ""@Grid.Row"": ""6"", - ""@xmlns"": """" - }, - { - ""@Grid.Column"": ""0"", - ""@Text"": ""Assignee"", - ""@Style"": ""{StaticResource trimFormGrid_LBL}"", - ""@Grid.Row"": ""7"", - ""@xmlns"": """" - } - ] - } -}"; - - Assert.AreEqual(expectedJson, json); - - XNode node = JsonConvert.DeserializeXNode(json); - - string xaml2 = node.ToString(); - - string expectedXaml = @" - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -"; - - Assert.AreEqual(expectedXaml, xaml2); - } -#endif - - [Test] - public void DeserializeXmlNodeDefaultNamespace() - { - string xaml = @" - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -"; - - XmlDocument document = new XmlDocument(); - document.LoadXml(xaml); - - string json = JsonConvert.SerializeXmlNode(document, Formatting.Indented); - - string expectedJson = @"{ - ""Grid"": { - ""@xmlns"": ""http://schemas.microsoft.com/winfx/2006/xaml/presentation"", - ""@xmlns:x"": ""http://schemas.microsoft.com/winfx/2006/xaml"", - ""@xmlns:toolkit"": ""clr-namespace:Microsoft.Phone.Controls;assembly=Microsoft.Phone.Controls.Toolkit"", - ""@Style"": ""{StaticResource trimFormGrid}"", - ""@x:Name"": ""TrimObjectForm"", - ""Grid.ColumnDefinitions"": { - ""ColumnDefinition"": [ - { - ""@Width"": ""63*"" - }, - { - ""@Width"": ""320*"" - } - ] - }, - ""Grid.RowDefinitions"": { - ""@xmlns"": """", - ""RowDefinition"": [ - null, - null, - null, - null, - null, - null, - null, - null - ] - }, - ""TextBox"": [ - { - ""@Style"": ""{StaticResource trimFormGrid_TB}"", - ""@Text"": ""{Binding TypedTitle, Converter={StaticResource trimPropertyConverter}}"", - ""@Name"": ""RecordTypedTitle"", - ""@Grid.Column"": ""1"", - ""@Grid.Row"": ""0"", - ""@xmlns"": """" - }, - { - ""@Style"": ""{StaticResource trimFormGrid_TB}"", - ""@Text"": ""{Binding ExternalReference, Converter={StaticResource trimPropertyConverter}}"", - ""@Name"": ""RecordExternalReference"", - ""@Grid.Column"": ""1"", - ""@Grid.Row"": ""1"", - ""@xmlns"": """" - }, - { - ""@Style"": ""{StaticResource trimFormGrid_TB}"", - ""@Text"": ""{Binding Author, Converter={StaticResource trimPropertyConverter}}"", - ""@Name"": ""RecordAuthor"", - ""@Grid.Column"": ""1"", - ""@Grid.Row"": ""4"", - ""@xmlns"": """" - }, - { - ""@Style"": ""{StaticResource trimFormGrid_TB}"", - ""@Text"": ""{Binding Container, Converter={StaticResource trimPropertyConverter}}"", - ""@Name"": ""RecordContainer"", - ""@Grid.Column"": ""1"", - ""@Grid.Row"": ""5"", - ""@xmlns"": """" - }, - { - ""@Style"": ""{StaticResource trimFormGrid_TB}"", - ""@Text"": ""{Binding IsEnclosed, Converter={StaticResource trimPropertyConverter}}"", - ""@Name"": ""RecordIsEnclosed"", - ""@Grid.Column"": ""1"", - ""@Grid.Row"": ""6"", - ""@xmlns"": """" - }, - { - ""@Style"": ""{StaticResource trimFormGrid_TB}"", - ""@Text"": ""{Binding Assignee, Converter={StaticResource trimPropertyConverter}}"", - ""@Name"": ""RecordAssignee"", - ""@Grid.Column"": ""1"", - ""@Grid.Row"": ""7"", - ""@xmlns"": """" - } - ], - ""toolkit:DatePicker"": [ - { - ""@Style"": ""{StaticResource trimFormGrid_DP}"", - ""@Value"": ""{Binding DateCreated, Converter={StaticResource trimPropertyConverter}}"", - ""@Name"": ""RecordDateCreated"", - ""@Grid.Column"": ""1"", - ""@Grid.Row"": ""2"" - }, - { - ""@Style"": ""{StaticResource trimFormGrid_DP}"", - ""@Value"": ""{Binding DateDue, Converter={StaticResource trimPropertyConverter}}"", - ""@Name"": ""RecordDateDue"", - ""@Grid.Column"": ""1"", - ""@Grid.Row"": ""3"" - } - ], - ""TextBlock"": [ - { - ""@Grid.Column"": ""0"", - ""@Text"": ""Title (Free Text Part)"", - ""@Style"": ""{StaticResource trimFormGrid_LBL}"", - ""@Grid.Row"": ""0"", - ""@xmlns"": """" - }, - { - ""@Grid.Column"": ""0"", - ""@Text"": ""External ID"", - ""@Style"": ""{StaticResource trimFormGrid_LBL}"", - ""@Grid.Row"": ""1"", - ""@xmlns"": """" - }, - { - ""@Grid.Column"": ""0"", - ""@Text"": ""Date Created"", - ""@Style"": ""{StaticResource trimFormGrid_LBL}"", - ""@Grid.Row"": ""2"", - ""@xmlns"": """" - }, - { - ""@Grid.Column"": ""0"", - ""@Text"": ""Date Due"", - ""@Style"": ""{StaticResource trimFormGrid_LBL}"", - ""@Grid.Row"": ""3"", - ""@xmlns"": """" - }, - { - ""@Grid.Column"": ""0"", - ""@Text"": ""Author"", - ""@Style"": ""{StaticResource trimFormGrid_LBL}"", - ""@Grid.Row"": ""4"", - ""@xmlns"": """" - }, - { - ""@Grid.Column"": ""0"", - ""@Text"": ""Container"", - ""@Style"": ""{StaticResource trimFormGrid_LBL}"", - ""@Grid.Row"": ""5"", - ""@xmlns"": """" - }, - { - ""@Grid.Column"": ""0"", - ""@Text"": ""Enclosed?"", - ""@Style"": ""{StaticResource trimFormGrid_LBL}"", - ""@Grid.Row"": ""6"", - ""@xmlns"": """" - }, - { - ""@Grid.Column"": ""0"", - ""@Text"": ""Assignee"", - ""@Style"": ""{StaticResource trimFormGrid_LBL}"", - ""@Grid.Row"": ""7"", - ""@xmlns"": """" - } - ] - } -}"; - - Assert.AreEqual(expectedJson, json); - - XmlNode node = JsonConvert.DeserializeXmlNode(json); - - StringWriter sw = new StringWriter(); - XmlWriter writer = XmlWriter.Create(sw, new XmlWriterSettings - { - Indent = true, - OmitXmlDeclaration = true - }); - node.WriteTo(writer); - writer.Flush(); - - string xaml2 = sw.ToString(); - - string expectedXaml = @" - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -"; - - Assert.AreEqual(expectedXaml, xaml2); - } - } -} +#region License +// Copyright (c) 2007 James Newton-King +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +#endregion + +#if !SILVERLIGHT && !NETFX_CORE +using System; +using System.Collections.Generic; +using Newtonsoft.Json.Tests.Serialization; +using Newtonsoft.Json.Tests.TestObjects; +#if !NETFX_CORE +using NUnit.Framework; +#else +using Microsoft.VisualStudio.TestTools.UnitTesting; +using TestFixture = Microsoft.VisualStudio.TestTools.UnitTesting.TestClassAttribute; +using Test = Microsoft.VisualStudio.TestTools.UnitTesting.TestMethodAttribute; +#endif +using Newtonsoft.Json; +using System.IO; +using System.Xml; +using Newtonsoft.Json.Converters; +using Newtonsoft.Json.Utilities; +using Newtonsoft.Json.Linq; +#if !NET20 +using System.Xml.Linq; +#endif + +namespace Newtonsoft.Json.Tests.Converters +{ + [TestFixture] + public class XmlNodeConverterTest : TestFixtureBase + { + private string SerializeXmlNode(XmlNode node) + { + string json = JsonConvert.SerializeXmlNode(node, Formatting.Indented); + XmlNodeReader reader = new XmlNodeReader(node); + +#if !NET20 + XObject xNode; + if (node is XmlDocument) + { + xNode = XDocument.Load(reader); + } + else if (node is XmlAttribute) + { + XmlAttribute attribute = (XmlAttribute) node; + xNode = new XAttribute(XName.Get(attribute.LocalName, attribute.NamespaceURI), attribute.Value); + } + else + { + reader.MoveToContent(); + xNode = XNode.ReadFrom(reader); + } + + string linqJson = JsonConvert.SerializeXNode(xNode, Formatting.Indented); + + Assert.AreEqual(json, linqJson); +#endif + + return json; + } + + private XmlNode DeserializeXmlNode(string json) + { + return DeserializeXmlNode(json, null); + } + + private XmlNode DeserializeXmlNode(string json, string deserializeRootElementName) + { + JsonTextReader reader; + + reader = new JsonTextReader(new StringReader(json)); + reader.Read(); + XmlNodeConverter converter = new XmlNodeConverter(); + if (deserializeRootElementName != null) + converter.DeserializeRootElementName = deserializeRootElementName; + + XmlNode node = (XmlNode)converter.ReadJson(reader, typeof (XmlDocument), null, new JsonSerializer()); + +#if !NET20 + string xmlText = node.OuterXml; + + reader = new JsonTextReader(new StringReader(json)); + reader.Read(); + XDocument d = (XDocument) converter.ReadJson(reader, typeof (XDocument), null, new JsonSerializer()); + + string linqXmlText = d.ToString(SaveOptions.DisableFormatting); + if (d.Declaration != null) + linqXmlText = d.Declaration + linqXmlText; + + Assert.AreEqual(xmlText, linqXmlText); +#endif + + return node; + } + + [Test] + public void DocumentSerializeIndented() + { + string xml = @" + + + + Web 2.0 Conference + October 5 + 7 + Argent Hotel, San Francisco, CA + +"; + XmlDocument doc = new XmlDocument(); + doc.LoadXml(xml); + + string jsonText = SerializeXmlNode(doc); + string expected = @"{ + ""?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"" + } + ] + } + } +}"; + + Assert.AreEqual(expected, jsonText); + + Console.WriteLine("DocumentSerializeIndented"); + Console.WriteLine(jsonText); + Console.WriteLine(); + } + + [Test] + public void SerializeNodeTypes() + { + XmlDocument doc = new XmlDocument(); + string jsonText; + + Console.WriteLine("SerializeNodeTypes"); + + string xml = @" + + + +"; + + XmlDocument document = new XmlDocument(); + document.LoadXml(xml); + + // XmlAttribute + XmlAttribute attribute = document.DocumentElement.ChildNodes[0].Attributes["IsDataSet", "urn:schemas-microsoft-com:xml-msdata"]; + attribute.Value = "true"; + + jsonText = JsonConvert.SerializeXmlNode(attribute); + + Console.WriteLine(jsonText); + Assert.AreEqual(@"{""@msdata:IsDataSet"":""true""}", jsonText); + +#if !NET20 + XDocument d = XDocument.Parse(xml); + XAttribute a = d.Root.Element("{http://www.w3.org/2001/XMLSchema}element").Attribute("{urn:schemas-microsoft-com:xml-msdata}IsDataSet"); + + jsonText = JsonConvert.SerializeXNode(a); + + Assert.AreEqual(@"{""@msdata:IsDataSet"":""true""}", jsonText); +#endif + + // XmlProcessingInstruction + XmlProcessingInstruction instruction = doc.CreateProcessingInstruction("xml-stylesheet", @"href=""classic.xsl"" type=""text/xml"""); + + jsonText = JsonConvert.SerializeXmlNode(instruction); + + Console.WriteLine(jsonText); + Assert.AreEqual(@"{""?xml-stylesheet"":""href=\""classic.xsl\"" type=\""text/xml\""""}", jsonText); + + + // XmlProcessingInstruction + XmlCDataSection cDataSection = doc.CreateCDataSection("true"); + + jsonText = JsonConvert.SerializeXmlNode(cDataSection); + + Console.WriteLine(jsonText); + Assert.AreEqual(@"{""#cdata-section"":""true""}", jsonText); + + + // XmlElement + XmlElement element = doc.CreateElement("xs", "Choice", "http://www.w3.org/2001/XMLSchema"); + element.SetAttributeNode(doc.CreateAttribute("msdata", "IsDataSet", "urn:schemas-microsoft-com:xml-msdata")); + + XmlAttribute aa = doc.CreateAttribute(@"xmlns", "xs", "http://www.w3.org/2000/xmlns/"); + aa.Value = "http://www.w3.org/2001/XMLSchema"; + element.SetAttributeNode(aa); + + aa = doc.CreateAttribute(@"xmlns", "msdata", "http://www.w3.org/2000/xmlns/"); + aa.Value = "urn:schemas-microsoft-com:xml-msdata"; + element.SetAttributeNode(aa); + + element.AppendChild(instruction); + element.AppendChild(cDataSection); + + doc.AppendChild(element); + + jsonText = JsonConvert.SerializeXmlNode(element, Formatting.Indented); + + Assert.AreEqual(@"{ + ""xs:Choice"": { + ""@msdata:IsDataSet"": """", + ""@xmlns:xs"": ""http://www.w3.org/2001/XMLSchema"", + ""@xmlns:msdata"": ""urn:schemas-microsoft-com:xml-msdata"", + ""?xml-stylesheet"": ""href=\""classic.xsl\"" type=\""text/xml\"""", + ""#cdata-section"": ""true"" + } +}", jsonText); + } + + [Test] + public void DocumentFragmentSerialize() + { + XmlDocument doc = new XmlDocument(); + + XmlDocumentFragment fragement = doc.CreateDocumentFragment(); + + fragement.InnerXml = "widgetwidget"; + + string jsonText = JsonConvert.SerializeXmlNode(fragement); + + string expected = @"{""Item"":[""widget"",""widget""]}"; + + Assert.AreEqual(expected, jsonText); + + Console.WriteLine("DocumentFragmentSerialize"); + Console.WriteLine(jsonText); + Console.WriteLine(); + } + + [Test] + public void NamespaceSerializeDeserialize() + { + string xml = @" + + + + + + + + + + + + + + + + +"; + + XmlDocument doc = new XmlDocument(); + doc.LoadXml(xml); + + string jsonText = SerializeXmlNode(doc); + + string expected = @"{ + ""?xml"": { + ""@version"": ""1.0"", + ""@encoding"": ""utf-8"" + }, + ""xs:schema"": { + ""@xs:id"": ""SomeID"", + ""@xmlns"": """", + ""@xmlns:xs"": ""http://www.w3.org/2001/XMLSchema"", + ""@xmlns:msdata"": ""urn:schemas-microsoft-com:xml-msdata"", + ""xs:element"": { + ""@name"": ""MyDataSet"", + ""@msdata:IsDataSet"": ""true"", + ""xs:complexType"": { + ""xs:choice"": { + ""@maxOccurs"": ""unbounded"", + ""xs:element"": { + ""@name"": ""customers"", + ""xs:complexType"": { + ""xs:sequence"": { + ""xs:element"": [ + { + ""@name"": ""CustomerID"", + ""@type"": ""xs:integer"", + ""@minOccurs"": ""0"" + }, + { + ""@name"": ""CompanyName"", + ""@type"": ""xs:string"", + ""@minOccurs"": ""0"" + }, + { + ""@name"": ""Phone"", + ""@type"": ""xs:string"" + } + ] + } + } + } + } + } + } + } +}"; + + Assert.AreEqual(expected, jsonText); + + XmlDocument deserializedDoc = (XmlDocument)DeserializeXmlNode(jsonText); + + Assert.AreEqual(doc.InnerXml, deserializedDoc.InnerXml); + + Console.WriteLine("NamespaceSerializeDeserialize"); + Console.WriteLine(jsonText); + Console.WriteLine(deserializedDoc.InnerXml); + Console.WriteLine(); + } + + [Test] + public void DocumentDeserialize() + { + string jsonText = @"{ + ""?xml"": { + ""@version"": ""1.0"", + ""@standalone"": ""no"" + }, + ""span"": { + ""@class"": ""vevent"", + ""a"": { + ""@class"": ""url"", + ""span"": { + ""@class"": ""summary"", + ""#text"": ""Web 2.0 Conference"", + ""#cdata-section"": ""my escaped text"" + }, + ""@href"": ""http://www.web2con.com/"" + } + } +}"; + + XmlDocument doc = (XmlDocument)DeserializeXmlNode(jsonText); + + string expected = @" + + + Web 2.0 Conference + +"; + + string formattedXml = GetIndentedInnerXml(doc); + + Console.WriteLine("DocumentDeserialize"); + Console.WriteLine(formattedXml); + Console.WriteLine(); + + Assert.AreEqual(expected, formattedXml); + } + + private string GetIndentedInnerXml(XmlNode node) + { + XmlWriterSettings settings = new XmlWriterSettings(); + settings.Indent = true; + + StringWriter sw = new StringWriter(); + + using (XmlWriter writer = XmlWriter.Create(sw, settings)) + { + node.WriteTo(writer); + } + + return sw.ToString(); + } + + [Test] + public void SingleTextNode() + { + string xml = @" + + + Alan + http://www.google.com + + + Louis + http://www.yahoo.com + + "; + + XmlDocument doc = new XmlDocument(); + doc.LoadXml(xml); + + string jsonText = SerializeXmlNode(doc); + + XmlDocument newDoc = (XmlDocument)DeserializeXmlNode(jsonText); + + Assert.AreEqual(doc.InnerXml, newDoc.InnerXml); + } + + [Test] + public void EmptyNode() + { + string xml = @" + + + Alan + + + + Louis + http://www.yahoo.com + + "; + + XmlDocument doc = new XmlDocument(); + doc.LoadXml(xml); + + string jsonText = SerializeXmlNode(doc); + + Console.WriteLine(jsonText); + + XmlDocument newDoc = (XmlDocument)DeserializeXmlNode(jsonText); + + Assert.AreEqual(doc.InnerXml, newDoc.InnerXml); + } + + [Test] + public void OtherElementDataTypes() + { + string jsonText = @"{""?xml"":{""@version"":""1.0"",""@standalone"":""no""},""root"":{""person"":[{""@id"":""1"",""Float"":2.5,""Integer"":99},{""Boolean"":true,""@id"":""2"",""date"":""\/Date(954374400000)\/""}]}}"; + + XmlDocument newDoc = (XmlDocument)DeserializeXmlNode(jsonText); + + string expected = @"2.599true2000-03-30T00:00:00Z"; + + Assert.AreEqual(expected, newDoc.InnerXml); + } + + [Test] + [ExpectedException(typeof(JsonSerializationException), ExpectedMessage = "XmlNodeConverter can only convert JSON that begins with an object.")] + public void NoRootObject() + { + XmlDocument newDoc = (XmlDocument)JsonConvert.DeserializeXmlNode(@"[1]"); + } + + [Test] + [ExpectedException(typeof(JsonSerializationException), ExpectedMessage = "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.")] + public void RootObjectMultipleProperties() + { + XmlDocument newDoc = (XmlDocument)JsonConvert.DeserializeXmlNode(@"{Prop1:1,Prop2:2}"); + } + + [Test] + public void JavaScriptConstructor() + { + string jsonText = @"{root:{r:new Date(34343, 55)}}"; + + XmlDocument newDoc = (XmlDocument)DeserializeXmlNode(jsonText); + + string expected = @"3434355"; + + Assert.AreEqual(expected, newDoc.InnerXml); + + string json = SerializeXmlNode(newDoc); + expected = @"{ + ""root"": { + ""r"": { + ""Date"": [ + ""34343"", + ""55"" + ] + } + } +}"; + + Assert.AreEqual(expected, json); + } + + [Test] + public void ForceJsonArray() + { + string arrayXml = @" + + Alan + http://www.google.com + Admin + + "; + + XmlDocument arrayDoc = new XmlDocument(); + arrayDoc.LoadXml(arrayXml); + + string arrayJsonText = SerializeXmlNode(arrayDoc); + string expected = @"{ + ""root"": { + ""person"": { + ""@id"": ""1"", + ""name"": ""Alan"", + ""url"": ""http://www.google.com"", + ""role"": [ + ""Admin"" + ] + } + } +}"; + Assert.AreEqual(expected, arrayJsonText); + + arrayXml = @" + + Alan + http://www.google.com + Admin1 + Admin2 + + "; + + arrayDoc = new XmlDocument(); + arrayDoc.LoadXml(arrayXml); + + arrayJsonText = SerializeXmlNode(arrayDoc); + expected = @"{ + ""root"": { + ""person"": { + ""@id"": ""1"", + ""name"": ""Alan"", + ""url"": ""http://www.google.com"", + ""role"": [ + ""Admin1"", + ""Admin2"" + ] + } + } +}"; + Assert.AreEqual(expected, arrayJsonText); + + arrayXml = @" + + Alan + http://www.google.com + Admin1 + + "; + + arrayDoc = new XmlDocument(); + arrayDoc.LoadXml(arrayXml); + + arrayJsonText = SerializeXmlNode(arrayDoc); + expected = @"{ + ""root"": { + ""person"": { + ""@id"": ""1"", + ""name"": ""Alan"", + ""url"": ""http://www.google.com"", + ""role"": ""Admin1"" + } + } +}"; + Assert.AreEqual(expected, arrayJsonText); + } + + [Test] + [ExpectedException(typeof(JsonSerializationException), ExpectedMessage = "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.")] + public void MultipleRootPropertiesXmlDocument() + { + string json = @"{""count"": 773840,""photos"": null}"; + + JsonConvert.DeserializeXmlNode(json); + } + +#if !NET20 + [Test] + [ExpectedException(typeof(JsonSerializationException), ExpectedMessage = "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.")] + public void MultipleRootPropertiesXDocument() + { + string json = @"{""count"": 773840,""photos"": null}"; + + JsonConvert.DeserializeXNode(json); + } +#endif + + [Test] + public void MultipleRootPropertiesAddRootElement() + { + string json = @"{""count"": 773840,""photos"": 773840}"; + + XmlDocument newDoc = JsonConvert.DeserializeXmlNode(json, "myRoot"); + + Assert.AreEqual(@"773840773840", newDoc.InnerXml); + +#if !NET20 + XDocument newXDoc = JsonConvert.DeserializeXNode(json, "myRoot"); + + Assert.AreEqual(@"773840773840", newXDoc.ToString(SaveOptions.DisableFormatting)); +#endif + } + + [Test] + public void NestedArrays() + { + string json = @"{ + ""available_sizes"": [ + [ + ""assets/images/resized/0001/1070/11070v1-max-150x150.jpg"", + ""assets/images/resized/0001/1070/11070v1-max-150x150.jpg"" + ], + [ + ""assets/images/resized/0001/1070/11070v1-max-250x250.jpg"", + ""assets/images/resized/0001/1070/11070v1-max-250x250.jpg"" + ], + [ + ""assets/images/resized/0001/1070/11070v1-max-250x250.jpg"" + ] + ] +}"; + + XmlDocument newDoc = JsonConvert.DeserializeXmlNode(json, "myRoot"); + + string xml = IndentXml(newDoc.InnerXml); + + Assert.AreEqual(@" + + assets/images/resized/0001/1070/11070v1-max-150x150.jpg + assets/images/resized/0001/1070/11070v1-max-150x150.jpg + + + assets/images/resized/0001/1070/11070v1-max-250x250.jpg + assets/images/resized/0001/1070/11070v1-max-250x250.jpg + + + assets/images/resized/0001/1070/11070v1-max-250x250.jpg + +", IndentXml(newDoc.InnerXml)); + +#if !NET20 + XDocument newXDoc = JsonConvert.DeserializeXNode(json, "myRoot"); + + Assert.AreEqual(@" + + assets/images/resized/0001/1070/11070v1-max-150x150.jpg + assets/images/resized/0001/1070/11070v1-max-150x150.jpg + + + assets/images/resized/0001/1070/11070v1-max-250x250.jpg + assets/images/resized/0001/1070/11070v1-max-250x250.jpg + + + assets/images/resized/0001/1070/11070v1-max-250x250.jpg + +", IndentXml(newXDoc.ToString(SaveOptions.DisableFormatting))); +#endif + + string newJson = JsonConvert.SerializeXmlNode(newDoc, Formatting.Indented); + Console.WriteLine(newJson); + } + + [Test] + public void RoundTripNestedArrays() + { + string json = @"{ + ""available_sizes"": [ + [ + ""assets/images/resized/0001/1070/11070v1-max-150x150.jpg"", + ""assets/images/resized/0001/1070/11070v1-max-150x150.jpg"" + ], + [ + ""assets/images/resized/0001/1070/11070v1-max-250x250.jpg"", + ""assets/images/resized/0001/1070/11070v1-max-250x250.jpg"" + ], + [ + ""assets/images/resized/0001/1070/11070v1-max-250x250.jpg"" + ] + ] +}"; + + XmlDocument newDoc = JsonConvert.DeserializeXmlNode(json, "myRoot", true); + + Assert.AreEqual(@" + + assets/images/resized/0001/1070/11070v1-max-150x150.jpg + assets/images/resized/0001/1070/11070v1-max-150x150.jpg + + + assets/images/resized/0001/1070/11070v1-max-250x250.jpg + assets/images/resized/0001/1070/11070v1-max-250x250.jpg + + + assets/images/resized/0001/1070/11070v1-max-250x250.jpg + +", IndentXml(newDoc.InnerXml)); + +#if !NET20 + XDocument newXDoc = JsonConvert.DeserializeXNode(json, "myRoot", true); + + Console.WriteLine(IndentXml(newXDoc.ToString(SaveOptions.DisableFormatting))); + + Assert.AreEqual(@" + + assets/images/resized/0001/1070/11070v1-max-150x150.jpg + assets/images/resized/0001/1070/11070v1-max-150x150.jpg + + + assets/images/resized/0001/1070/11070v1-max-250x250.jpg + assets/images/resized/0001/1070/11070v1-max-250x250.jpg + + + assets/images/resized/0001/1070/11070v1-max-250x250.jpg + +", IndentXml(newXDoc.ToString(SaveOptions.DisableFormatting))); +#endif + + string newJson = JsonConvert.SerializeXmlNode(newDoc, Formatting.Indented, true); + Assert.AreEqual(json, newJson); + } + + [Test] + public void MultipleNestedArraysToXml() + { + string json = @"{ + ""available_sizes"": [ + [ + [113, 150], + ""assets/images/resized/0001/1070/11070v1-max-150x150.jpg"" + ], + [ + [189, 250], + ""assets/images/resized/0001/1070/11070v1-max-250x250.jpg"" + ], + [ + [341, 450], + ""assets/images/resized/0001/1070/11070v1-max-450x450.jpg"" + ] + ] +}"; + + XmlDocument newDoc = JsonConvert.DeserializeXmlNode(json, "myRoot"); + + Assert.AreEqual(@"113150assets/images/resized/0001/1070/11070v1-max-150x150.jpg189250assets/images/resized/0001/1070/11070v1-max-250x250.jpg341450assets/images/resized/0001/1070/11070v1-max-450x450.jpg", newDoc.InnerXml); + +#if !NET20 + XDocument newXDoc = JsonConvert.DeserializeXNode(json, "myRoot"); + + Assert.AreEqual(@"113150assets/images/resized/0001/1070/11070v1-max-150x150.jpg189250assets/images/resized/0001/1070/11070v1-max-250x250.jpg341450assets/images/resized/0001/1070/11070v1-max-450x450.jpg", newXDoc.ToString(SaveOptions.DisableFormatting)); +#endif + } + + [Test] + public void Encoding() + { + XmlDocument doc = new XmlDocument(); + + doc.LoadXml(@"O""Connor"); // i use "" so it will be easier to see the problem + + string json = SerializeXmlNode(doc); + Assert.AreEqual(@"{ + ""name"": ""O\""Connor"" +}", json); + } + + [Test] + public void SerializeComment() + { + string xml = @" + Text +"; + XmlDocument doc = new XmlDocument(); + doc.LoadXml(xml); + + string jsonText = SerializeXmlNode(doc); + + string expected = @"{ + ""span"": { + ""@class"": ""vevent"", + ""a"": { + ""@class"": ""url"", + ""@href"": ""http://www.web2con.com/"", + ""#text"": ""Text"" + }/* Hi! */ + } +}"; + + Assert.AreEqual(expected, jsonText); + + XmlDocument newDoc = (XmlDocument)DeserializeXmlNode(jsonText); + Assert.AreEqual(@"Text", newDoc.InnerXml); + } + + [Test] + public void SerializeExample() + { + string xml = @" + + + Alan + http://www.google.com + + + Louis + http://www.yahoo.com + + "; + + XmlDocument doc = new XmlDocument(); + doc.LoadXml(xml); + + string jsonText = SerializeXmlNode(doc); + // { + // "?xml": { + // "@version": "1.0", + // "@standalone": "no" + // }, + // "root": { + // "person": [ + // { + // "@id": "1", + // "name": "Alan", + // "url": "http://www.google.com" + // }, + // { + // "@id": "2", + // "name": "Louis", + // "url": "http://www.yahoo.com" + // } + // ] + // } + // } + + // format + jsonText = JObject.Parse(jsonText).ToString(); + + Assert.AreEqual(@"{ + ""?xml"": { + ""@version"": ""1.0"", + ""@standalone"": ""no"" + }, + ""root"": { + ""person"": [ + { + ""@id"": ""1"", + ""name"": ""Alan"", + ""url"": ""http://www.google.com"" + }, + { + ""@id"": ""2"", + ""name"": ""Louis"", + ""url"": ""http://www.yahoo.com"" + } + ] + } +}", jsonText); + + XmlDocument newDoc = (XmlDocument)DeserializeXmlNode(jsonText); + + Assert.AreEqual(doc.InnerXml, newDoc.InnerXml); + } + + [Test] + public void DeserializeExample() + { + string json = @"{ + ""?xml"": { + ""@version"": ""1.0"", + ""@standalone"": ""no"" + }, + ""root"": { + ""person"": [ + { + ""@id"": ""1"", + ""name"": ""Alan"", + ""url"": ""http://www.google.com"" + }, + { + ""@id"": ""2"", + ""name"": ""Louis"", + ""url"": ""http://www.yahoo.com"" + } + ] + } + }"; + + XmlDocument doc = (XmlDocument)DeserializeXmlNode(json); + // + // + // + // Alan + // http://www.google.com + // + // + // Louis + // http://www.yahoo.com + // + // + + Assert.AreEqual(@" + + +Alan +http://www.google.com + + +Louis +http://www.yahoo.com + +".Replace(Environment.NewLine, string.Empty), doc.InnerXml); + } + + [Test] + public void SerializeDeserializeSpecialProperties() + { + PreserveReferencesHandlingTests.CircularDictionary circularDictionary = new PreserveReferencesHandlingTests.CircularDictionary(); + circularDictionary.Add("other", new PreserveReferencesHandlingTests.CircularDictionary { { "blah", null } }); + circularDictionary.Add("self", circularDictionary); + + string json = JsonConvert.SerializeObject(circularDictionary, Formatting.Indented, + new JsonSerializerSettings { PreserveReferencesHandling = PreserveReferencesHandling.All }); + + Assert.AreEqual(@"{ + ""$id"": ""1"", + ""other"": { + ""$id"": ""2"", + ""blah"": null + }, + ""self"": { + ""$ref"": ""1"" + } +}", json); + + XmlNode node = DeserializeXmlNode(json, "root"); + string xml = GetIndentedInnerXml(node); + string expected = @" + + + + + +"; + + Assert.AreEqual(expected, xml); + + string xmlJson = SerializeXmlNode(node); + string expectedXmlJson = @"{ + ""root"": { + ""$id"": ""1"", + ""other"": { + ""$id"": ""2"", + ""blah"": null + }, + ""self"": { + ""$ref"": ""1"" + } + } +}"; + + Assert.AreEqual(expectedXmlJson, xmlJson); + } + + [Test] + [ExpectedException(typeof(JsonSerializationException), ExpectedMessage = "XmlNodeConverter cannot convert JSON with an empty property name to XML.")] + public void EmptyPropertyName() + { + string json = @"{ + ""8452309520V2"": { + """": { + ""CLIENT"": { + ""ID_EXPIRATION_1"": { + ""VALUE"": ""12/12/2000"", + ""DATATYPE"": ""D"", + ""MSG"": ""Missing Identification Exp. Date 1"" + }, + ""ID_ISSUEDATE_1"": { + ""VALUE"": """", + ""DATATYPE"": ""D"", + ""MSG"": ""Missing Identification Issue Date 1"" + } + } + }, + ""457463534534"": { + ""ACCOUNT"": { + ""FUNDING_SOURCE"": { + ""VALUE"": ""FS0"", + ""DATATYPE"": ""L"", + ""MSG"": ""Missing Source of Funds"" + } + } + } + } +}{ + ""34534634535345"": { + """": { + ""CLIENT"": { + ""ID_NUMBER_1"": { + ""VALUE"": """", + ""DATATYPE"": ""S"", + ""MSG"": ""Missing Picture ID"" + }, + ""ID_EXPIRATION_1"": { + ""VALUE"": ""12/12/2000"", + ""DATATYPE"": ""D"", + ""MSG"": ""Missing Picture ID"" + }, + ""WALK_IN"": { + ""VALUE"": """", + ""DATATYPE"": ""L"", + ""MSG"": ""Missing Walk in"" + }, + ""PERSONAL_MEETING"": { + ""VALUE"": ""PM1"", + ""DATATYPE"": ""L"", + ""MSG"": ""Missing Met Client in Person"" + }, + ""ID_ISSUEDATE_1"": { + ""VALUE"": """", + ""DATATYPE"": ""D"", + ""MSG"": ""Missing Picture ID"" + }, + ""PHOTO_ID"": { + ""VALUE"": """", + ""DATATYPE"": ""L"", + ""MSG"": ""Missing Picture ID"" + }, + ""ID_TYPE_1"": { + ""VALUE"": """", + ""DATATYPE"": ""L"", + ""MSG"": ""Missing Picture ID"" + } + } + }, + ""45635624523"": { + ""ACCOUNT"": { + ""FUNDING_SOURCE"": { + ""VALUE"": ""FS1"", + ""DATATYPE"": ""L"", + ""MSG"": ""Missing Source of Funds"" + } + } + } + } +}"; + + DeserializeXmlNode(json); + } + + [Test] + public void SingleItemArrayPropertySerialization() + { + Product product = new Product(); + + product.Name = "Apple"; + product.ExpiryDate = new DateTime(2008, 12, 28, 0, 0, 0, DateTimeKind.Utc); + product.Price = 3.99M; + product.Sizes = new string[] { "Small" }; + + string output = JsonConvert.SerializeObject(product, new IsoDateTimeConverter()); + + XmlDocument xmlProduct = JsonConvert.DeserializeXmlNode(output, "product", true); + + Assert.AreEqual(@" + Apple + 2008-12-28T00:00:00Z + 3.99 + Small +", IndentXml(xmlProduct.InnerXml)); + + string output2 = JsonConvert.SerializeXmlNode(xmlProduct.DocumentElement, Formatting.Indented); + + Assert.AreEqual(@"{ + ""product"": { + ""Name"": ""Apple"", + ""ExpiryDate"": ""2008-12-28T00:00:00Z"", + ""Price"": ""3.99"", + ""Sizes"": [ + ""Small"" + ] + } +}", output2); + } + + public class TestComplexArrayClass + { + public string Name { get; set; } + public IList Products { get; set; } + } + + [Test] + public void ComplexSingleItemArrayPropertySerialization() + { + TestComplexArrayClass o = new TestComplexArrayClass + { + Name = "Hi", + Products = new List + { + new Product { Name = "First" } + } + }; + + string output = JsonConvert.SerializeObject(o, new IsoDateTimeConverter()); + + XmlDocument xmlProduct = JsonConvert.DeserializeXmlNode(output, "test", true); + + Assert.AreEqual(@" + Hi + + First + 2000-01-01T00:00:00Z + 0 + + +", IndentXml(xmlProduct.InnerXml)); + + string output2 = JsonConvert.SerializeXmlNode(xmlProduct.DocumentElement, Formatting.Indented, true); + + Assert.AreEqual(@"{ + ""Name"": ""Hi"", + ""Products"": [ + { + ""Name"": ""First"", + ""ExpiryDate"": ""2000-01-01T00:00:00Z"", + ""Price"": ""0"", + ""Sizes"": null + } + ] +}", output2); + } + + private string IndentXml(string xml) + { + XmlReader reader = XmlReader.Create(new StringReader(xml)); + + StringWriter sw = new StringWriter(); + XmlWriter writer = XmlWriter.Create(sw, new XmlWriterSettings { Indent = true, OmitXmlDeclaration = true }); + + while (reader.Read()) + { + writer.WriteNode(reader, false); + } + + writer.Flush(); + + return sw.ToString(); + } + + [Test] + public void OmitRootObject() + { + string xml = @" + Hi + Hi + + First + 2000-01-01T00:00:00Z + 0 + + +"; + + XmlDocument d = new XmlDocument(); + d.LoadXml(xml); + + string output = JsonConvert.SerializeXmlNode(d, Formatting.Indented, true); + + Assert.AreEqual(@"{ + ""Name"": [ + ""Hi"", + ""Hi"" + ], + ""Products"": [ + { + ""Name"": ""First"", + ""ExpiryDate"": ""2000-01-01T00:00:00Z"", + ""Price"": ""0"", + ""Sizes"": null + } + ] +}", output); + } + + [Test] + public void EmtpyElementWithArrayAttributeShouldWriteAttributes() + { + string xml = @" + + + + + + +"; + + XmlDocument d = new XmlDocument(); + d.LoadXml(xml); + + string json = JsonConvert.SerializeXmlNode(d, Formatting.Indented); + + Assert.AreEqual(@"{ + ""?xml"": { + ""@version"": ""1.0"", + ""@encoding"": ""utf-8"" + }, + ""root"": { + ""A"": { + ""B"": [ + { + ""@name"": ""sample"" + } + ], + ""C"": [ + null, + null + ] + } + } +}", json); + } + + [Test] + public void DeserializeNonInt64IntegerValues() + { + var dict = new Dictionary { { "Int16", (short)1 }, { "Float", 2f }, { "Int32", 3 } }; + var obj = JObject.FromObject(dict); + var serializer = JsonSerializer.Create(new JsonSerializerSettings { Converters = { new XmlNodeConverter() { DeserializeRootElementName = "root" } } }); + using (var reader = obj.CreateReader()) + { + var value = (XmlDocument)serializer.Deserialize(reader, typeof(XmlDocument)); + + Assert.AreEqual(@"123", value.InnerXml); + } + } + + [Test] + public void DeserializingBooleanValues() + { + MemoryStream ms = new MemoryStream(System.Text.Encoding.UTF8.GetBytes(@"{root:{""@booleanType"":true}}")); + MemoryStream xml = new MemoryStream(); + + JsonBodyToSoapXml(ms, xml); + + string xmlString = System.Text.Encoding.UTF8.GetString(xml.ToArray()); + + Assert.AreEqual(@"", xmlString); + } + + private static void JsonBodyToSoapXml(Stream json, Stream xml) + { + Newtonsoft.Json.JsonSerializerSettings settings = new Newtonsoft.Json.JsonSerializerSettings(); + settings.Converters.Add(new Newtonsoft.Json.Converters.XmlNodeConverter()); + Newtonsoft.Json.JsonSerializer serializer = Newtonsoft.Json.JsonSerializer.Create(settings); + using (Newtonsoft.Json.JsonTextReader reader = new Newtonsoft.Json.JsonTextReader(new System.IO.StreamReader(json))) + { + XmlDocument doc = (XmlDocument)serializer.Deserialize(reader, typeof(XmlDocument)); + if (reader.Read() && reader.TokenType != JsonToken.Comment) + throw new JsonSerializationException("Additional text found in JSON string after finishing deserializing object."); + using (XmlWriter writer = XmlWriter.Create(xml)) + { + doc.Save(writer); + } + } + } + +#if !NET20 + [Test] + public void DeserializeXNodeDefaultNamespace() + { + string xaml = @" + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +"; + + string json = JsonConvert.SerializeXNode(XDocument.Parse(xaml), Formatting.Indented); + + string expectedJson = @"{ + ""Grid"": { + ""@xmlns"": ""http://schemas.microsoft.com/winfx/2006/xaml/presentation"", + ""@xmlns:x"": ""http://schemas.microsoft.com/winfx/2006/xaml"", + ""@xmlns:toolkit"": ""clr-namespace:Microsoft.Phone.Controls;assembly=Microsoft.Phone.Controls.Toolkit"", + ""@Style"": ""{StaticResource trimFormGrid}"", + ""@x:Name"": ""TrimObjectForm"", + ""Grid.ColumnDefinitions"": { + ""ColumnDefinition"": [ + { + ""@Width"": ""63*"" + }, + { + ""@Width"": ""320*"" + } + ] + }, + ""Grid.RowDefinitions"": { + ""@xmlns"": """", + ""RowDefinition"": [ + null, + null, + null, + null, + null, + null, + null, + null + ] + }, + ""TextBox"": [ + { + ""@Style"": ""{StaticResource trimFormGrid_TB}"", + ""@Text"": ""{Binding TypedTitle, Converter={StaticResource trimPropertyConverter}}"", + ""@Name"": ""RecordTypedTitle"", + ""@Grid.Column"": ""1"", + ""@Grid.Row"": ""0"", + ""@xmlns"": """" + }, + { + ""@Style"": ""{StaticResource trimFormGrid_TB}"", + ""@Text"": ""{Binding ExternalReference, Converter={StaticResource trimPropertyConverter}}"", + ""@Name"": ""RecordExternalReference"", + ""@Grid.Column"": ""1"", + ""@Grid.Row"": ""1"", + ""@xmlns"": """" + }, + { + ""@Style"": ""{StaticResource trimFormGrid_TB}"", + ""@Text"": ""{Binding Author, Converter={StaticResource trimPropertyConverter}}"", + ""@Name"": ""RecordAuthor"", + ""@Grid.Column"": ""1"", + ""@Grid.Row"": ""4"", + ""@xmlns"": """" + }, + { + ""@Style"": ""{StaticResource trimFormGrid_TB}"", + ""@Text"": ""{Binding Container, Converter={StaticResource trimPropertyConverter}}"", + ""@Name"": ""RecordContainer"", + ""@Grid.Column"": ""1"", + ""@Grid.Row"": ""5"", + ""@xmlns"": """" + }, + { + ""@Style"": ""{StaticResource trimFormGrid_TB}"", + ""@Text"": ""{Binding IsEnclosed, Converter={StaticResource trimPropertyConverter}}"", + ""@Name"": ""RecordIsEnclosed"", + ""@Grid.Column"": ""1"", + ""@Grid.Row"": ""6"", + ""@xmlns"": """" + }, + { + ""@Style"": ""{StaticResource trimFormGrid_TB}"", + ""@Text"": ""{Binding Assignee, Converter={StaticResource trimPropertyConverter}}"", + ""@Name"": ""RecordAssignee"", + ""@Grid.Column"": ""1"", + ""@Grid.Row"": ""7"", + ""@xmlns"": """" + } + ], + ""toolkit:DatePicker"": [ + { + ""@Style"": ""{StaticResource trimFormGrid_DP}"", + ""@Value"": ""{Binding DateCreated, Converter={StaticResource trimPropertyConverter}}"", + ""@Name"": ""RecordDateCreated"", + ""@Grid.Column"": ""1"", + ""@Grid.Row"": ""2"" + }, + { + ""@Style"": ""{StaticResource trimFormGrid_DP}"", + ""@Value"": ""{Binding DateDue, Converter={StaticResource trimPropertyConverter}}"", + ""@Name"": ""RecordDateDue"", + ""@Grid.Column"": ""1"", + ""@Grid.Row"": ""3"" + } + ], + ""TextBlock"": [ + { + ""@Grid.Column"": ""0"", + ""@Text"": ""Title (Free Text Part)"", + ""@Style"": ""{StaticResource trimFormGrid_LBL}"", + ""@Grid.Row"": ""0"", + ""@xmlns"": """" + }, + { + ""@Grid.Column"": ""0"", + ""@Text"": ""External ID"", + ""@Style"": ""{StaticResource trimFormGrid_LBL}"", + ""@Grid.Row"": ""1"", + ""@xmlns"": """" + }, + { + ""@Grid.Column"": ""0"", + ""@Text"": ""Date Created"", + ""@Style"": ""{StaticResource trimFormGrid_LBL}"", + ""@Grid.Row"": ""2"", + ""@xmlns"": """" + }, + { + ""@Grid.Column"": ""0"", + ""@Text"": ""Date Due"", + ""@Style"": ""{StaticResource trimFormGrid_LBL}"", + ""@Grid.Row"": ""3"", + ""@xmlns"": """" + }, + { + ""@Grid.Column"": ""0"", + ""@Text"": ""Author"", + ""@Style"": ""{StaticResource trimFormGrid_LBL}"", + ""@Grid.Row"": ""4"", + ""@xmlns"": """" + }, + { + ""@Grid.Column"": ""0"", + ""@Text"": ""Container"", + ""@Style"": ""{StaticResource trimFormGrid_LBL}"", + ""@Grid.Row"": ""5"", + ""@xmlns"": """" + }, + { + ""@Grid.Column"": ""0"", + ""@Text"": ""Enclosed?"", + ""@Style"": ""{StaticResource trimFormGrid_LBL}"", + ""@Grid.Row"": ""6"", + ""@xmlns"": """" + }, + { + ""@Grid.Column"": ""0"", + ""@Text"": ""Assignee"", + ""@Style"": ""{StaticResource trimFormGrid_LBL}"", + ""@Grid.Row"": ""7"", + ""@xmlns"": """" + } + ] + } +}"; + + Assert.AreEqual(expectedJson, json); + + XNode node = JsonConvert.DeserializeXNode(json); + + string xaml2 = node.ToString(); + + string expectedXaml = @" + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +"; + + Assert.AreEqual(expectedXaml, xaml2); + } +#endif + + [Test] + public void DeserializeXmlNodeDefaultNamespace() + { + string xaml = @" + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +"; + + XmlDocument document = new XmlDocument(); + document.LoadXml(xaml); + + string json = JsonConvert.SerializeXmlNode(document, Formatting.Indented); + + string expectedJson = @"{ + ""Grid"": { + ""@xmlns"": ""http://schemas.microsoft.com/winfx/2006/xaml/presentation"", + ""@xmlns:x"": ""http://schemas.microsoft.com/winfx/2006/xaml"", + ""@xmlns:toolkit"": ""clr-namespace:Microsoft.Phone.Controls;assembly=Microsoft.Phone.Controls.Toolkit"", + ""@Style"": ""{StaticResource trimFormGrid}"", + ""@x:Name"": ""TrimObjectForm"", + ""Grid.ColumnDefinitions"": { + ""ColumnDefinition"": [ + { + ""@Width"": ""63*"" + }, + { + ""@Width"": ""320*"" + } + ] + }, + ""Grid.RowDefinitions"": { + ""@xmlns"": """", + ""RowDefinition"": [ + null, + null, + null, + null, + null, + null, + null, + null + ] + }, + ""TextBox"": [ + { + ""@Style"": ""{StaticResource trimFormGrid_TB}"", + ""@Text"": ""{Binding TypedTitle, Converter={StaticResource trimPropertyConverter}}"", + ""@Name"": ""RecordTypedTitle"", + ""@Grid.Column"": ""1"", + ""@Grid.Row"": ""0"", + ""@xmlns"": """" + }, + { + ""@Style"": ""{StaticResource trimFormGrid_TB}"", + ""@Text"": ""{Binding ExternalReference, Converter={StaticResource trimPropertyConverter}}"", + ""@Name"": ""RecordExternalReference"", + ""@Grid.Column"": ""1"", + ""@Grid.Row"": ""1"", + ""@xmlns"": """" + }, + { + ""@Style"": ""{StaticResource trimFormGrid_TB}"", + ""@Text"": ""{Binding Author, Converter={StaticResource trimPropertyConverter}}"", + ""@Name"": ""RecordAuthor"", + ""@Grid.Column"": ""1"", + ""@Grid.Row"": ""4"", + ""@xmlns"": """" + }, + { + ""@Style"": ""{StaticResource trimFormGrid_TB}"", + ""@Text"": ""{Binding Container, Converter={StaticResource trimPropertyConverter}}"", + ""@Name"": ""RecordContainer"", + ""@Grid.Column"": ""1"", + ""@Grid.Row"": ""5"", + ""@xmlns"": """" + }, + { + ""@Style"": ""{StaticResource trimFormGrid_TB}"", + ""@Text"": ""{Binding IsEnclosed, Converter={StaticResource trimPropertyConverter}}"", + ""@Name"": ""RecordIsEnclosed"", + ""@Grid.Column"": ""1"", + ""@Grid.Row"": ""6"", + ""@xmlns"": """" + }, + { + ""@Style"": ""{StaticResource trimFormGrid_TB}"", + ""@Text"": ""{Binding Assignee, Converter={StaticResource trimPropertyConverter}}"", + ""@Name"": ""RecordAssignee"", + ""@Grid.Column"": ""1"", + ""@Grid.Row"": ""7"", + ""@xmlns"": """" + } + ], + ""toolkit:DatePicker"": [ + { + ""@Style"": ""{StaticResource trimFormGrid_DP}"", + ""@Value"": ""{Binding DateCreated, Converter={StaticResource trimPropertyConverter}}"", + ""@Name"": ""RecordDateCreated"", + ""@Grid.Column"": ""1"", + ""@Grid.Row"": ""2"" + }, + { + ""@Style"": ""{StaticResource trimFormGrid_DP}"", + ""@Value"": ""{Binding DateDue, Converter={StaticResource trimPropertyConverter}}"", + ""@Name"": ""RecordDateDue"", + ""@Grid.Column"": ""1"", + ""@Grid.Row"": ""3"" + } + ], + ""TextBlock"": [ + { + ""@Grid.Column"": ""0"", + ""@Text"": ""Title (Free Text Part)"", + ""@Style"": ""{StaticResource trimFormGrid_LBL}"", + ""@Grid.Row"": ""0"", + ""@xmlns"": """" + }, + { + ""@Grid.Column"": ""0"", + ""@Text"": ""External ID"", + ""@Style"": ""{StaticResource trimFormGrid_LBL}"", + ""@Grid.Row"": ""1"", + ""@xmlns"": """" + }, + { + ""@Grid.Column"": ""0"", + ""@Text"": ""Date Created"", + ""@Style"": ""{StaticResource trimFormGrid_LBL}"", + ""@Grid.Row"": ""2"", + ""@xmlns"": """" + }, + { + ""@Grid.Column"": ""0"", + ""@Text"": ""Date Due"", + ""@Style"": ""{StaticResource trimFormGrid_LBL}"", + ""@Grid.Row"": ""3"", + ""@xmlns"": """" + }, + { + ""@Grid.Column"": ""0"", + ""@Text"": ""Author"", + ""@Style"": ""{StaticResource trimFormGrid_LBL}"", + ""@Grid.Row"": ""4"", + ""@xmlns"": """" + }, + { + ""@Grid.Column"": ""0"", + ""@Text"": ""Container"", + ""@Style"": ""{StaticResource trimFormGrid_LBL}"", + ""@Grid.Row"": ""5"", + ""@xmlns"": """" + }, + { + ""@Grid.Column"": ""0"", + ""@Text"": ""Enclosed?"", + ""@Style"": ""{StaticResource trimFormGrid_LBL}"", + ""@Grid.Row"": ""6"", + ""@xmlns"": """" + }, + { + ""@Grid.Column"": ""0"", + ""@Text"": ""Assignee"", + ""@Style"": ""{StaticResource trimFormGrid_LBL}"", + ""@Grid.Row"": ""7"", + ""@xmlns"": """" + } + ] + } +}"; + + Assert.AreEqual(expectedJson, json); + + XmlNode node = JsonConvert.DeserializeXmlNode(json); + + StringWriter sw = new StringWriter(); + XmlWriter writer = XmlWriter.Create(sw, new XmlWriterSettings + { + Indent = true, + OmitXmlDeclaration = true + }); + node.WriteTo(writer); + writer.Flush(); + + string xaml2 = sw.ToString(); + + string expectedXaml = @" + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +"; + + Assert.AreEqual(expectedXaml, xaml2); + } + + [Test] + public void DeserializeAttributePropertyNotAtStart() + { + string json = @"{""item"": {""@action"": ""update"", ""@itemid"": ""1"", ""elements"": [{""@action"": ""none"", ""@id"": ""2""},{""@action"": ""none"", ""@id"": ""3""}],""@description"": ""temp""}}"; + + XmlDocument xmldoc = JsonConvert.DeserializeXmlNode(json); + + Assert.AreEqual(@"", xmldoc.InnerXml); + } + } +} #endif \ No newline at end of file diff --git a/Src/Newtonsoft.Json/Converters/XmlNodeConverter.cs b/Src/Newtonsoft.Json/Converters/XmlNodeConverter.cs index faa3bdfc..2c4beb8d 100644 --- a/Src/Newtonsoft.Json/Converters/XmlNodeConverter.cs +++ b/Src/Newtonsoft.Json/Converters/XmlNodeConverter.cs @@ -1,1551 +1,1567 @@ -#region License -// Copyright (c) 2007 James Newton-King -// -// Permission is hereby granted, free of charge, to any person -// obtaining a copy of this software and associated documentation -// files (the "Software"), to deal in the Software without -// restriction, including without limitation the rights to use, -// copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following -// conditions: -// -// The above copyright notice and this permission notice shall be -// included in all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES -// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT -// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, -// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR -// OTHER DEALINGS IN THE SOFTWARE. -#endregion - -#if (!SILVERLIGHT || WINDOWS_PHONE) -using System; -using System.Collections.Generic; -using System.Globalization; -using System.Xml; -#if !NET20 -using System.Xml.Linq; -#endif -using Newtonsoft.Json.Utilities; -#if NET20 -using Newtonsoft.Json.Utilities.LinqBridge; -#else -using System.Linq; -#endif - -namespace Newtonsoft.Json.Converters -{ - #region XmlNodeWrappers -#if !SILVERLIGHT && !NETFX_CORE - internal class XmlDocumentWrapper : XmlNodeWrapper, IXmlDocument - { - private readonly XmlDocument _document; - - public XmlDocumentWrapper(XmlDocument document) - : base(document) - { - _document = document; - } - - public IXmlNode CreateComment(string data) - { - return new XmlNodeWrapper(_document.CreateComment(data)); - } - - public IXmlNode CreateTextNode(string text) - { - return new XmlNodeWrapper(_document.CreateTextNode(text)); - } - - public IXmlNode CreateCDataSection(string data) - { - return new XmlNodeWrapper(_document.CreateCDataSection(data)); - } - - public IXmlNode CreateWhitespace(string text) - { - return new XmlNodeWrapper(_document.CreateWhitespace(text)); - } - - public IXmlNode CreateSignificantWhitespace(string text) - { - return new XmlNodeWrapper(_document.CreateSignificantWhitespace(text)); - } - - public IXmlNode CreateXmlDeclaration(string version, string encoding, string standalone) - { - return new XmlNodeWrapper(_document.CreateXmlDeclaration(version, encoding, standalone)); - } - - public IXmlNode CreateProcessingInstruction(string target, string data) - { - return new XmlNodeWrapper(_document.CreateProcessingInstruction(target, data)); - } - - public IXmlElement CreateElement(string elementName) - { - return new XmlElementWrapper(_document.CreateElement(elementName)); - } - - public IXmlElement CreateElement(string qualifiedName, string namespaceUri) - { - return new XmlElementWrapper(_document.CreateElement(qualifiedName, namespaceUri)); - } - - public IXmlNode CreateAttribute(string name, string value) - { - XmlNodeWrapper attribute = new XmlNodeWrapper(_document.CreateAttribute(name)); - attribute.Value = value; - - return attribute; - } - - public IXmlNode CreateAttribute(string qualifiedName, string namespaceUri, string value) - { - XmlNodeWrapper attribute = new XmlNodeWrapper(_document.CreateAttribute(qualifiedName, namespaceUri)); - attribute.Value = value; - - return attribute; - } - - public IXmlElement DocumentElement - { - get - { - if (_document.DocumentElement == null) - return null; - - return new XmlElementWrapper(_document.DocumentElement); - } - } - } - - internal class XmlElementWrapper : XmlNodeWrapper, IXmlElement - { - private readonly XmlElement _element; - - public XmlElementWrapper(XmlElement element) - : base(element) - { - _element = element; - } - - public void SetAttributeNode(IXmlNode attribute) - { - XmlNodeWrapper xmlAttributeWrapper = (XmlNodeWrapper)attribute; - - _element.SetAttributeNode((XmlAttribute) xmlAttributeWrapper.WrappedNode); - } - - public string GetPrefixOfNamespace(string namespaceUri) - { - return _element.GetPrefixOfNamespace(namespaceUri); - } - } - - internal class XmlDeclarationWrapper : XmlNodeWrapper, IXmlDeclaration - { - private readonly XmlDeclaration _declaration; - - public XmlDeclarationWrapper(XmlDeclaration declaration) - : base(declaration) - { - _declaration = declaration; - } - - public string Version - { - get { return _declaration.Version; } - } - - public string Encoding - { - get { return _declaration.Encoding; } - set { _declaration.Encoding = value; } - } - - public string Standalone - { - get { return _declaration.Standalone; } - set { _declaration.Standalone = value; } - } - } - - internal class XmlNodeWrapper : IXmlNode - { - private readonly XmlNode _node; - - public XmlNodeWrapper(XmlNode node) - { - _node = node; - } - - public object WrappedNode - { - get { return _node; } - } - - public XmlNodeType NodeType - { - get { return _node.NodeType; } - } - - public string Name - { - get { return _node.Name; } - } - - public string LocalName - { - get { return _node.LocalName; } - } - - public IList ChildNodes - { - get { return _node.ChildNodes.Cast().Select(n => WrapNode(n)).ToList(); } - } - - private IXmlNode WrapNode(XmlNode node) - { - switch (node.NodeType) - { - case XmlNodeType.Element: - return new XmlElementWrapper((XmlElement) node); - case XmlNodeType.XmlDeclaration: - return new XmlDeclarationWrapper((XmlDeclaration) node); - default: - return new XmlNodeWrapper(node); - } - } - - public IList Attributes - { - get - { - if (_node.Attributes == null) - return null; - - return _node.Attributes.Cast().Select(a => WrapNode(a)).ToList(); - } - } - - public IXmlNode ParentNode - { - get - { - XmlNode node = (_node is XmlAttribute) - ? ((XmlAttribute) _node).OwnerElement - : _node.ParentNode; - - if (node == null) - return null; - - return WrapNode(node); - } - } - - public string Value - { - get { return _node.Value; } - set { _node.Value = value; } - } - - public IXmlNode AppendChild(IXmlNode newChild) - { - XmlNodeWrapper xmlNodeWrapper = (XmlNodeWrapper) newChild; - _node.AppendChild(xmlNodeWrapper._node); - - return newChild; - } - - public string Prefix - { - get { return _node.Prefix; } - } - - public string NamespaceUri - { - get { return _node.NamespaceURI; } - } - } -#endif - #endregion - - #region Interfaces - internal interface IXmlDocument : IXmlNode - { - IXmlNode CreateComment(string text); - IXmlNode CreateTextNode(string text); - IXmlNode CreateCDataSection(string data); - IXmlNode CreateWhitespace(string text); - IXmlNode CreateSignificantWhitespace(string text); - IXmlNode CreateXmlDeclaration(string version, string encoding, string standalone); - IXmlNode CreateProcessingInstruction(string target, string data); - IXmlElement CreateElement(string elementName); - IXmlElement CreateElement(string qualifiedName, string namespaceUri); - IXmlNode CreateAttribute(string name, string value); - IXmlNode CreateAttribute(string qualifiedName, string namespaceUri, string value); - - IXmlElement DocumentElement { get; } - } - - internal interface IXmlDeclaration : IXmlNode - { - string Version { get; } - string Encoding { get; set; } - string Standalone { get; set; } - } - - internal interface IXmlElement : IXmlNode - { - void SetAttributeNode(IXmlNode attribute); - string GetPrefixOfNamespace(string namespaceUri); - } - - internal interface IXmlNode - { - XmlNodeType NodeType { get; } - string LocalName { get; } - IList ChildNodes { get; } - IList Attributes { get; } - IXmlNode ParentNode { get; } - string Value { get; set; } - IXmlNode AppendChild(IXmlNode newChild); - string NamespaceUri { get; } - object WrappedNode { get; } - } - #endregion - - #region XNodeWrappers -#if !NET20 - internal class XDeclarationWrapper : XObjectWrapper, IXmlDeclaration - { - internal XDeclaration Declaration { get; private set; } - - public XDeclarationWrapper(XDeclaration declaration) - : base(null) - { - Declaration = declaration; - } - - public override XmlNodeType NodeType - { - get { return XmlNodeType.XmlDeclaration; } - } - - public string Version - { - get { return Declaration.Version; } - } - - public string Encoding - { - get { return Declaration.Encoding; } - set { Declaration.Encoding = value; } - } - - public string Standalone - { - get { return Declaration.Standalone; } - set { Declaration.Standalone = value; } - } - } - - internal class XDocumentWrapper : XContainerWrapper, IXmlDocument - { - private XDocument Document - { - get { return (XDocument)WrappedNode; } - } - - public XDocumentWrapper(XDocument document) - : base(document) - { - } - - public override IList ChildNodes - { - get - { - IList childNodes = base.ChildNodes; - - if (Document.Declaration != null) - childNodes.Insert(0, new XDeclarationWrapper(Document.Declaration)); - - return childNodes; - } - } - - public IXmlNode CreateComment(string text) - { - return new XObjectWrapper(new XComment(text)); - } - - public IXmlNode CreateTextNode(string text) - { - return new XObjectWrapper(new XText(text)); - } - - public IXmlNode CreateCDataSection(string data) - { - return new XObjectWrapper(new XCData(data)); - } - - public IXmlNode CreateWhitespace(string text) - { - return new XObjectWrapper(new XText(text)); - } - - public IXmlNode CreateSignificantWhitespace(string text) - { - return new XObjectWrapper(new XText(text)); - } - - public IXmlNode CreateXmlDeclaration(string version, string encoding, string standalone) - { - return new XDeclarationWrapper(new XDeclaration(version, encoding, standalone)); - } - - public IXmlNode CreateProcessingInstruction(string target, string data) - { - return new XProcessingInstructionWrapper(new XProcessingInstruction(target, data)); - } - - public IXmlElement CreateElement(string elementName) - { - return new XElementWrapper(new XElement(elementName)); - } - - public IXmlElement CreateElement(string qualifiedName, string namespaceUri) - { - string localName = MiscellaneousUtils.GetLocalName(qualifiedName); - return new XElementWrapper(new XElement(XName.Get(localName, namespaceUri))); - } - - public IXmlNode CreateAttribute(string name, string value) - { - return new XAttributeWrapper(new XAttribute(name, value)); - } - - public IXmlNode CreateAttribute(string qualifiedName, string namespaceUri, string value) - { - string localName = MiscellaneousUtils.GetLocalName(qualifiedName); - return new XAttributeWrapper(new XAttribute(XName.Get(localName, namespaceUri), value)); - } - - public IXmlElement DocumentElement - { - get - { - if (Document.Root == null) - return null; - - return new XElementWrapper(Document.Root); - } - } - - public override IXmlNode AppendChild(IXmlNode newChild) - { - XDeclarationWrapper declarationWrapper = newChild as XDeclarationWrapper; - if (declarationWrapper != null) - { - Document.Declaration = declarationWrapper.Declaration; - return declarationWrapper; - } - else - { - return base.AppendChild(newChild); - } - } - } - - internal class XTextWrapper : XObjectWrapper - { - private XText Text - { - get { return (XText)WrappedNode; } - } - - public XTextWrapper(XText text) - : base(text) - { - } - - public override string Value - { - get { return Text.Value; } - set { Text.Value = value; } - } - - public override IXmlNode ParentNode - { - get - { - if (Text.Parent == null) - return null; - - return XContainerWrapper.WrapNode(Text.Parent); - } - } - } - - internal class XCommentWrapper : XObjectWrapper - { - private XComment Text - { - get { return (XComment)WrappedNode; } - } - - public XCommentWrapper(XComment text) - : base(text) - { - } - - public override string Value - { - get { return Text.Value; } - set { Text.Value = value; } - } - - public override IXmlNode ParentNode - { - get - { - if (Text.Parent == null) - return null; - - return XContainerWrapper.WrapNode(Text.Parent); - } - } - } - - internal class XProcessingInstructionWrapper : XObjectWrapper - { - private XProcessingInstruction ProcessingInstruction - { - get { return (XProcessingInstruction)WrappedNode; } - } - - public XProcessingInstructionWrapper(XProcessingInstruction processingInstruction) - : base(processingInstruction) - { - } - - public override string LocalName - { - get { return ProcessingInstruction.Target; } - } - - public override string Value - { - get { return ProcessingInstruction.Data; } - set { ProcessingInstruction.Data = value; } - } - } - - internal class XContainerWrapper : XObjectWrapper - { - private XContainer Container - { - get { return (XContainer)WrappedNode; } - } - - public XContainerWrapper(XContainer container) - : base(container) - { - } - - public override IList ChildNodes - { - get { return Container.Nodes().Select(n => WrapNode(n)).ToList(); } - } - - public override IXmlNode ParentNode - { - get - { - if (Container.Parent == null) - return null; - - return WrapNode(Container.Parent); - } - } - - internal static IXmlNode WrapNode(XObject node) - { - if (node is XDocument) - return new XDocumentWrapper((XDocument)node); - else if (node is XElement) - return new XElementWrapper((XElement)node); - else if (node is XContainer) - return new XContainerWrapper((XContainer)node); - else if (node is XProcessingInstruction) - return new XProcessingInstructionWrapper((XProcessingInstruction)node); - else if (node is XText) - return new XTextWrapper((XText)node); - else if (node is XComment) - return new XCommentWrapper((XComment)node); - else if (node is XAttribute) - return new XAttributeWrapper((XAttribute) node); - else - return new XObjectWrapper(node); - } - - public override IXmlNode AppendChild(IXmlNode newChild) - { - Container.Add(newChild.WrappedNode); - return newChild; - } - } - - internal class XObjectWrapper : IXmlNode - { - private readonly XObject _xmlObject; - - public XObjectWrapper(XObject xmlObject) - { - _xmlObject = xmlObject; - } - - public object WrappedNode - { - get { return _xmlObject; } - } - - public virtual XmlNodeType NodeType - { - get { return _xmlObject.NodeType; } - } - - public virtual string LocalName - { - get { return null; } - } - - public virtual IList ChildNodes - { - get { return new List(); } - } - - public virtual IList Attributes - { - get { return null; } - } - - public virtual IXmlNode ParentNode - { - get { return null; } - } - - public virtual string Value - { - get { return null; } - set { throw new InvalidOperationException(); } - } - - public virtual IXmlNode AppendChild(IXmlNode newChild) - { - throw new InvalidOperationException(); - } - - public virtual string NamespaceUri - { - get { return null; } - } - } - - internal class XAttributeWrapper : XObjectWrapper - { - private XAttribute Attribute - { - get { return (XAttribute)WrappedNode; } - } - - public XAttributeWrapper(XAttribute attribute) - : base(attribute) - { - } - - public override string Value - { - get { return Attribute.Value; } - set { Attribute.Value = value; } - } - - public override string LocalName - { - get { return Attribute.Name.LocalName; } - } - - public override string NamespaceUri - { - get { return Attribute.Name.NamespaceName; } - } - - public override IXmlNode ParentNode - { - get - { - if (Attribute.Parent == null) - return null; - - return XContainerWrapper.WrapNode(Attribute.Parent); - } - } - } - - internal class XElementWrapper : XContainerWrapper, IXmlElement - { - private XElement Element - { - get { return (XElement) WrappedNode; } - } - - public XElementWrapper(XElement element) - : base(element) - { - } - - public void SetAttributeNode(IXmlNode attribute) - { - XObjectWrapper wrapper = (XObjectWrapper)attribute; - Element.Add(wrapper.WrappedNode); - } - - public override IList Attributes - { - get { return Element.Attributes().Select(a => new XAttributeWrapper(a)).Cast().ToList(); } - } - - public override string Value - { - get { return Element.Value; } - set { Element.Value = value; } - } - - public override string LocalName - { - get { return Element.Name.LocalName; } - } - - public override string NamespaceUri - { - get { return Element.Name.NamespaceName; } - } - - public string GetPrefixOfNamespace(string namespaceUri) - { - return Element.GetPrefixOfNamespace(namespaceUri); - } - } -#endif - #endregion - - /// - /// Converts XML to and from JSON. - /// - 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"; - - /// - /// Gets or sets the name of the root element to insert when deserializing to XML if the JSON structure has produces multiple root elements. - /// - /// The name of the deserialize root element. - public string DeserializeRootElementName { get; set; } - - /// - /// Gets or sets a flag 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 attibute is written to the XML; otherwise, false. - public bool WriteArrayAttribute { get; set; } - - /// - /// Gets or sets a value indicating whether to write the root JSON object. - /// - /// true if the JSON root object is omitted; otherwise, false. - public bool OmitRootObject { get; set; } - - #region Writing - /// - /// Writes the JSON representation of the object. - /// - /// The to write to. - /// The calling serializer. - /// The value. - 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 !NET20 - if (value is XObject) - return XContainerWrapper.WrapNode((XObject)value); -#endif -#if !(SILVERLIGHT || NETFX_CORE) - if (value is XmlNode) - return new XmlNodeWrapper((XmlNode)value); -#endif - - throw new ArgumentException("Value must be an XML object.", "value"); - } - - private void PushParentNamespaces(IXmlNode node, XmlNamespaceManager manager) - { - List parentElements = null; - - IXmlNode parent = node; - while ((parent = parent.ParentNode) != null) - { - if (parent.NodeType == XmlNodeType.Element) - { - if (parentElements == null) - parentElements = new List(); - - parentElements.Add(parent); - } - } - - if (parentElements != null) - { - parentElements.Reverse(); - - foreach (IXmlNode parentElement in parentElements) - { - manager.PushScope(); - foreach (IXmlNode attribute in parentElement.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 prefix = (node.NamespaceUri == null || (node.LocalName == "xmlns" && node.NamespaceUri == "http://www.w3.org/2000/xmlns/")) - ? null - : manager.LookupPrefix(node.NamespaceUri); - - if (!string.IsNullOrEmpty(prefix)) - return prefix + ":" + node.LocalName; - else - return node.LocalName; - } - - private string GetPropertyName(IXmlNode node, XmlNamespaceManager manager) - { - switch (node.NodeType) - { - case XmlNodeType.Attribute: - if (node.NamespaceUri == JsonNamespaceUri) - return "$" + node.LocalName; - else - return "@" + ResolveFullName(node, manager); - case XmlNodeType.CDATA: - return CDataName; - case XmlNodeType.Comment: - return CommentName; - case XmlNodeType.Element: - return ResolveFullName(node, manager); - case XmlNodeType.ProcessingInstruction: - return "?" + ResolveFullName(node, manager); - case XmlNodeType.XmlDeclaration: - return DeclarationName; - case XmlNodeType.SignificantWhitespace: - return SignificantWhitespaceName; - case XmlNodeType.Text: - return TextName; - case XmlNodeType.Whitespace: - return WhitespaceName; - default: - throw new JsonSerializationException("Unexpected XmlNodeType when getting node name: " + node.NodeType); - } - } - - private bool IsArray(IXmlNode node) - { - IXmlNode jsonArrayAttribute = (node.Attributes != null) - ? node.Attributes.SingleOrDefault(a => a.LocalName == "Array" && a.NamespaceUri == JsonNamespaceUri) - : null; - - return (jsonArrayAttribute != null && XmlConvert.ToBoolean(jsonArrayAttribute.Value)); - } - - private void SerializeGroupedNodes(JsonWriter writer, IXmlNode node, XmlNamespaceManager manager, bool writePropertyName) - { - // group nodes together by name - Dictionary> nodesGroupedByName = new Dictionary>(); - - for (int i = 0; i < node.ChildNodes.Count; i++) - { - IXmlNode childNode = node.ChildNodes[i]; - string nodeName = GetPropertyName(childNode, manager); - - List nodes; - if (!nodesGroupedByName.TryGetValue(nodeName, out nodes)) - { - nodes = new List(); - nodesGroupedByName.Add(nodeName, nodes); - } - - nodes.Add(childNode); - } - - // loop through grouped nodes. write single name instances as normal, - // write multiple names together in an array - foreach (KeyValuePair> nodeNameGroup in nodesGroupedByName) - { - List groupedNodes = nodeNameGroup.Value; - bool writeArray; - - if (groupedNodes.Count == 1) - { - writeArray = IsArray(groupedNodes[0]); - } - else - { - writeArray = true; - } - - if (!writeArray) - { - SerializeNode(writer, groupedNodes[0], manager, writePropertyName); - } - else - { - string elementNames = nodeNameGroup.Key; - - if (writePropertyName) - writer.WritePropertyName(elementNames); - - writer.WriteStartArray(); - - for (int i = 0; i < groupedNodes.Count; i++) - { - SerializeNode(writer, groupedNodes[i], 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(n => n.LocalName == node.LocalName) && node.ChildNodes.Count > 0) - { - SerializeGroupedNodes(writer, node, manager, false); - } - else - { - foreach (IXmlNode attribute in node.Attributes) - { - if (attribute.NamespaceUri == "http://www.w3.org/2000/xmlns/") - { - string prefix = (attribute.LocalName != "xmlns") - ? attribute.LocalName - : string.Empty; - - manager.AddNamespace(prefix, attribute.Value); - } - } - - if (writePropertyName) - writer.WritePropertyName(GetPropertyName(node, manager)); - - if (!ValueAttributes(node.Attributes).Any() && node.ChildNodes.Count == 1 - && node.ChildNodes[0].NodeType == XmlNodeType.Text) - { - // write elements with a single text child as a name value pair - writer.WriteValue(node.ChildNodes[0].Value); - } - else if (node.ChildNodes.Count == 0 && CollectionUtils.IsNullOrEmpty(node.Attributes)) - { - // empty element - 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(); - } - } - - 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 == JsonNamespaceUri) - return; - - if (node.NamespaceUri == JsonNamespaceUri) - { - if (node.LocalName == "Array") - return; - } - - if (writePropertyName) - writer.WritePropertyName(GetPropertyName(node, manager)); - writer.WriteValue(node.Value); - break; - case XmlNodeType.XmlDeclaration: - IXmlDeclaration declaration = (IXmlDeclaration)node; - writer.WritePropertyName(GetPropertyName(node, manager)); - writer.WriteStartObject(); - - if (!string.IsNullOrEmpty(declaration.Version)) - { - writer.WritePropertyName("@version"); - writer.WriteValue(declaration.Version); - } - if (!string.IsNullOrEmpty(declaration.Encoding)) - { - writer.WritePropertyName("@encoding"); - writer.WriteValue(declaration.Encoding); - } - if (!string.IsNullOrEmpty(declaration.Standalone)) - { - writer.WritePropertyName("@standalone"); - writer.WriteValue(declaration.Standalone); - } - - writer.WriteEndObject(); - break; - default: - throw new JsonSerializationException("Unexpected XmlNodeType when serializing nodes: " + node.NodeType); - } - } - #endregion - - #region Reading - /// - /// Reads the JSON representation of the object. - /// - /// The to read from. - /// Type of the object. - /// The existing value of object being read. - /// The calling serializer. - /// The object value. - public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) - { - XmlNamespaceManager manager = new XmlNamespaceManager(new NameTable()); - IXmlDocument document = null; - IXmlNode rootNode = null; - -#if !NET20 - if (typeof(XObject).IsAssignableFrom(objectType)) - { - if (objectType != typeof (XDocument) && objectType != typeof (XElement)) - throw new JsonSerializationException("XmlNodeConverter only supports deserializing XDocument or XElement."); - - XDocument d = new XDocument(); - document = new XDocumentWrapper(d); - rootNode = document; - } -#endif -#if !(SILVERLIGHT || NETFX_CORE) - if (typeof(XmlNode).IsAssignableFrom(objectType)) - { - if (objectType != typeof (XmlDocument)) - throw new JsonSerializationException("XmlNodeConverter only supports deserializing XmlDocuments"); - - XmlDocument d = new XmlDocument(); - document = new XmlDocumentWrapper(d); - rootNode = document; - } -#endif - - if (document == null || rootNode == 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)) - { - //rootNode = document.CreateElement(DeserializeRootElementName); - //document.AppendChild(rootNode); - ReadElement(reader, document, rootNode, DeserializeRootElementName, manager); - } - else - { - reader.Read(); - DeserializeNode(reader, document, manager, rootNode); - } - -#if !NET20 - if (objectType == typeof(XElement)) - { - XElement element = (XElement)document.DocumentElement.WrappedNode; - element.Remove(); - - return element; - } -#endif - - return document.WrappedNode; - } - - private void DeserializeValue(JsonReader reader, IXmlDocument document, XmlNamespaceManager manager, string propertyName, IXmlNode currentNode) - { - switch (propertyName) - { - case TextName: - currentNode.AppendChild(document.CreateTextNode(reader.Value.ToString())); - break; - case CDataName: - currentNode.AppendChild(document.CreateCDataSection(reader.Value.ToString())); - break; - case WhitespaceName: - currentNode.AppendChild(document.CreateWhitespace(reader.Value.ToString())); - break; - case SignificantWhitespaceName: - currentNode.AppendChild(document.CreateSignificantWhitespace(reader.Value.ToString())); - break; - default: - // processing instructions and the xml declaration start with ? - if (!string.IsNullOrEmpty(propertyName) && propertyName[0] == '?') - { - CreateInstruction(reader, document, currentNode, propertyName); - } - else - { - 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); - } - 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 attributeNameValues = ReadAttributeElements(reader, manager); - - string elementPrefix = MiscellaneousUtils.GetPrefix(propertyName); - - IXmlElement element = CreateElement(propertyName, document, elementPrefix, manager); - - currentNode.AppendChild(element); - - // add attributes to newly created element - foreach (KeyValuePair nameValue in attributeNameValues) - { - string attributePrefix = MiscellaneousUtils.GetPrefix(nameValue.Key); - - IXmlNode attribute = (!string.IsNullOrEmpty(attributePrefix)) - ? document.CreateAttribute(nameValue.Key, manager.LookupNamespace(attributePrefix), nameValue.Value) - : document.CreateAttribute(nameValue.Key, nameValue.Value); - - element.SetAttributeNode(attribute); - } - - if (reader.TokenType == JsonToken.String - || reader.TokenType == JsonToken.Integer - || reader.TokenType == JsonToken.Float - || reader.TokenType == JsonToken.Boolean - || reader.TokenType == JsonToken.Date) - { - element.AppendChild(document.CreateTextNode(ConvertTokenToXmlValue(reader))); - } - else if (reader.TokenType == JsonToken.Null) - { - // empty element. do nothing - } - else - { - // finished element will have no children to deserialize - if (reader.TokenType != JsonToken.EndObject) - { - manager.PushScope(); - - DeserializeNode(reader, document, manager, element); - - manager.PopScope(); - } - } - } - - private string ConvertTokenToXmlValue(JsonReader reader) - { - if (reader.TokenType == JsonToken.String) - { - return reader.Value.ToString(); - } - else if (reader.TokenType == JsonToken.Integer) - { - return XmlConvert.ToString(Convert.ToInt64(reader.Value, CultureInfo.InvariantCulture)); - } - else if (reader.TokenType == JsonToken.Float) - { - return XmlConvert.ToString(Convert.ToDouble(reader.Value, CultureInfo.InvariantCulture)); - } - else if (reader.TokenType == JsonToken.Boolean) - { - return XmlConvert.ToString(Convert.ToBoolean(reader.Value, CultureInfo.InvariantCulture)); - } - else if (reader.TokenType == JsonToken.Date) - { - DateTime d = Convert.ToDateTime(reader.Value, CultureInfo.InvariantCulture); -#if !NETFX_CORE - return XmlConvert.ToString(d, DateTimeUtils.ToSerializationMode(d.Kind)); -#else - return XmlConvert.ToString(d); -#endif - } - else if (reader.TokenType == JsonToken.Null) - { - return null; - } - else - { - throw new Exception("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 elementPrefix = MiscellaneousUtils.GetPrefix(propertyName); - - IXmlElement nestedArrayElement = CreateElement(propertyName, document, elementPrefix, manager); - - currentNode.AppendChild(nestedArrayElement); - - int count = 0; - while (reader.Read() && reader.TokenType != JsonToken.EndArray) - { - DeserializeValue(reader, document, manager, propertyName, nestedArrayElement); - count++; - } - - if (WriteArrayAttribute) - { - AddJsonArrayAttribute(nestedArrayElement, document); - } - - if (count == 1 && WriteArrayAttribute) - { - IXmlElement arrayElement = nestedArrayElement.ChildNodes.CastValid().Single(n => n.LocalName == propertyName); - AddJsonArrayAttribute(arrayElement, document); - } - } - - private void AddJsonArrayAttribute(IXmlElement element, IXmlDocument document) - { - element.SetAttributeNode(document.CreateAttribute("json:Array", JsonNamespaceUri, "true")); - -#if !NET20 - // linq to xml doesn't automatically include prefixes via the namespace manager - if (element is XElementWrapper) - { - if (element.GetPrefixOfNamespace(JsonNamespaceUri) == null) - { - element.SetAttributeNode(document.CreateAttribute("xmlns:json", "http://www.w3.org/2000/xmlns/", JsonNamespaceUri)); - } - } -#endif - } - - private Dictionary ReadAttributeElements(JsonReader reader, XmlNamespaceManager manager) - { - Dictionary attributeNameValues = new Dictionary(); - bool finishedAttributes = false; - bool finishedElement = false; - - // a string token means the element only has a single text child - 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) - { - // read properties until first non-attribute is encountered - while (!finishedAttributes && !finishedElement && reader.Read()) - { - switch (reader.TokenType) - { - case JsonToken.PropertyName: - string attributeName = reader.Value.ToString(); - - if (!string.IsNullOrEmpty(attributeName)) - { - char firstChar = attributeName[0]; - string attributeValue; - - switch (firstChar) - { - case '@': - attributeName = attributeName.Substring(1); - reader.Read(); - attributeValue = ConvertTokenToXmlValue(reader); - attributeNameValues.Add(attributeName, attributeValue); - - string namespacePrefix; - if (IsNamespaceAttribute(attributeName, out namespacePrefix)) - { - manager.AddNamespace(namespacePrefix, attributeValue); - } - break; - case '$': - attributeName = attributeName.Substring(1); - reader.Read(); - attributeValue = reader.Value.ToString(); - - // check that JsonNamespaceUri is in scope - // if it isn't then add it to document and namespace manager - string jsonPrefix = manager.LookupPrefix(JsonNamespaceUri); - if (jsonPrefix == null) - { - // ensure that the prefix used is free - int? i = null; - while (manager.LookupNamespace("json" + i) != null) - { - i = i.GetValueOrDefault() + 1; - } - jsonPrefix = "json" + i; - - attributeNameValues.Add("xmlns:" + jsonPrefix, JsonNamespaceUri); - manager.AddNamespace(jsonPrefix, JsonNamespaceUri); - } - - attributeNameValues.Add(jsonPrefix + ":" + attributeName, attributeValue); - break; - default: - finishedAttributes = true; - break; - } - } - else - { - finishedAttributes = true; - } - - break; - case JsonToken.EndObject: - finishedElement = true; - break; - default: - throw new JsonSerializationException("Unexpected JsonToken: " + reader.TokenType); - } - } - } - - return attributeNameValues; - } - - private void CreateInstruction(JsonReader reader, IXmlDocument document, IXmlNode currentNode, string propertyName) - { - if (propertyName == DeclarationName) - { - 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 declaration = document.CreateXmlDeclaration(version, encoding, standalone); - currentNode.AppendChild(declaration); - } - else - { - IXmlNode instruction = document.CreateProcessingInstruction(propertyName.Substring(1), reader.Value.ToString()); - currentNode.AppendChild(instruction); - } - } - - private IXmlElement CreateElement(string elementName, IXmlDocument document, string elementPrefix, XmlNamespaceManager manager) - { - string ns = string.IsNullOrEmpty(elementPrefix) ? manager.DefaultNamespace : manager.LookupNamespace(elementPrefix); - - IXmlElement element = (!string.IsNullOrEmpty(ns)) ? document.CreateElement(elementName, ns) : document.CreateElement(elementName); - - return element; - } - - private void DeserializeNode(JsonReader reader, IXmlDocument document, XmlNamespaceManager manager, IXmlNode currentNode) - { - do - { - switch (reader.TokenType) - { - 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 count = 0; - while (reader.Read() && reader.TokenType != JsonToken.EndArray) - { - DeserializeValue(reader, document, manager, propertyName, currentNode); - count++; - } - - if (count == 1 && WriteArrayAttribute) - { - IXmlElement arrayElement = currentNode.ChildNodes.CastValid().Single(n => n.LocalName == propertyName); - AddJsonArrayAttribute(arrayElement, document); - } - } - else - { - DeserializeValue(reader, document, manager, propertyName, currentNode); - } - break; - case JsonToken.StartConstructor: - string constructorName = reader.Value.ToString(); - - while (reader.Read() && reader.TokenType != JsonToken.EndConstructor) - { - DeserializeValue(reader, document, manager, constructorName, currentNode); - } - break; - case JsonToken.Comment: - currentNode.AppendChild(document.CreateComment((string)reader.Value)); - break; - case JsonToken.EndObject: - case JsonToken.EndArray: - return; - default: - throw new JsonSerializationException("Unexpected JsonToken when deserializing node: " + reader.TokenType); - } - } while (reader.TokenType == JsonToken.PropertyName || reader.Read()); - // don't read if current token is a property. token was already read when parsing element attributes - } - - /// - /// Checks if the attributeName is a namespace attribute. - /// - /// Attribute name to test. - /// The attribute name prefix if it has one, otherwise an empty string. - /// True if attribute name is for a namespace attribute, otherwise false. - private bool IsNamespaceAttribute(string attributeName, out string prefix) - { - if (attributeName.StartsWith("xmlns", StringComparison.Ordinal)) - { - if (attributeName.Length == 5) - { - prefix = string.Empty; - return true; - } - else if (attributeName[5] == ':') - { - prefix = attributeName.Substring(6, attributeName.Length - 6); - return true; - } - } - prefix = null; - return false; - } - - private IEnumerable ValueAttributes(IEnumerable c) - { - return c.Where(a => a.NamespaceUri != JsonNamespaceUri); - } - #endregion - - /// - /// Determines whether this instance can convert the specified value type. - /// - /// Type of the value. - /// - /// true if this instance can convert the specified value type; otherwise, false. - /// - public override bool CanConvert(Type valueType) - { -#if !NET20 - if (typeof(XObject).IsAssignableFrom(valueType)) - return true; -#endif -#if !(SILVERLIGHT || NETFX_CORE) - if (typeof(XmlNode).IsAssignableFrom(valueType)) - return true; -#endif - - return false; - } - } -} +#region License +// Copyright (c) 2007 James Newton-King +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +#endregion + +#if (!SILVERLIGHT || WINDOWS_PHONE) +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Xml; +#if !NET20 +using System.Xml.Linq; +#endif +using Newtonsoft.Json.Utilities; +#if NET20 +using Newtonsoft.Json.Utilities.LinqBridge; +#else +using System.Linq; +#endif + +namespace Newtonsoft.Json.Converters +{ + #region XmlNodeWrappers +#if !SILVERLIGHT && !NETFX_CORE + internal class XmlDocumentWrapper : XmlNodeWrapper, IXmlDocument + { + private readonly XmlDocument _document; + + public XmlDocumentWrapper(XmlDocument document) + : base(document) + { + _document = document; + } + + public IXmlNode CreateComment(string data) + { + return new XmlNodeWrapper(_document.CreateComment(data)); + } + + public IXmlNode CreateTextNode(string text) + { + return new XmlNodeWrapper(_document.CreateTextNode(text)); + } + + public IXmlNode CreateCDataSection(string data) + { + return new XmlNodeWrapper(_document.CreateCDataSection(data)); + } + + public IXmlNode CreateWhitespace(string text) + { + return new XmlNodeWrapper(_document.CreateWhitespace(text)); + } + + public IXmlNode CreateSignificantWhitespace(string text) + { + return new XmlNodeWrapper(_document.CreateSignificantWhitespace(text)); + } + + public IXmlNode CreateXmlDeclaration(string version, string encoding, string standalone) + { + return new XmlNodeWrapper(_document.CreateXmlDeclaration(version, encoding, standalone)); + } + + public IXmlNode CreateProcessingInstruction(string target, string data) + { + return new XmlNodeWrapper(_document.CreateProcessingInstruction(target, data)); + } + + public IXmlElement CreateElement(string elementName) + { + return new XmlElementWrapper(_document.CreateElement(elementName)); + } + + public IXmlElement CreateElement(string qualifiedName, string namespaceUri) + { + return new XmlElementWrapper(_document.CreateElement(qualifiedName, namespaceUri)); + } + + public IXmlNode CreateAttribute(string name, string value) + { + XmlNodeWrapper attribute = new XmlNodeWrapper(_document.CreateAttribute(name)); + attribute.Value = value; + + return attribute; + } + + public IXmlNode CreateAttribute(string qualifiedName, string namespaceUri, string value) + { + XmlNodeWrapper attribute = new XmlNodeWrapper(_document.CreateAttribute(qualifiedName, namespaceUri)); + attribute.Value = value; + + return attribute; + } + + public IXmlElement DocumentElement + { + get + { + if (_document.DocumentElement == null) + return null; + + return new XmlElementWrapper(_document.DocumentElement); + } + } + } + + internal class XmlElementWrapper : XmlNodeWrapper, IXmlElement + { + private readonly XmlElement _element; + + public XmlElementWrapper(XmlElement element) + : base(element) + { + _element = element; + } + + public void SetAttributeNode(IXmlNode attribute) + { + XmlNodeWrapper xmlAttributeWrapper = (XmlNodeWrapper)attribute; + + _element.SetAttributeNode((XmlAttribute) xmlAttributeWrapper.WrappedNode); + } + + public string GetPrefixOfNamespace(string namespaceUri) + { + return _element.GetPrefixOfNamespace(namespaceUri); + } + } + + internal class XmlDeclarationWrapper : XmlNodeWrapper, IXmlDeclaration + { + private readonly XmlDeclaration _declaration; + + public XmlDeclarationWrapper(XmlDeclaration declaration) + : base(declaration) + { + _declaration = declaration; + } + + public string Version + { + get { return _declaration.Version; } + } + + public string Encoding + { + get { return _declaration.Encoding; } + set { _declaration.Encoding = value; } + } + + public string Standalone + { + get { return _declaration.Standalone; } + set { _declaration.Standalone = value; } + } + } + + internal class XmlNodeWrapper : IXmlNode + { + private readonly XmlNode _node; + + public XmlNodeWrapper(XmlNode node) + { + _node = node; + } + + public object WrappedNode + { + get { return _node; } + } + + public XmlNodeType NodeType + { + get { return _node.NodeType; } + } + + public string Name + { + get { return _node.Name; } + } + + public string LocalName + { + get { return _node.LocalName; } + } + + public IList ChildNodes + { + get { return _node.ChildNodes.Cast().Select(n => WrapNode(n)).ToList(); } + } + + private IXmlNode WrapNode(XmlNode node) + { + switch (node.NodeType) + { + case XmlNodeType.Element: + return new XmlElementWrapper((XmlElement) node); + case XmlNodeType.XmlDeclaration: + return new XmlDeclarationWrapper((XmlDeclaration) node); + default: + return new XmlNodeWrapper(node); + } + } + + public IList Attributes + { + get + { + if (_node.Attributes == null) + return null; + + return _node.Attributes.Cast().Select(a => WrapNode(a)).ToList(); + } + } + + public IXmlNode ParentNode + { + get + { + XmlNode node = (_node is XmlAttribute) + ? ((XmlAttribute) _node).OwnerElement + : _node.ParentNode; + + if (node == null) + return null; + + return WrapNode(node); + } + } + + public string Value + { + get { return _node.Value; } + set { _node.Value = value; } + } + + public IXmlNode AppendChild(IXmlNode newChild) + { + XmlNodeWrapper xmlNodeWrapper = (XmlNodeWrapper) newChild; + _node.AppendChild(xmlNodeWrapper._node); + + return newChild; + } + + public string Prefix + { + get { return _node.Prefix; } + } + + public string NamespaceUri + { + get { return _node.NamespaceURI; } + } + } +#endif + #endregion + + #region Interfaces + internal interface IXmlDocument : IXmlNode + { + IXmlNode CreateComment(string text); + IXmlNode CreateTextNode(string text); + IXmlNode CreateCDataSection(string data); + IXmlNode CreateWhitespace(string text); + IXmlNode CreateSignificantWhitespace(string text); + IXmlNode CreateXmlDeclaration(string version, string encoding, string standalone); + IXmlNode CreateProcessingInstruction(string target, string data); + IXmlElement CreateElement(string elementName); + IXmlElement CreateElement(string qualifiedName, string namespaceUri); + IXmlNode CreateAttribute(string name, string value); + IXmlNode CreateAttribute(string qualifiedName, string namespaceUri, string value); + + IXmlElement DocumentElement { get; } + } + + internal interface IXmlDeclaration : IXmlNode + { + string Version { get; } + string Encoding { get; set; } + string Standalone { get; set; } + } + + internal interface IXmlElement : IXmlNode + { + void SetAttributeNode(IXmlNode attribute); + string GetPrefixOfNamespace(string namespaceUri); + } + + internal interface IXmlNode + { + XmlNodeType NodeType { get; } + string LocalName { get; } + IList ChildNodes { get; } + IList Attributes { get; } + IXmlNode ParentNode { get; } + string Value { get; set; } + IXmlNode AppendChild(IXmlNode newChild); + string NamespaceUri { get; } + object WrappedNode { get; } + } + #endregion + + #region XNodeWrappers +#if !NET20 + internal class XDeclarationWrapper : XObjectWrapper, IXmlDeclaration + { + internal XDeclaration Declaration { get; private set; } + + public XDeclarationWrapper(XDeclaration declaration) + : base(null) + { + Declaration = declaration; + } + + public override XmlNodeType NodeType + { + get { return XmlNodeType.XmlDeclaration; } + } + + public string Version + { + get { return Declaration.Version; } + } + + public string Encoding + { + get { return Declaration.Encoding; } + set { Declaration.Encoding = value; } + } + + public string Standalone + { + get { return Declaration.Standalone; } + set { Declaration.Standalone = value; } + } + } + + internal class XDocumentWrapper : XContainerWrapper, IXmlDocument + { + private XDocument Document + { + get { return (XDocument)WrappedNode; } + } + + public XDocumentWrapper(XDocument document) + : base(document) + { + } + + public override IList ChildNodes + { + get + { + IList childNodes = base.ChildNodes; + + if (Document.Declaration != null) + childNodes.Insert(0, new XDeclarationWrapper(Document.Declaration)); + + return childNodes; + } + } + + public IXmlNode CreateComment(string text) + { + return new XObjectWrapper(new XComment(text)); + } + + public IXmlNode CreateTextNode(string text) + { + return new XObjectWrapper(new XText(text)); + } + + public IXmlNode CreateCDataSection(string data) + { + return new XObjectWrapper(new XCData(data)); + } + + public IXmlNode CreateWhitespace(string text) + { + return new XObjectWrapper(new XText(text)); + } + + public IXmlNode CreateSignificantWhitespace(string text) + { + return new XObjectWrapper(new XText(text)); + } + + public IXmlNode CreateXmlDeclaration(string version, string encoding, string standalone) + { + return new XDeclarationWrapper(new XDeclaration(version, encoding, standalone)); + } + + public IXmlNode CreateProcessingInstruction(string target, string data) + { + return new XProcessingInstructionWrapper(new XProcessingInstruction(target, data)); + } + + public IXmlElement CreateElement(string elementName) + { + return new XElementWrapper(new XElement(elementName)); + } + + public IXmlElement CreateElement(string qualifiedName, string namespaceUri) + { + string localName = MiscellaneousUtils.GetLocalName(qualifiedName); + return new XElementWrapper(new XElement(XName.Get(localName, namespaceUri))); + } + + public IXmlNode CreateAttribute(string name, string value) + { + return new XAttributeWrapper(new XAttribute(name, value)); + } + + public IXmlNode CreateAttribute(string qualifiedName, string namespaceUri, string value) + { + string localName = MiscellaneousUtils.GetLocalName(qualifiedName); + return new XAttributeWrapper(new XAttribute(XName.Get(localName, namespaceUri), value)); + } + + public IXmlElement DocumentElement + { + get + { + if (Document.Root == null) + return null; + + return new XElementWrapper(Document.Root); + } + } + + public override IXmlNode AppendChild(IXmlNode newChild) + { + XDeclarationWrapper declarationWrapper = newChild as XDeclarationWrapper; + if (declarationWrapper != null) + { + Document.Declaration = declarationWrapper.Declaration; + return declarationWrapper; + } + else + { + return base.AppendChild(newChild); + } + } + } + + internal class XTextWrapper : XObjectWrapper + { + private XText Text + { + get { return (XText)WrappedNode; } + } + + public XTextWrapper(XText text) + : base(text) + { + } + + public override string Value + { + get { return Text.Value; } + set { Text.Value = value; } + } + + public override IXmlNode ParentNode + { + get + { + if (Text.Parent == null) + return null; + + return XContainerWrapper.WrapNode(Text.Parent); + } + } + } + + internal class XCommentWrapper : XObjectWrapper + { + private XComment Text + { + get { return (XComment)WrappedNode; } + } + + public XCommentWrapper(XComment text) + : base(text) + { + } + + public override string Value + { + get { return Text.Value; } + set { Text.Value = value; } + } + + public override IXmlNode ParentNode + { + get + { + if (Text.Parent == null) + return null; + + return XContainerWrapper.WrapNode(Text.Parent); + } + } + } + + internal class XProcessingInstructionWrapper : XObjectWrapper + { + private XProcessingInstruction ProcessingInstruction + { + get { return (XProcessingInstruction)WrappedNode; } + } + + public XProcessingInstructionWrapper(XProcessingInstruction processingInstruction) + : base(processingInstruction) + { + } + + public override string LocalName + { + get { return ProcessingInstruction.Target; } + } + + public override string Value + { + get { return ProcessingInstruction.Data; } + set { ProcessingInstruction.Data = value; } + } + } + + internal class XContainerWrapper : XObjectWrapper + { + private XContainer Container + { + get { return (XContainer)WrappedNode; } + } + + public XContainerWrapper(XContainer container) + : base(container) + { + } + + public override IList ChildNodes + { + get { return Container.Nodes().Select(n => WrapNode(n)).ToList(); } + } + + public override IXmlNode ParentNode + { + get + { + if (Container.Parent == null) + return null; + + return WrapNode(Container.Parent); + } + } + + internal static IXmlNode WrapNode(XObject node) + { + if (node is XDocument) + return new XDocumentWrapper((XDocument)node); + else if (node is XElement) + return new XElementWrapper((XElement)node); + else if (node is XContainer) + return new XContainerWrapper((XContainer)node); + else if (node is XProcessingInstruction) + return new XProcessingInstructionWrapper((XProcessingInstruction)node); + else if (node is XText) + return new XTextWrapper((XText)node); + else if (node is XComment) + return new XCommentWrapper((XComment)node); + else if (node is XAttribute) + return new XAttributeWrapper((XAttribute) node); + else + return new XObjectWrapper(node); + } + + public override IXmlNode AppendChild(IXmlNode newChild) + { + Container.Add(newChild.WrappedNode); + return newChild; + } + } + + internal class XObjectWrapper : IXmlNode + { + private readonly XObject _xmlObject; + + public XObjectWrapper(XObject xmlObject) + { + _xmlObject = xmlObject; + } + + public object WrappedNode + { + get { return _xmlObject; } + } + + public virtual XmlNodeType NodeType + { + get { return _xmlObject.NodeType; } + } + + public virtual string LocalName + { + get { return null; } + } + + public virtual IList ChildNodes + { + get { return new List(); } + } + + public virtual IList Attributes + { + get { return null; } + } + + public virtual IXmlNode ParentNode + { + get { return null; } + } + + public virtual string Value + { + get { return null; } + set { throw new InvalidOperationException(); } + } + + public virtual IXmlNode AppendChild(IXmlNode newChild) + { + throw new InvalidOperationException(); + } + + public virtual string NamespaceUri + { + get { return null; } + } + } + + internal class XAttributeWrapper : XObjectWrapper + { + private XAttribute Attribute + { + get { return (XAttribute)WrappedNode; } + } + + public XAttributeWrapper(XAttribute attribute) + : base(attribute) + { + } + + public override string Value + { + get { return Attribute.Value; } + set { Attribute.Value = value; } + } + + public override string LocalName + { + get { return Attribute.Name.LocalName; } + } + + public override string NamespaceUri + { + get { return Attribute.Name.NamespaceName; } + } + + public override IXmlNode ParentNode + { + get + { + if (Attribute.Parent == null) + return null; + + return XContainerWrapper.WrapNode(Attribute.Parent); + } + } + } + + internal class XElementWrapper : XContainerWrapper, IXmlElement + { + private XElement Element + { + get { return (XElement) WrappedNode; } + } + + public XElementWrapper(XElement element) + : base(element) + { + } + + public void SetAttributeNode(IXmlNode attribute) + { + XObjectWrapper wrapper = (XObjectWrapper)attribute; + Element.Add(wrapper.WrappedNode); + } + + public override IList Attributes + { + get { return Element.Attributes().Select(a => new XAttributeWrapper(a)).Cast().ToList(); } + } + + public override string Value + { + get { return Element.Value; } + set { Element.Value = value; } + } + + public override string LocalName + { + get { return Element.Name.LocalName; } + } + + public override string NamespaceUri + { + get { return Element.Name.NamespaceName; } + } + + public string GetPrefixOfNamespace(string namespaceUri) + { + return Element.GetPrefixOfNamespace(namespaceUri); + } + } +#endif + #endregion + + /// + /// Converts XML to and from JSON. + /// + 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"; + + /// + /// Gets or sets the name of the root element to insert when deserializing to XML if the JSON structure has produces multiple root elements. + /// + /// The name of the deserialize root element. + public string DeserializeRootElementName { get; set; } + + /// + /// Gets or sets a flag 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 attibute is written to the XML; otherwise, false. + public bool WriteArrayAttribute { get; set; } + + /// + /// Gets or sets a value indicating whether to write the root JSON object. + /// + /// true if the JSON root object is omitted; otherwise, false. + public bool OmitRootObject { get; set; } + + #region Writing + /// + /// Writes the JSON representation of the object. + /// + /// The to write to. + /// The calling serializer. + /// The value. + 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 !NET20 + if (value is XObject) + return XContainerWrapper.WrapNode((XObject)value); +#endif +#if !(SILVERLIGHT || NETFX_CORE) + if (value is XmlNode) + return new XmlNodeWrapper((XmlNode)value); +#endif + + throw new ArgumentException("Value must be an XML object.", "value"); + } + + private void PushParentNamespaces(IXmlNode node, XmlNamespaceManager manager) + { + List parentElements = null; + + IXmlNode parent = node; + while ((parent = parent.ParentNode) != null) + { + if (parent.NodeType == XmlNodeType.Element) + { + if (parentElements == null) + parentElements = new List(); + + parentElements.Add(parent); + } + } + + if (parentElements != null) + { + parentElements.Reverse(); + + foreach (IXmlNode parentElement in parentElements) + { + manager.PushScope(); + foreach (IXmlNode attribute in parentElement.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 prefix = (node.NamespaceUri == null || (node.LocalName == "xmlns" && node.NamespaceUri == "http://www.w3.org/2000/xmlns/")) + ? null + : manager.LookupPrefix(node.NamespaceUri); + + if (!string.IsNullOrEmpty(prefix)) + return prefix + ":" + node.LocalName; + else + return node.LocalName; + } + + private string GetPropertyName(IXmlNode node, XmlNamespaceManager manager) + { + switch (node.NodeType) + { + case XmlNodeType.Attribute: + if (node.NamespaceUri == JsonNamespaceUri) + return "$" + node.LocalName; + else + return "@" + ResolveFullName(node, manager); + case XmlNodeType.CDATA: + return CDataName; + case XmlNodeType.Comment: + return CommentName; + case XmlNodeType.Element: + return ResolveFullName(node, manager); + case XmlNodeType.ProcessingInstruction: + return "?" + ResolveFullName(node, manager); + case XmlNodeType.XmlDeclaration: + return DeclarationName; + case XmlNodeType.SignificantWhitespace: + return SignificantWhitespaceName; + case XmlNodeType.Text: + return TextName; + case XmlNodeType.Whitespace: + return WhitespaceName; + default: + throw new JsonSerializationException("Unexpected XmlNodeType when getting node name: " + node.NodeType); + } + } + + private bool IsArray(IXmlNode node) + { + IXmlNode jsonArrayAttribute = (node.Attributes != null) + ? node.Attributes.SingleOrDefault(a => a.LocalName == "Array" && a.NamespaceUri == JsonNamespaceUri) + : null; + + return (jsonArrayAttribute != null && XmlConvert.ToBoolean(jsonArrayAttribute.Value)); + } + + private void SerializeGroupedNodes(JsonWriter writer, IXmlNode node, XmlNamespaceManager manager, bool writePropertyName) + { + // group nodes together by name + Dictionary> nodesGroupedByName = new Dictionary>(); + + for (int i = 0; i < node.ChildNodes.Count; i++) + { + IXmlNode childNode = node.ChildNodes[i]; + string nodeName = GetPropertyName(childNode, manager); + + List nodes; + if (!nodesGroupedByName.TryGetValue(nodeName, out nodes)) + { + nodes = new List(); + nodesGroupedByName.Add(nodeName, nodes); + } + + nodes.Add(childNode); + } + + // loop through grouped nodes. write single name instances as normal, + // write multiple names together in an array + foreach (KeyValuePair> nodeNameGroup in nodesGroupedByName) + { + List groupedNodes = nodeNameGroup.Value; + bool writeArray; + + if (groupedNodes.Count == 1) + { + writeArray = IsArray(groupedNodes[0]); + } + else + { + writeArray = true; + } + + if (!writeArray) + { + SerializeNode(writer, groupedNodes[0], manager, writePropertyName); + } + else + { + string elementNames = nodeNameGroup.Key; + + if (writePropertyName) + writer.WritePropertyName(elementNames); + + writer.WriteStartArray(); + + for (int i = 0; i < groupedNodes.Count; i++) + { + SerializeNode(writer, groupedNodes[i], 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(n => n.LocalName == node.LocalName) && node.ChildNodes.Count > 0) + { + SerializeGroupedNodes(writer, node, manager, false); + } + else + { + foreach (IXmlNode attribute in node.Attributes) + { + if (attribute.NamespaceUri == "http://www.w3.org/2000/xmlns/") + { + string prefix = (attribute.LocalName != "xmlns") + ? attribute.LocalName + : string.Empty; + + manager.AddNamespace(prefix, attribute.Value); + } + } + + if (writePropertyName) + writer.WritePropertyName(GetPropertyName(node, manager)); + + if (!ValueAttributes(node.Attributes).Any() && node.ChildNodes.Count == 1 + && node.ChildNodes[0].NodeType == XmlNodeType.Text) + { + // write elements with a single text child as a name value pair + writer.WriteValue(node.ChildNodes[0].Value); + } + else if (node.ChildNodes.Count == 0 && CollectionUtils.IsNullOrEmpty(node.Attributes)) + { + // empty element + 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(); + } + } + + 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 == JsonNamespaceUri) + return; + + if (node.NamespaceUri == JsonNamespaceUri) + { + if (node.LocalName == "Array") + return; + } + + if (writePropertyName) + writer.WritePropertyName(GetPropertyName(node, manager)); + writer.WriteValue(node.Value); + break; + case XmlNodeType.XmlDeclaration: + IXmlDeclaration declaration = (IXmlDeclaration)node; + writer.WritePropertyName(GetPropertyName(node, manager)); + writer.WriteStartObject(); + + if (!string.IsNullOrEmpty(declaration.Version)) + { + writer.WritePropertyName("@version"); + writer.WriteValue(declaration.Version); + } + if (!string.IsNullOrEmpty(declaration.Encoding)) + { + writer.WritePropertyName("@encoding"); + writer.WriteValue(declaration.Encoding); + } + if (!string.IsNullOrEmpty(declaration.Standalone)) + { + writer.WritePropertyName("@standalone"); + writer.WriteValue(declaration.Standalone); + } + + writer.WriteEndObject(); + break; + default: + throw new JsonSerializationException("Unexpected XmlNodeType when serializing nodes: " + node.NodeType); + } + } + #endregion + + #region Reading + /// + /// Reads the JSON representation of the object. + /// + /// The to read from. + /// Type of the object. + /// The existing value of object being read. + /// The calling serializer. + /// The object value. + public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) + { + XmlNamespaceManager manager = new XmlNamespaceManager(new NameTable()); + IXmlDocument document = null; + IXmlNode rootNode = null; + +#if !NET20 + if (typeof(XObject).IsAssignableFrom(objectType)) + { + if (objectType != typeof (XDocument) && objectType != typeof (XElement)) + throw new JsonSerializationException("XmlNodeConverter only supports deserializing XDocument or XElement."); + + XDocument d = new XDocument(); + document = new XDocumentWrapper(d); + rootNode = document; + } +#endif +#if !(SILVERLIGHT || NETFX_CORE) + if (typeof(XmlNode).IsAssignableFrom(objectType)) + { + if (objectType != typeof (XmlDocument)) + throw new JsonSerializationException("XmlNodeConverter only supports deserializing XmlDocuments"); + + XmlDocument d = new XmlDocument(); + document = new XmlDocumentWrapper(d); + rootNode = document; + } +#endif + + if (document == null || rootNode == 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)) + { + //rootNode = document.CreateElement(DeserializeRootElementName); + //document.AppendChild(rootNode); + ReadElement(reader, document, rootNode, DeserializeRootElementName, manager); + } + else + { + reader.Read(); + DeserializeNode(reader, document, manager, rootNode); + } + +#if !NET20 + if (objectType == typeof(XElement)) + { + XElement element = (XElement)document.DocumentElement.WrappedNode; + element.Remove(); + + return element; + } +#endif + + return document.WrappedNode; + } + + private void DeserializeValue(JsonReader reader, IXmlDocument document, XmlNamespaceManager manager, string propertyName, IXmlNode currentNode) + { + switch (propertyName) + { + case TextName: + currentNode.AppendChild(document.CreateTextNode(reader.Value.ToString())); + break; + case CDataName: + currentNode.AppendChild(document.CreateCDataSection(reader.Value.ToString())); + break; + case WhitespaceName: + currentNode.AppendChild(document.CreateWhitespace(reader.Value.ToString())); + break; + case SignificantWhitespaceName: + currentNode.AppendChild(document.CreateSignificantWhitespace(reader.Value.ToString())); + break; + default: + // processing instructions and the xml declaration start with ? + if (!string.IsNullOrEmpty(propertyName) && propertyName[0] == '?') + { + CreateInstruction(reader, document, currentNode, propertyName); + } + else + { + 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); + } + 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 attributeNameValues = ReadAttributeElements(reader, manager); + + string elementPrefix = MiscellaneousUtils.GetPrefix(propertyName); + + if (propertyName.StartsWith("@")) + { + var attributeName = propertyName.Substring(1); + var attributeValue = reader.Value.ToString(); + + var attributePrefix = MiscellaneousUtils.GetPrefix(attributeName); + + var attribute = (!string.IsNullOrEmpty(attributePrefix)) + ? document.CreateAttribute(attributeName, manager.LookupNamespace(attributePrefix), attributeValue) + : document.CreateAttribute(attributeName, attributeValue); + + ((IXmlElement)currentNode).SetAttributeNode(attribute); + } + else + { + IXmlElement element = CreateElement(propertyName, document, elementPrefix, manager); + + currentNode.AppendChild(element); + + // add attributes to newly created element + foreach (KeyValuePair nameValue in attributeNameValues) + { + string attributePrefix = MiscellaneousUtils.GetPrefix(nameValue.Key); + + IXmlNode attribute = (!string.IsNullOrEmpty(attributePrefix)) + ? document.CreateAttribute(nameValue.Key, manager.LookupNamespace(attributePrefix), nameValue.Value) + : document.CreateAttribute(nameValue.Key, nameValue.Value); + + element.SetAttributeNode(attribute); + } + + if (reader.TokenType == JsonToken.String + || reader.TokenType == JsonToken.Integer + || reader.TokenType == JsonToken.Float + || reader.TokenType == JsonToken.Boolean + || reader.TokenType == JsonToken.Date) + { + element.AppendChild(document.CreateTextNode(ConvertTokenToXmlValue(reader))); + } + else if (reader.TokenType == JsonToken.Null) + { + // empty element. do nothing + } + else + { + // finished element will have no children to deserialize + if (reader.TokenType != JsonToken.EndObject) + { + manager.PushScope(); + + DeserializeNode(reader, document, manager, element); + + manager.PopScope(); + } + } + } + } + + private string ConvertTokenToXmlValue(JsonReader reader) + { + if (reader.TokenType == JsonToken.String) + { + return reader.Value.ToString(); + } + else if (reader.TokenType == JsonToken.Integer) + { + return XmlConvert.ToString(Convert.ToInt64(reader.Value, CultureInfo.InvariantCulture)); + } + else if (reader.TokenType == JsonToken.Float) + { + return XmlConvert.ToString(Convert.ToDouble(reader.Value, CultureInfo.InvariantCulture)); + } + else if (reader.TokenType == JsonToken.Boolean) + { + return XmlConvert.ToString(Convert.ToBoolean(reader.Value, CultureInfo.InvariantCulture)); + } + else if (reader.TokenType == JsonToken.Date) + { + DateTime d = Convert.ToDateTime(reader.Value, CultureInfo.InvariantCulture); +#if !NETFX_CORE + return XmlConvert.ToString(d, DateTimeUtils.ToSerializationMode(d.Kind)); +#else + return XmlConvert.ToString(d); +#endif + } + else if (reader.TokenType == JsonToken.Null) + { + return null; + } + else + { + throw new Exception("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 elementPrefix = MiscellaneousUtils.GetPrefix(propertyName); + + IXmlElement nestedArrayElement = CreateElement(propertyName, document, elementPrefix, manager); + + currentNode.AppendChild(nestedArrayElement); + + int count = 0; + while (reader.Read() && reader.TokenType != JsonToken.EndArray) + { + DeserializeValue(reader, document, manager, propertyName, nestedArrayElement); + count++; + } + + if (WriteArrayAttribute) + { + AddJsonArrayAttribute(nestedArrayElement, document); + } + + if (count == 1 && WriteArrayAttribute) + { + IXmlElement arrayElement = nestedArrayElement.ChildNodes.CastValid().Single(n => n.LocalName == propertyName); + AddJsonArrayAttribute(arrayElement, document); + } + } + + private void AddJsonArrayAttribute(IXmlElement element, IXmlDocument document) + { + element.SetAttributeNode(document.CreateAttribute("json:Array", JsonNamespaceUri, "true")); + +#if !NET20 + // linq to xml doesn't automatically include prefixes via the namespace manager + if (element is XElementWrapper) + { + if (element.GetPrefixOfNamespace(JsonNamespaceUri) == null) + { + element.SetAttributeNode(document.CreateAttribute("xmlns:json", "http://www.w3.org/2000/xmlns/", JsonNamespaceUri)); + } + } +#endif + } + + private Dictionary ReadAttributeElements(JsonReader reader, XmlNamespaceManager manager) + { + Dictionary attributeNameValues = new Dictionary(); + bool finishedAttributes = false; + bool finishedElement = false; + + // a string token means the element only has a single text child + 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) + { + // read properties until first non-attribute is encountered + while (!finishedAttributes && !finishedElement && reader.Read()) + { + switch (reader.TokenType) + { + case JsonToken.PropertyName: + string attributeName = reader.Value.ToString(); + + if (!string.IsNullOrEmpty(attributeName)) + { + char firstChar = attributeName[0]; + string attributeValue; + + switch (firstChar) + { + case '@': + attributeName = attributeName.Substring(1); + reader.Read(); + attributeValue = ConvertTokenToXmlValue(reader); + attributeNameValues.Add(attributeName, attributeValue); + + string namespacePrefix; + if (IsNamespaceAttribute(attributeName, out namespacePrefix)) + { + manager.AddNamespace(namespacePrefix, attributeValue); + } + break; + case '$': + attributeName = attributeName.Substring(1); + reader.Read(); + attributeValue = reader.Value.ToString(); + + // check that JsonNamespaceUri is in scope + // if it isn't then add it to document and namespace manager + string jsonPrefix = manager.LookupPrefix(JsonNamespaceUri); + if (jsonPrefix == null) + { + // ensure that the prefix used is free + int? i = null; + while (manager.LookupNamespace("json" + i) != null) + { + i = i.GetValueOrDefault() + 1; + } + jsonPrefix = "json" + i; + + attributeNameValues.Add("xmlns:" + jsonPrefix, JsonNamespaceUri); + manager.AddNamespace(jsonPrefix, JsonNamespaceUri); + } + + attributeNameValues.Add(jsonPrefix + ":" + attributeName, attributeValue); + break; + default: + finishedAttributes = true; + break; + } + } + else + { + finishedAttributes = true; + } + + break; + case JsonToken.EndObject: + finishedElement = true; + break; + default: + throw new JsonSerializationException("Unexpected JsonToken: " + reader.TokenType); + } + } + } + + return attributeNameValues; + } + + private void CreateInstruction(JsonReader reader, IXmlDocument document, IXmlNode currentNode, string propertyName) + { + if (propertyName == DeclarationName) + { + 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 declaration = document.CreateXmlDeclaration(version, encoding, standalone); + currentNode.AppendChild(declaration); + } + else + { + IXmlNode instruction = document.CreateProcessingInstruction(propertyName.Substring(1), reader.Value.ToString()); + currentNode.AppendChild(instruction); + } + } + + private IXmlElement CreateElement(string elementName, IXmlDocument document, string elementPrefix, XmlNamespaceManager manager) + { + string ns = string.IsNullOrEmpty(elementPrefix) ? manager.DefaultNamespace : manager.LookupNamespace(elementPrefix); + + IXmlElement element = (!string.IsNullOrEmpty(ns)) ? document.CreateElement(elementName, ns) : document.CreateElement(elementName); + + return element; + } + + private void DeserializeNode(JsonReader reader, IXmlDocument document, XmlNamespaceManager manager, IXmlNode currentNode) + { + do + { + switch (reader.TokenType) + { + 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 count = 0; + while (reader.Read() && reader.TokenType != JsonToken.EndArray) + { + DeserializeValue(reader, document, manager, propertyName, currentNode); + count++; + } + + if (count == 1 && WriteArrayAttribute) + { + IXmlElement arrayElement = currentNode.ChildNodes.CastValid().Single(n => n.LocalName == propertyName); + AddJsonArrayAttribute(arrayElement, document); + } + } + else + { + DeserializeValue(reader, document, manager, propertyName, currentNode); + } + break; + case JsonToken.StartConstructor: + string constructorName = reader.Value.ToString(); + + while (reader.Read() && reader.TokenType != JsonToken.EndConstructor) + { + DeserializeValue(reader, document, manager, constructorName, currentNode); + } + break; + case JsonToken.Comment: + currentNode.AppendChild(document.CreateComment((string)reader.Value)); + break; + case JsonToken.EndObject: + case JsonToken.EndArray: + return; + default: + throw new JsonSerializationException("Unexpected JsonToken when deserializing node: " + reader.TokenType); + } + } while (reader.TokenType == JsonToken.PropertyName || reader.Read()); + // don't read if current token is a property. token was already read when parsing element attributes + } + + /// + /// Checks if the attributeName is a namespace attribute. + /// + /// Attribute name to test. + /// The attribute name prefix if it has one, otherwise an empty string. + /// True if attribute name is for a namespace attribute, otherwise false. + private bool IsNamespaceAttribute(string attributeName, out string prefix) + { + if (attributeName.StartsWith("xmlns", StringComparison.Ordinal)) + { + if (attributeName.Length == 5) + { + prefix = string.Empty; + return true; + } + else if (attributeName[5] == ':') + { + prefix = attributeName.Substring(6, attributeName.Length - 6); + return true; + } + } + prefix = null; + return false; + } + + private IEnumerable ValueAttributes(IEnumerable c) + { + return c.Where(a => a.NamespaceUri != JsonNamespaceUri); + } + #endregion + + /// + /// Determines whether this instance can convert the specified value type. + /// + /// Type of the value. + /// + /// true if this instance can convert the specified value type; otherwise, false. + /// + public override bool CanConvert(Type valueType) + { +#if !NET20 + if (typeof(XObject).IsAssignableFrom(valueType)) + return true; +#endif +#if !(SILVERLIGHT || NETFX_CORE) + if (typeof(XmlNode).IsAssignableFrom(valueType)) + return true; +#endif + + return false; + } + } +} #endif \ No newline at end of file diff --git a/Src/Newtonsoft.Json/Properties/AssemblyInfo.cs b/Src/Newtonsoft.Json/Properties/AssemblyInfo.cs index ba840dea..99d1f056 100644 --- a/Src/Newtonsoft.Json/Properties/AssemblyInfo.cs +++ b/Src/Newtonsoft.Json/Properties/AssemblyInfo.cs @@ -1,94 +1,94 @@ -#region License -// Copyright (c) 2007 James Newton-King -// -// Permission is hereby granted, free of charge, to any person -// obtaining a copy of this software and associated documentation -// files (the "Software"), to deal in the Software without -// restriction, including without limitation the rights to use, -// copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following -// conditions: -// -// The above copyright notice and this permission notice shall be -// included in all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES -// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT -// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, -// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR -// OTHER DEALINGS IN THE SOFTWARE. -#endregion - -using System; -using System.Reflection; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; -using System.Security; - -// General Information about an assembly is controlled through the following -// set of attributes. Change these attribute values to modify the information -// associated with an assembly. -#if WINDOWS_PHONE -[assembly: AssemblyTitle("Json.NET Windows Phone")] -#elif SILVERLIGHT -[assembly: AssemblyTitle("Json.NET Silverlight")] -#elif PocketPC -[assembly: AssemblyTitle("Json.NET Compact")] -#elif NETFX_CORE -[assembly: AssemblyTitle("Json.NET Metro")] -[assembly: AllowPartiallyTrustedCallers] -#elif NET20 -[assembly: AssemblyTitle("Json.NET .NET 2.0")] -[assembly: AllowPartiallyTrustedCallers] -#elif NET35 -[assembly: AssemblyTitle("Json.NET .NET 3.5")] -[assembly: AllowPartiallyTrustedCallers] -#else -[assembly: AssemblyTitle("Json.NET")] -[assembly: AllowPartiallyTrustedCallers] -#endif - -#if !SIGNED -[assembly: InternalsVisibleTo("Newtonsoft.Json.Tests")] -#else -[assembly: InternalsVisibleTo("Newtonsoft.Json.Tests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f561df277c6c0b497d629032b410cdcf286e537c054724f7ffa0164345f62b3e642029d7a80cc351918955328c4adc8a048823ef90b0cf38ea7db0d729caf2b633c3babe08b0310198c1081995c19029bc675193744eab9d7345b8a67258ec17d112cebdbbb2a281487dceeafb9d83aa930f32103fbe1d2911425bc5744002c7")] -#endif - -[assembly: InternalsVisibleTo("Newtonsoft.Json.Dynamic, PublicKey=0024000004800000940000000602000000240000525341310004000001000100cbd8d53b9d7de30f1f1278f636ec462cf9c254991291e66ebb157a885638a517887633b898ccbcf0d5c5ff7be85a6abe9e765d0ac7cd33c68dac67e7e64530e8222101109f154ab14a941c490ac155cd1d4fcba0fabb49016b4ef28593b015cab5937da31172f03f67d09edda404b88a60023f062ae71d0b2e4438b74cc11dc9")] - -[assembly: AssemblyDescription("Json.NET is a popular high-performance JSON framework for .NET")] -[assembly: AssemblyConfiguration("")] -[assembly: AssemblyCompany("Newtonsoft")] -[assembly: AssemblyProduct("Json.NET")] -[assembly: AssemblyCopyright("Copyright © James Newton-King 2008")] -[assembly: AssemblyTrademark("")] -[assembly: AssemblyCulture("")] - - -// Setting ComVisible to false makes the types in this assembly not visible -// to COM componenets. If you need to access a type in this assembly from -// COM, set the ComVisible attribute to true on that type. -[assembly: ComVisible(false)] - -// The following GUID is for the ID of the typelib if this project is exposed to COM -[assembly: Guid("9ca358aa-317b-4925-8ada-4a29e943a363")] - -// Version information for an assembly consists of the following four values: -// -// Major Version -// Minor Version -// Build Number -// Revision -// -// You can specify all the values or you can default the Revision and Build Numbers -// by using the '*' as shown below: -[assembly: AssemblyVersion("4.5.0.0")] -#if !PocketPC -[assembly: AssemblyFileVersion("4.5.1.14719")] -#endif - -[assembly: CLSCompliant(true)] +#region License +// Copyright (c) 2007 James Newton-King +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +#endregion + +using System; +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using System.Security; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +#if WINDOWS_PHONE +[assembly: AssemblyTitle("Json.NET Windows Phone")] +#elif SILVERLIGHT +[assembly: AssemblyTitle("Json.NET Silverlight")] +#elif PocketPC +[assembly: AssemblyTitle("Json.NET Compact")] +#elif NETFX_CORE +[assembly: AssemblyTitle("Json.NET Metro")] +[assembly: AllowPartiallyTrustedCallers] +#elif NET20 +[assembly: AssemblyTitle("Json.NET .NET 2.0")] +[assembly: AllowPartiallyTrustedCallers] +#elif NET35 +[assembly: AssemblyTitle("Json.NET .NET 3.5")] +[assembly: AllowPartiallyTrustedCallers] +#else +[assembly: AssemblyTitle("Json.NET")] +[assembly: AllowPartiallyTrustedCallers] +#endif + +#if !SIGNED +[assembly: InternalsVisibleTo("Newtonsoft.Json.Tests")] +#else +[assembly: InternalsVisibleTo("Newtonsoft.Json.Tests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f561df277c6c0b497d629032b410cdcf286e537c054724f7ffa0164345f62b3e642029d7a80cc351918955328c4adc8a048823ef90b0cf38ea7db0d729caf2b633c3babe08b0310198c1081995c19029bc675193744eab9d7345b8a67258ec17d112cebdbbb2a281487dceeafb9d83aa930f32103fbe1d2911425bc5744002c7")] +#endif + +[assembly: InternalsVisibleTo("Newtonsoft.Json.Dynamic, PublicKey=0024000004800000940000000602000000240000525341310004000001000100cbd8d53b9d7de30f1f1278f636ec462cf9c254991291e66ebb157a885638a517887633b898ccbcf0d5c5ff7be85a6abe9e765d0ac7cd33c68dac67e7e64530e8222101109f154ab14a941c490ac155cd1d4fcba0fabb49016b4ef28593b015cab5937da31172f03f67d09edda404b88a60023f062ae71d0b2e4438b74cc11dc9")] + +[assembly: AssemblyDescription("Json.NET is a popular high-performance JSON framework for .NET")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("Newtonsoft")] +[assembly: AssemblyProduct("Json.NET")] +[assembly: AssemblyCopyright("Copyright © James Newton-King 2008")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM componenets. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("9ca358aa-317b-4925-8ada-4a29e943a363")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Revision and Build Numbers +// by using the '*' as shown below: +[assembly: AssemblyVersion("4.5.0.0")] +#if !PocketPC +[assembly: AssemblyFileVersion("4.5.1.14719")] +#endif + +[assembly: CLSCompliant(true)]