diff --git a/Src/Newtonsoft.Json.Tests/Converters/XmlNodeConverterTest.cs b/Src/Newtonsoft.Json.Tests/Converters/XmlNodeConverterTest.cs index a446d61d..24a93032 100644 --- a/Src/Newtonsoft.Json.Tests/Converters/XmlNodeConverterTest.cs +++ b/Src/Newtonsoft.Json.Tests/Converters/XmlNodeConverterTest.cs @@ -32,11 +32,71 @@ 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 { 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) + { + JsonTextReader reader; + + reader = new JsonTextReader(new StringReader(json)); + reader.Read(); + XmlNodeConverter converter = new XmlNodeConverter(); + + XmlNode node = (XmlNode)converter.ReadJson(reader, typeof (XmlDocument), new JsonSerializer()); + +#if !NET20 + string xmlText = node.OuterXml; + + reader = new JsonTextReader(new StringReader(json)); + reader.Read(); + XDocument d = (XDocument) converter.ReadJson(reader, typeof (XDocument), 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() { @@ -53,19 +113,7 @@ namespace Newtonsoft.Json.Tests.Converters XmlDocument doc = new XmlDocument(); doc.LoadXml(xml); - StringWriter sw = new StringWriter(); - - using (JsonWriter jsonWriter = new JsonTextWriter(sw)) - { - jsonWriter.Formatting = Formatting.Indented; - - JsonSerializer jsonSerializer = new JsonSerializer(); - jsonSerializer.Converters.Add(new XmlNodeConverter()); - - jsonSerializer.Serialize(jsonWriter, doc); - } - - string jsonText = sw.ToString(); + string jsonText = SerializeXmlNode(doc); string expected = @"{ ""?xml"": { ""@version"": ""1.0"", @@ -119,8 +167,20 @@ namespace Newtonsoft.Json.Tests.Converters Console.WriteLine("SerializeNodeTypes"); + string xml = @" + + + +"; + + XmlDocument document = new XmlDocument(); + document.LoadXml(xml); + // XmlAttribute - XmlAttribute attribute = doc.CreateAttribute("msdata:IsDataSet"); + XmlAttribute attribute = document.DocumentElement.ChildNodes[0].Attributes["IsDataSet", "urn:schemas-microsoft-com:xml-msdata"]; attribute.Value = "true"; jsonText = JsonConvert.SerializeXmlNode(attribute); @@ -128,6 +188,14 @@ namespace Newtonsoft.Json.Tests.Converters 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"""); @@ -148,16 +216,33 @@ namespace Newtonsoft.Json.Tests.Converters // XmlElement - XmlElement element = doc.CreateElement("xs:Choice"); - element.SetAttributeNode(attribute); + 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); - jsonText = JsonConvert.SerializeXmlNode(element); + doc.AppendChild(element); - Console.WriteLine(jsonText); - Assert.AreEqual(@"{""xs:Choice"":{""@msdata:IsDataSet"":""true"",""?xml-stylesheet"":""href=\""classic.xsl\"" type=\""text/xml\"""",""#cdata-section"":""true""}}", jsonText); + 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] @@ -210,9 +295,56 @@ namespace Newtonsoft.Json.Tests.Converters XmlDocument doc = new XmlDocument(); doc.LoadXml(xml); - string jsonText = JsonConvert.SerializeXmlNode(doc); + string jsonText = SerializeXmlNode(doc); - XmlDocument deserializedDoc = (XmlDocument)JsonConvert.DeserializeXmlNode(jsonText); + 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); @@ -244,7 +376,7 @@ namespace Newtonsoft.Json.Tests.Converters } }"; - XmlDocument doc = (XmlDocument)JsonConvert.DeserializeXmlNode(jsonText); + XmlDocument doc = (XmlDocument)DeserializeXmlNode(jsonText); string expected = @" @@ -295,9 +427,9 @@ namespace Newtonsoft.Json.Tests.Converters XmlDocument doc = new XmlDocument(); doc.LoadXml(xml); - string jsonText = JsonConvert.SerializeXmlNode(doc); + string jsonText = SerializeXmlNode(doc); - XmlDocument newDoc = (XmlDocument)JsonConvert.DeserializeXmlNode(jsonText); + XmlDocument newDoc = (XmlDocument)DeserializeXmlNode(jsonText); Assert.AreEqual(doc.InnerXml, newDoc.InnerXml); } @@ -320,11 +452,11 @@ namespace Newtonsoft.Json.Tests.Converters XmlDocument doc = new XmlDocument(); doc.LoadXml(xml); - string jsonText = JsonConvert.SerializeXmlNode(doc); + string jsonText = SerializeXmlNode(doc); Console.WriteLine(jsonText); - XmlDocument newDoc = (XmlDocument)JsonConvert.DeserializeXmlNode(jsonText); + XmlDocument newDoc = (XmlDocument)DeserializeXmlNode(jsonText); Assert.AreEqual(doc.InnerXml, newDoc.InnerXml); } @@ -334,7 +466,7 @@ namespace Newtonsoft.Json.Tests.Converters { 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)JsonConvert.DeserializeXmlNode(jsonText); + XmlDocument newDoc = (XmlDocument)DeserializeXmlNode(jsonText); string expected = @"2.599true2000-03-30T00:00:00Z"; @@ -360,14 +492,25 @@ namespace Newtonsoft.Json.Tests.Converters { string jsonText = @"{root:{r:new Date(34343, 55)}}"; - XmlDocument newDoc = (XmlDocument)JsonConvert.DeserializeXmlNode(jsonText); + XmlDocument newDoc = (XmlDocument)DeserializeXmlNode(jsonText); - string expected = @"<-Date>34343<-Date>55"; + string expected = @"3434355"; Assert.AreEqual(expected, newDoc.InnerXml); - string json = JsonConvert.SerializeXmlNode(newDoc); - Assert.AreEqual(@"{""root"":{""r"":new Date(""34343"",""55"")}}", json); + string json = SerializeXmlNode(newDoc); + expected = @"{ + ""root"": { + ""r"": { + ""Date"": [ + ""34343"", + ""55"" + ] + } + } +}"; + + Assert.AreEqual(expected, json); } [Test] @@ -384,8 +527,20 @@ namespace Newtonsoft.Json.Tests.Converters XmlDocument arrayDoc = new XmlDocument(); arrayDoc.LoadXml(arrayXml); - string arrayJsonText = JsonConvert.SerializeXmlNode(arrayDoc); - Assert.AreEqual(@"{""root"":{""person"":{""@id"":""1"",""name"":""Alan"",""url"":""http://www.google.com"",""role"":[""Admin""]}}}", arrayJsonText); + string arrayJsonText = SerializeXmlNode(arrayDoc); + string expected = @"{ + ""root"": { + ""person"": { + ""@id"": ""1"", + ""name"": ""Alan"", + ""url"": ""http://www.google.com"", + ""role"": [ + ""Admin"" + ] + } + } +}"; + Assert.AreEqual(expected, arrayJsonText); arrayXml = @" @@ -399,8 +554,21 @@ namespace Newtonsoft.Json.Tests.Converters arrayDoc = new XmlDocument(); arrayDoc.LoadXml(arrayXml); - arrayJsonText = JsonConvert.SerializeXmlNode(arrayDoc); - Assert.AreEqual(@"{""root"":{""person"":{""@id"":""1"",""name"":""Alan"",""url"":""http://www.google.com"",""role"":[""Admin1"",""Admin2""]}}}", arrayJsonText); + arrayJsonText = SerializeXmlNode(arrayDoc); + expected = @"{ + ""root"": { + ""person"": { + ""@id"": ""1"", + ""name"": ""Alan"", + ""url"": ""http://www.google.com"", + ""role"": [ + ""Admin1"", + ""Admin2"" + ] + } + } +}"; + Assert.AreEqual(expected, arrayJsonText); arrayXml = @" @@ -413,58 +581,54 @@ namespace Newtonsoft.Json.Tests.Converters arrayDoc = new XmlDocument(); arrayDoc.LoadXml(arrayXml); - arrayJsonText = JsonConvert.SerializeXmlNode(arrayDoc); - Assert.AreEqual(@"{""root"":{""person"":{""@id"":""1"",""name"":""Alan"",""url"":""http://www.google.com"",""role"":""Admin1""}}}", arrayJsonText); + 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 MultipleRootProperties() + public void MultipleRootPropertiesXmlDocument() { - string strJSON = @"{ -""count"": 773840,""photos"": [ -{ -""photo_id"": 532693, -""photo_title"": ""Wheatfield in afternoon light"", -""photo_url"": ""http://www.panoramio.com/photo/532693"", -""photofileurl"": ""http://static2.bareka.com/photos/medium/532693.jpg"", -""longitude"": 11.280727, -""latitude"": 59.643198, -""width"": 500, -""height"": 333, -""upload_date"": ""22 January 2007"", -""owner_id"": 39160, -""owner_name"": ""Snemann"", -""owner_url"": ""http://www.panoramio.com/user/39160"", -}, -{ -""photo_id"": 505229, -""photo_title"": ""Etangs près de Dijon"", -""photo_url"": ""http://www.panoramio.com/photo/505229"", -""photofileurl"": ""http://static2.bareka.com/photos/medium/505229.jpg"", -""longitude"": 5.168552, -""latitude"": 47.312642, -""width"": 350, -""height"": 500, -""upload_date"": ""20 January 2007"", -""owner_id"": 78506, -""owner_name"": ""Philippe Stoop"", -""owner_url"": ""http://www.panoramio.com/user/78506"" -} -] -}"; + string json = @"{""count"": 773840,""photos"": null}"; - XmlDocument newDoc = (XmlDocument)JsonConvert.DeserializeXmlNode(strJSON); + 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 strJSON = @"{""count"": 773840,""photos"": 773840}"; + string json = @"{""count"": 773840,""photos"": 773840}"; - XmlDocument newDoc = (XmlDocument)JsonConvert.DeserializeXmlNode(strJSON, "myRoot"); + 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] @@ -483,9 +647,15 @@ namespace Newtonsoft.Json.Tests.Converters ] }"; - XmlDocument newDoc = (XmlDocument)JsonConvert.DeserializeXmlNode(json, "myRoot"); + XmlDocument newDoc = JsonConvert.DeserializeXmlNode(json, "myRoot"); Assert.AreEqual(@"assets/images/resized/0001/1070/11070v1-max-150x150.jpgassets/images/resized/0001/1070/11070v1-max-150x150.jpgassets/images/resized/0001/1070/11070v1-max-250x250.jpgassets/images/resized/0001/1070/11070v1-max-250x250.jpg", newDoc.InnerXml); + +#if !NET20 + XDocument newXDoc = JsonConvert.DeserializeXNode(json, "myRoot"); + + Assert.AreEqual(@"assets/images/resized/0001/1070/11070v1-max-150x150.jpgassets/images/resized/0001/1070/11070v1-max-150x150.jpgassets/images/resized/0001/1070/11070v1-max-250x250.jpgassets/images/resized/0001/1070/11070v1-max-250x250.jpg", newXDoc.ToString(SaveOptions.DisableFormatting)); +#endif } [Test] @@ -508,9 +678,15 @@ namespace Newtonsoft.Json.Tests.Converters ] }"; - XmlDocument newDoc = (XmlDocument)JsonConvert.DeserializeXmlNode(json, "myRoot"); + 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] @@ -520,8 +696,10 @@ namespace Newtonsoft.Json.Tests.Converters doc.LoadXml(@"O""Connor"); // i use "" so it will be easier to see the problem - string json = JsonConvert.SerializeXmlNode(doc); - Assert.AreEqual(@"{""name"":""O\""Connor""}", json); + string json = SerializeXmlNode(doc); + Assert.AreEqual(@"{ + ""name"": ""O\""Connor"" +}", json); } [Test] @@ -533,7 +711,7 @@ namespace Newtonsoft.Json.Tests.Converters XmlDocument doc = new XmlDocument(); doc.LoadXml(xml); - string jsonText = JsonConvert.SerializeObject(doc, Formatting.Indented, new XmlNodeConverter()); + string jsonText = SerializeXmlNode(doc); string expected = @"{ ""span"": { @@ -548,7 +726,7 @@ namespace Newtonsoft.Json.Tests.Converters Assert.AreEqual(expected, jsonText); - XmlDocument newDoc = JsonConvert.DeserializeObject(jsonText, new XmlNodeConverter()); + XmlDocument newDoc = (XmlDocument)DeserializeXmlNode(jsonText); Assert.AreEqual(@"Text", newDoc.InnerXml); } @@ -570,7 +748,7 @@ namespace Newtonsoft.Json.Tests.Converters XmlDocument doc = new XmlDocument(); doc.LoadXml(xml); - string jsonText = JsonConvert.SerializeXmlNode(doc); + string jsonText = SerializeXmlNode(doc); // { // "?xml": { // "@version": "1.0", @@ -616,7 +794,7 @@ namespace Newtonsoft.Json.Tests.Converters } }", jsonText); - XmlDocument newDoc = (XmlDocument)JsonConvert.DeserializeXmlNode(jsonText); + XmlDocument newDoc = (XmlDocument)DeserializeXmlNode(jsonText); Assert.AreEqual(doc.InnerXml, newDoc.InnerXml); } @@ -645,7 +823,7 @@ namespace Newtonsoft.Json.Tests.Converters } }"; - XmlDocument doc = (XmlDocument)JsonConvert.DeserializeXmlNode(json); + XmlDocument doc = (XmlDocument)DeserializeXmlNode(json); // // // diff --git a/Src/Newtonsoft.Json.Tests/Serialization/JsonSerializerTest.cs b/Src/Newtonsoft.Json.Tests/Serialization/JsonSerializerTest.cs index 771bdc56..099f5009 100644 --- a/Src/Newtonsoft.Json.Tests/Serialization/JsonSerializerTest.cs +++ b/Src/Newtonsoft.Json.Tests/Serialization/JsonSerializerTest.cs @@ -48,6 +48,9 @@ using System.Runtime.Serialization; using System.Globalization; using Newtonsoft.Json.Utilities; using System.Reflection; +#if !NET20 && !SILVERLIGHT +using System.Xml.Linq; +#endif namespace Newtonsoft.Json.Tests.Serialization { @@ -2715,12 +2718,14 @@ keyword such as type of business."" json = JsonConvert.SerializeObject(myB, Formatting.Indented); Assert.AreEqual(@"{ ""BB_field1"": 4, + ""BB_field2"": 4, ""AA_field1"": 3, ""BB_property1"": 4, ""BB_property2"": 4, ""BB_property3"": 4, ""BB_property4"": 4, ""BB_property5"": 4, + ""BB_property7"": 4, ""AA_property1"": 3, ""AA_property2"": 3, ""AA_property3"": 3, @@ -2734,6 +2739,7 @@ keyword such as type of business."" AA myA = JsonConvert.DeserializeObject( @"{ ""AA_field1"": 2, + ""AA_field2"": 2, ""AA_property1"": 2, ""AA_property2"": 2, ""AA_property3"": 2, @@ -2743,6 +2749,7 @@ keyword such as type of business."" }"); Assert.AreEqual(2, ReflectionUtils.GetMemberValue(typeof(AA).GetField("AA_field1", BindingFlags.Instance | BindingFlags.NonPublic), myA)); + Assert.AreEqual(0, ReflectionUtils.GetMemberValue(typeof(AA).GetField("AA_field2", BindingFlags.Instance | BindingFlags.NonPublic), myA)); Assert.AreEqual(2, ReflectionUtils.GetMemberValue(typeof(AA).GetProperty("AA_property1", BindingFlags.Instance | BindingFlags.NonPublic), myA)); Assert.AreEqual(2, ReflectionUtils.GetMemberValue(typeof(AA).GetProperty("AA_property2", BindingFlags.Instance | BindingFlags.NonPublic), myA)); Assert.AreEqual(2, ReflectionUtils.GetMemberValue(typeof(AA).GetProperty("AA_property3", BindingFlags.Instance | BindingFlags.NonPublic), myA)); @@ -2753,7 +2760,9 @@ keyword such as type of business."" BB myB = JsonConvert.DeserializeObject( @"{ ""BB_field1"": 4, + ""BB_field2"": 4, ""AA_field1"": 3, + ""AA_field2"": 3, ""AA_property1"": 2, ""AA_property2"": 2, ""AA_property3"": 2, @@ -2765,10 +2774,13 @@ keyword such as type of business."" ""BB_property3"": 3, ""BB_property4"": 3, ""BB_property5"": 3, - ""BB_property6"": 3 + ""BB_property6"": 3, + ""BB_property7"": 3, + ""BB_property8"": 3 }"); Assert.AreEqual(3, ReflectionUtils.GetMemberValue(typeof(AA).GetField("AA_field1", BindingFlags.Instance | BindingFlags.NonPublic), myB)); + Assert.AreEqual(0, ReflectionUtils.GetMemberValue(typeof(AA).GetField("AA_field2", BindingFlags.Instance | BindingFlags.NonPublic), myB)); Assert.AreEqual(2, ReflectionUtils.GetMemberValue(typeof(AA).GetProperty("AA_property1", BindingFlags.Instance | BindingFlags.NonPublic), myB)); Assert.AreEqual(2, ReflectionUtils.GetMemberValue(typeof(AA).GetProperty("AA_property2", BindingFlags.Instance | BindingFlags.NonPublic), myB)); Assert.AreEqual(2, ReflectionUtils.GetMemberValue(typeof(AA).GetProperty("AA_property3", BindingFlags.Instance | BindingFlags.NonPublic), myB)); @@ -2777,18 +2789,22 @@ keyword such as type of business."" Assert.AreEqual(0, ReflectionUtils.GetMemberValue(typeof(AA).GetProperty("AA_property6", BindingFlags.Instance | BindingFlags.NonPublic), myB)); Assert.AreEqual(4, myB.BB_field1); + Assert.AreEqual(4, myB.BB_field2); Assert.AreEqual(3, myB.BB_property1); Assert.AreEqual(3, myB.BB_property2); Assert.AreEqual(3, ReflectionUtils.GetMemberValue(typeof(BB).GetProperty("BB_property3", BindingFlags.Instance | BindingFlags.Public), myB)); Assert.AreEqual(3, ReflectionUtils.GetMemberValue(typeof(BB).GetProperty("BB_property4", BindingFlags.Instance | BindingFlags.NonPublic), myB)); Assert.AreEqual(0, myB.BB_property5); Assert.AreEqual(3, ReflectionUtils.GetMemberValue(typeof(BB).GetProperty("BB_property6", BindingFlags.Instance | BindingFlags.Public), myB)); + Assert.AreEqual(3, ReflectionUtils.GetMemberValue(typeof(BB).GetProperty("BB_property7", BindingFlags.Instance | BindingFlags.Public), myB)); + Assert.AreEqual(3, ReflectionUtils.GetMemberValue(typeof(BB).GetProperty("BB_property8", BindingFlags.Instance | BindingFlags.Public), myB)); } public class AA { [JsonProperty] protected int AA_field1; + protected int AA_field2; [JsonProperty] protected int AA_property1 { get; set; } [JsonProperty] @@ -2807,6 +2823,7 @@ keyword such as type of business."" public AA(int f) { AA_field1 = f; + AA_field2 = f; AA_property1 = f; AA_property2 = f; AA_property3 = f; @@ -2820,6 +2837,7 @@ keyword such as type of business."" { [JsonProperty] public int BB_field1; + public int BB_field2; [JsonProperty] public int BB_property1 { get; set; } [JsonProperty] @@ -2830,6 +2848,9 @@ keyword such as type of business."" private int BB_property4 { get; set; } public int BB_property5 { get; private set; } public int BB_property6 { private get; set; } + [JsonProperty] + public int BB_property7 { protected get; set; } + public int BB_property8 { protected get; set; } public BB() { @@ -2839,13 +2860,84 @@ keyword such as type of business."" : base(f) { BB_field1 = g; + BB_field2 = g; BB_property1 = g; BB_property2 = g; BB_property3 = g; BB_property4 = g; BB_property5 = g; BB_property6 = g; + BB_property7 = g; + BB_property8 = g; } } + +#if !NET20 && !SILVERLIGHT + public class XNodeTestObject + { + public XDocument Document { get; set; } + public XElement Element { get; set; } + } +#endif + +#if !SILVERLIGHT + public class XmlNodeTestObject + { + public XmlDocument Document { get; set; } + } +#endif + +#if !NET20 && !SILVERLIGHT + [Test] + public void SerializeDeserializeXNodeProperties() + { + XNodeTestObject testObject = new XNodeTestObject(); + testObject.Document = XDocument.Parse("hehe, root"); + testObject.Element = XElement.Parse(@"element"); + + string json = JsonConvert.SerializeObject(testObject, Formatting.Indented); + string expected = @"{ + ""Document"": { + ""root"": ""hehe, root"" + }, + ""Element"": { + ""fifth"": { + ""@xmlns:json"": ""http://json.org"", + ""@json:Awesome"": ""true"", + ""#text"": ""element"" + } + } +}"; + Assert.AreEqual(expected, json); + + XNodeTestObject newTestObject = JsonConvert.DeserializeObject(json); + Assert.AreEqual(testObject.Document.ToString(), newTestObject.Document.ToString()); + Assert.AreEqual(testObject.Element.ToString(), newTestObject.Element.ToString()); + + Assert.IsNull(newTestObject.Element.Parent); + } +#endif + +#if !SILVERLIGHT + [Test] + public void SerializeDeserializeXmlNodeProperties() + { + XmlNodeTestObject testObject = new XmlNodeTestObject(); + XmlDocument document = new XmlDocument(); + document.LoadXml("hehe, root"); + testObject.Document = document; + + string json = JsonConvert.SerializeObject(testObject, Formatting.Indented); + string expected = @"{ + ""Document"": { + ""root"": ""hehe, root"" + } +}"; + Assert.AreEqual(expected, json); + + XmlNodeTestObject newTestObject = JsonConvert.DeserializeObject(json); + Assert.AreEqual(testObject.Document.InnerXml, newTestObject.Document.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 6f8ab2db..ee5434eb 100644 --- a/Src/Newtonsoft.Json/Converters/XmlNodeConverter.cs +++ b/Src/Newtonsoft.Json/Converters/XmlNodeConverter.cs @@ -27,11 +27,703 @@ using System; using System.Collections.Generic; using System.Xml; +#if !NET20 +using System.Xml.Linq; +#endif using Newtonsoft.Json.Utilities; using System.Linq; namespace Newtonsoft.Json.Converters { + #region Wrappers + internal class XmlDocumentWrapper : XmlNodeWrapper, IXmlDocument + { + private 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 XmlElement _element; + + public XmlElementWrapper(XmlElement element) + : base(element) + { + _element = element; + } + + public void SetAttributeNode(IXmlNode attribute) + { + XmlNodeWrapper xmlAttributeWrapper = (XmlNodeWrapper)attribute; + + _element.SetAttributeNode((XmlAttribute) xmlAttributeWrapper.WrappedNode); + } + } + + internal class XmlDeclarationWrapper : XmlNodeWrapper, IXmlDeclaration + { + private 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; } + } + } + + 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); + } + + 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; } + } + + #if !NET20 + internal class XDeclarationWrapper : XObjectWrapper, IXmlDeclaration + { + internal readonly XDeclaration _declaration; + + 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; } + } + } +#endif + #endregion + /// /// Converts an to and from JSON. /// @@ -60,30 +752,83 @@ namespace Newtonsoft.Json.Converters /// The value. public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) { - XmlNode node = value as XmlNode; + IXmlNode node; - if (node == null) + if (value is XmlNode) + node = new XmlNodeWrapper((XmlNode)value); +#if !NET20 + else if (value is XObject) + node = XContainerWrapper.WrapNode((XObject)value); +#endif + else throw new ArgumentException("Value must be an XmlNode", "value"); + XmlNamespaceManager manager = new XmlNamespaceManager(new NameTable()); + PushParentNamespaces(node, manager); + writer.WriteStartObject(); - SerializeNode(writer, node, true); + SerializeNode(writer, node, manager, true); writer.WriteEndObject(); } - private string GetPropertyName(XmlNode node) + 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: - return "@" + node.Name; + return "@" + ResolveFullName(node, manager); case XmlNodeType.CDATA: return CDataName; case XmlNodeType.Comment: return CommentName; case XmlNodeType.Element: - return node.Name; + return ResolveFullName(node, manager); case XmlNodeType.ProcessingInstruction: - return "?" + node.Name; + return "?" + ResolveFullName(node, manager); case XmlNodeType.XmlDeclaration: return DeclarationName; case XmlNodeType.SignificantWhitespace: @@ -97,20 +842,20 @@ namespace Newtonsoft.Json.Converters } } - private void SerializeGroupedNodes(JsonWriter writer, XmlNode node) + private void SerializeGroupedNodes(JsonWriter writer, IXmlNode node, XmlNamespaceManager manager) { // group nodes together by name - Dictionary> nodesGroupedByName = new Dictionary>(); + Dictionary> nodesGroupedByName = new Dictionary>(); for (int i = 0; i < node.ChildNodes.Count; i++) { - XmlNode childNode = node.ChildNodes[i]; - string nodeName = GetPropertyName(childNode); + IXmlNode childNode = node.ChildNodes[i]; + string nodeName = GetPropertyName(childNode, manager); - List nodes; + List nodes; if (!nodesGroupedByName.TryGetValue(nodeName, out nodes)) { - nodes = new List(); + nodes = new List(); nodesGroupedByName.Add(nodeName, nodes); } @@ -119,15 +864,17 @@ namespace Newtonsoft.Json.Converters // loop through grouped nodes. write single name instances as normal, // write multiple names together in an array - foreach (KeyValuePair> nodeNameGroup in nodesGroupedByName) + foreach (KeyValuePair> nodeNameGroup in nodesGroupedByName) { - List groupedNodes = nodeNameGroup.Value; + List groupedNodes = nodeNameGroup.Value; bool writeArray; if (groupedNodes.Count == 1) { - XmlNode singleNode = groupedNodes[0]; - XmlAttribute jsonArrayAttribute = (singleNode.Attributes != null) ? singleNode.Attributes["Array", JsonNamespaceUri] : null; + IXmlNode singleNode = groupedNodes[0]; + IXmlNode jsonArrayAttribute = (singleNode.Attributes != null) + ? singleNode.Attributes.SingleOrDefault(a => a.LocalName == "Array" && a.NamespaceURI == JsonNamespaceUri) + : null; if (jsonArrayAttribute != null) writeArray = XmlConvert.ToBoolean(jsonArrayAttribute.Value); else @@ -140,7 +887,7 @@ namespace Newtonsoft.Json.Converters if (!writeArray) { - SerializeNode(writer, groupedNodes[0], true); + SerializeNode(writer, groupedNodes[0], manager, true); } else { @@ -150,7 +897,7 @@ namespace Newtonsoft.Json.Converters for (int i = 0; i < groupedNodes.Count; i++) { - SerializeNode(writer, groupedNodes[i], false); + SerializeNode(writer, groupedNodes[i], manager, false); } writer.WriteEndArray(); @@ -158,17 +905,29 @@ namespace Newtonsoft.Json.Converters } } - private void SerializeNode(JsonWriter writer, XmlNode node, bool writePropertyName) + private void SerializeNode(JsonWriter writer, IXmlNode node, XmlNamespaceManager manager, bool writePropertyName) { switch (node.NodeType) { case XmlNodeType.Document: case XmlNodeType.DocumentFragment: - SerializeGroupedNodes(writer, node); + SerializeGroupedNodes(writer, node, manager); break; case XmlNodeType.Element: + 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(node.Name); + writer.WritePropertyName(GetPropertyName(node, manager)); if (ValueAttributes(node.Attributes).Count() == 0 && node.ChildNodes.Count == 1 && node.ChildNodes[0].NodeType == XmlNodeType.Text) @@ -181,30 +940,16 @@ namespace Newtonsoft.Json.Converters // empty element writer.WriteNull(); } - else if (node.ChildNodes.OfType().Where(x => x.Name.StartsWith("-")).Count() > 1) - { - XmlElement constructorValueElement = node.ChildNodes.OfType().Where(x => x.Name.StartsWith("-")).First(); - string constructorName = constructorValueElement.Name.Substring(1); - - writer.WriteStartConstructor(constructorName); - - for (int i = 0; i < node.ChildNodes.Count; i++) - { - SerializeNode(writer, node.ChildNodes[i], false); - } - - writer.WriteEndConstructor(); - } else { writer.WriteStartObject(); for (int i = 0; i < node.Attributes.Count; i++) { - SerializeNode(writer, node.Attributes[i], true); + SerializeNode(writer, node.Attributes[i], manager, true); } - SerializeGroupedNodes(writer, node); + SerializeGroupedNodes(writer, node, manager); writer.WriteEndObject(); } @@ -220,18 +965,18 @@ namespace Newtonsoft.Json.Converters case XmlNodeType.ProcessingInstruction: case XmlNodeType.Whitespace: case XmlNodeType.SignificantWhitespace: - if (node.Prefix == "xmlns" && node.Value == JsonNamespaceUri) + if (node.NamespaceURI == "http://www.w3.org/2000/xmlns/" && node.Value == JsonNamespaceUri) break; else if (node.NamespaceURI == JsonNamespaceUri) break; if (writePropertyName) - writer.WritePropertyName(GetPropertyName(node)); + writer.WritePropertyName(GetPropertyName(node, manager)); writer.WriteValue(node.Value); break; case XmlNodeType.XmlDeclaration: - XmlDeclaration declaration = (XmlDeclaration)node; - writer.WritePropertyName(GetPropertyName(node)); + IXmlDeclaration declaration = (IXmlDeclaration)node; + writer.WritePropertyName(GetPropertyName(node, manager)); writer.WriteStartObject(); if (!string.IsNullOrEmpty(declaration.Version)) @@ -268,24 +1013,40 @@ namespace Newtonsoft.Json.Converters /// The object value. public override object ReadJson(JsonReader reader, Type objectType, JsonSerializer serializer) { - // maybe have CanReader and a CanWrite methods so this sort of test wouldn't be necessary - if (objectType != typeof(XmlDocument)) - throw new JsonSerializationException("XmlNodeConverter only supports deserializing XmlDocuments"); + XmlNamespaceManager manager = new XmlNamespaceManager(new NameTable()); + IXmlDocument document; + IXmlNode rootNode; - XmlDocument document = new XmlDocument(); - XmlNamespaceManager manager = new XmlNamespaceManager(document.NameTable); + 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; + } +#if !NET20 + else 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 + else + { + throw new JsonSerializationException("Unexpected type when converting XML: " + objectType); + } - XmlNode rootNode; - if (!string.IsNullOrEmpty(DeserializeRootElementName)) { rootNode = document.CreateElement(DeserializeRootElementName); document.AppendChild(rootNode); } - else - { - rootNode = document; - } if (reader.TokenType != JsonToken.StartObject) throw new JsonSerializationException("XmlNodeConverter can only convert JSON that begins with an object."); @@ -294,10 +1055,20 @@ namespace Newtonsoft.Json.Converters DeserializeNode(reader, document, manager, rootNode); - return document; +#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, XmlDocument document, XmlNamespaceManager manager, string propertyName, XmlNode currentNode) + private void DeserializeValue(JsonReader reader, IXmlDocument document, XmlNamespaceManager manager, string propertyName, IXmlNode currentNode) { switch (propertyName) { @@ -343,12 +1114,12 @@ namespace Newtonsoft.Json.Converters } } - XmlDeclaration declaration = document.CreateXmlDeclaration(version, encoding, standalone); + IXmlNode declaration = document.CreateXmlDeclaration(version, encoding, standalone); currentNode.AppendChild(declaration); } else { - XmlProcessingInstruction instruction = document.CreateProcessingInstruction(propertyName.Substring(1), reader.Value.ToString()); + IXmlNode instruction = document.CreateProcessingInstruction(propertyName.Substring(1), reader.Value.ToString()); currentNode.AppendChild(instruction); } } @@ -357,12 +1128,13 @@ namespace Newtonsoft.Json.Converters // deserialize xml element bool finishedAttributes = false; bool finishedElement = false; - string elementPrefix = GetPrefix(propertyName); + string elementPrefix = MiscellaneousUtils.GetPrefix(propertyName); + Dictionary attributeNameValues = new Dictionary(); if (reader.TokenType == JsonToken.StartArray) { - XmlElement nestedArrayElement = CreateElement(propertyName, document, elementPrefix, manager); + IXmlElement nestedArrayElement = CreateElement(propertyName, document, elementPrefix, manager); currentNode.AppendChild(nestedArrayElement); @@ -421,20 +1193,18 @@ namespace Newtonsoft.Json.Converters // have to wait until attributes have been parsed before creating element // attributes may contain namespace info used by the element - XmlElement element = CreateElement(propertyName, document, elementPrefix, manager); + IXmlElement element = CreateElement(propertyName, document, elementPrefix, manager); currentNode.AppendChild(element); // add attributes to newly created element foreach (KeyValuePair nameValue in attributeNameValues) { - string attributePrefix = GetPrefix(nameValue.Key); + string attributePrefix = MiscellaneousUtils.GetPrefix(nameValue.Key); - XmlAttribute attribute = (!string.IsNullOrEmpty(attributePrefix)) - ? document.CreateAttribute(nameValue.Key, manager.LookupNamespace(attributePrefix)) - : document.CreateAttribute(nameValue.Key); - - attribute.Value = nameValue.Value; + IXmlNode attribute = (!string.IsNullOrEmpty(attributePrefix)) + ? document.CreateAttribute(nameValue.Key, manager.LookupNamespace(attributePrefix), nameValue.Value) + : document.CreateAttribute(nameValue.Key, nameValue.Value); element.SetAttributeNode(attribute); } @@ -481,14 +1251,14 @@ namespace Newtonsoft.Json.Converters } } - private XmlElement CreateElement(string elementName, XmlDocument document, string elementPrefix, XmlNamespaceManager manager) + private IXmlElement CreateElement(string elementName, IXmlDocument document, string elementPrefix, XmlNamespaceManager manager) { return (!string.IsNullOrEmpty(elementPrefix)) ? document.CreateElement(elementName, manager.LookupNamespace(elementPrefix)) : document.CreateElement(elementName); } - private void DeserializeNode(JsonReader reader, XmlDocument document, XmlNamespaceManager manager, XmlNode currentNode) + private void DeserializeNode(JsonReader reader, IXmlDocument document, XmlNamespaceManager manager, IXmlNode currentNode) { do { @@ -518,7 +1288,7 @@ namespace Newtonsoft.Json.Converters while (reader.Read() && reader.TokenType != JsonToken.EndConstructor) { - DeserializeValue(reader, document, manager, "-" + constructorName, currentNode); + DeserializeValue(reader, document, manager, constructorName, currentNode); } break; case JsonToken.Comment: @@ -559,19 +1329,9 @@ namespace Newtonsoft.Json.Converters return false; } - private string GetPrefix(string qualifiedName) + private IEnumerable ValueAttributes(IEnumerable c) { - int colonPosition = qualifiedName.IndexOf(':'); - - if ((colonPosition == -1 || colonPosition == 0) || (qualifiedName.Length - 1) == colonPosition) - return string.Empty; - else - return qualifiedName.Substring(0, colonPosition); - } - - private IEnumerable ValueAttributes(XmlAttributeCollection c) - { - return c.OfType().Where(a => a.NamespaceURI != JsonNamespaceUri); + return c.Where(a => a.NamespaceURI != JsonNamespaceUri); } #endregion @@ -584,7 +1344,14 @@ namespace Newtonsoft.Json.Converters /// public override bool CanConvert(Type valueType) { - return typeof(XmlNode).IsAssignableFrom(valueType); + if (typeof(XmlNode).IsAssignableFrom(valueType)) + return true; +#if !NET20 + if (typeof(XObject).IsAssignableFrom(valueType)) + return true; +#endif + + return false; } } } diff --git a/Src/Newtonsoft.Json/JsonConvert.cs b/Src/Newtonsoft.Json/JsonConvert.cs index 45b6a533..ebdf8697 100644 --- a/Src/Newtonsoft.Json/JsonConvert.cs +++ b/Src/Newtonsoft.Json/JsonConvert.cs @@ -30,6 +30,9 @@ using Newtonsoft.Json.Utilities; using System.Xml; using Newtonsoft.Json.Converters; using System.Text; +#if !NET20 && !SILVERLIGHT +using System.Xml.Linq; +#endif namespace Newtonsoft.Json { @@ -735,6 +738,20 @@ namespace Newtonsoft.Json } } +#if !NET20 && !SILVERLIGHT + public static string SerializeXNode(XObject node) + { + return SerializeXNode(node, Formatting.None); + } + + public static string SerializeXNode(XObject node, Formatting formatting) + { + XmlNodeConverter converter = new XmlNodeConverter(); + + return SerializeObject(node, formatting, converter); + } +#endif + #if !SILVERLIGHT /// /// Serializes the XML node to a JSON string. @@ -742,10 +759,21 @@ namespace Newtonsoft.Json /// The node to serialize. /// A JSON string of the XmlNode. public static string SerializeXmlNode(XmlNode node) + { + return SerializeXmlNode(node, Formatting.None); + } + + /// + /// Serializes the XML node to a JSON string. + /// + /// The node to serialize. + /// Indicates how the output is formatted. + /// A JSON string of the XmlNode. + public static string SerializeXmlNode(XmlNode node, Formatting formatting) { XmlNodeConverter converter = new XmlNodeConverter(); - return SerializeObject(node, converter); + return SerializeObject(node, formatting, converter); } /// @@ -753,7 +781,7 @@ namespace Newtonsoft.Json /// /// The JSON string. /// The deserialized XmlNode - public static XmlNode DeserializeXmlNode(string value) + public static XmlDocument DeserializeXmlNode(string value) { return DeserializeXmlNode(value, null); } @@ -764,13 +792,39 @@ namespace Newtonsoft.Json /// The JSON string. /// The name of the root element to append when deserializing. /// The deserialized XmlNode - public static XmlNode DeserializeXmlNode(string value, string deserializeRootElementName) + public static XmlDocument DeserializeXmlNode(string value, string deserializeRootElementName) { XmlNodeConverter converter = new XmlNodeConverter(); converter.DeserializeRootElementName = deserializeRootElementName; return (XmlDocument)DeserializeObject(value, typeof(XmlDocument), converter); } + +#if !NET20 + /// + /// Deserializes the XNode from a JSON string. + /// + /// The JSON string. + /// The deserialized XmlNode + public static XDocument DeserializeXNode(string value) + { + return DeserializeXNode(value, null); + } + + /// + /// Deserializes the XmlNode from a JSON string nested in a root elment. + /// + /// The JSON string. + /// The name of the root element to append when deserializing. + /// The deserialized XmlNode + public static XDocument DeserializeXNode(string value, string deserializeRootElementName) + { + XmlNodeConverter converter = new XmlNodeConverter(); + converter.DeserializeRootElementName = deserializeRootElementName; + + return (XDocument)DeserializeObject(value, typeof(XDocument), converter); + } +#endif #endif } } \ No newline at end of file diff --git a/Src/Newtonsoft.Json/Newtonsoft.Json.csproj b/Src/Newtonsoft.Json/Newtonsoft.Json.csproj index f9512fbc..458e9587 100644 --- a/Src/Newtonsoft.Json/Newtonsoft.Json.csproj +++ b/Src/Newtonsoft.Json/Newtonsoft.Json.csproj @@ -75,6 +75,9 @@ + + 3.5 + diff --git a/Src/Newtonsoft.Json/Serialization/DefaultContractResolver.cs b/Src/Newtonsoft.Json/Serialization/DefaultContractResolver.cs index 860e9fad..1d344ad4 100644 --- a/Src/Newtonsoft.Json/Serialization/DefaultContractResolver.cs +++ b/Src/Newtonsoft.Json/Serialization/DefaultContractResolver.cs @@ -50,6 +50,7 @@ namespace Newtonsoft.Json.Serialization #endif new BinaryConverter(), #if !SILVERLIGHT + new XmlNodeConverter(), new DataSetConverter(), new DataTableConverter() #endif diff --git a/Src/Newtonsoft.Json/Utilities/MiscellaneousUtils.cs b/Src/Newtonsoft.Json/Utilities/MiscellaneousUtils.cs index d21b6f10..b6665c92 100644 --- a/Src/Newtonsoft.Json/Utilities/MiscellaneousUtils.cs +++ b/Src/Newtonsoft.Json/Utilities/MiscellaneousUtils.cs @@ -98,5 +98,39 @@ namespace Newtonsoft.Json.Utilities return true; } + + public static string GetPrefix(string qualifiedName) + { + string prefix; + string localName; + GetQualifiedNameParts(qualifiedName, out prefix, out localName); + + return prefix; + } + + public static string GetLocalName(string qualifiedName) + { + string prefix; + string localName; + GetQualifiedNameParts(qualifiedName, out prefix, out localName); + + return localName; + } + + public static void GetQualifiedNameParts(string qualifiedName, out string prefix, out string localName) + { + int colonPosition = qualifiedName.IndexOf(':'); + + if ((colonPosition == -1 || colonPosition == 0) || (qualifiedName.Length - 1) == colonPosition) + { + prefix = null; + localName = qualifiedName; + } + else + { + prefix = qualifiedName.Substring(0, colonPosition); + localName = qualifiedName.Substring(colonPosition + 1); + } + } } } \ No newline at end of file