From 36040c9dd3eaad00c22ec1849aec4972761bf251 Mon Sep 17 00:00:00 2001 From: Joe Sauve Date: Wed, 7 Oct 2015 08:39:55 -0500 Subject: [PATCH 1/2] Added new functions: XPathReplaceInnerText XmlPokeInnerText XPathReplaceInnerTextNS XmlPokeInnerTextNS The existing XPathReplace and XPathPoke functions are incapable of modifying the Value property of XmlNode. This is due to both Mono and .NET throwing an InvalidOperationException in the body of the XmlNode.Value property setter: Mono: https://github.com/mosa/Mono-Class-Libraries/blob/master/mcs/class/System.XML/System.Xml/XmlNode.cs#L309 .NET: https://github.com/Microsoft/referencesource/blob/master/System.Xml/System/Xml/Dom/XmlNode.cs#L92 I'm getting seeing that InvalidOperationException in practice in one of my FAKE build scripts. I know the XPath expression I'm using is valid; if I change it to be invalid, I get the "node not found" message. So, I've added some functions that allow updating the InnerText value of a node; something for which I have a need in my FAKE build scripts. InnerText IS a mutable property, unlike Value. Suggestion: Consider removing XPathReplace, XPathReplaceNS, XmlPoke, and XmlPokeNS. I'm not sure what purpose they serve, because they don't seem to work. XmlNode.Value is unsettable in both .NET and Mono. --- src/app/FakeLib/XMLHelper.fs | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/src/app/FakeLib/XMLHelper.fs b/src/app/FakeLib/XMLHelper.fs index de80593b4cc..ad98dc5081b 100644 --- a/src/app/FakeLib/XMLHelper.fs +++ b/src/app/FakeLib/XMLHelper.fs @@ -118,6 +118,14 @@ let XPathReplace xpath value (doc : XmlDocument) = node.Value <- value doc +/// Replaces the inner text of an xml node in the XML document specified by a XPath expression. +let XPathReplaceInnerText xpath innerTextValue (doc : XmlDocument) = + let node = doc.SelectSingleNode xpath + if node = null then failwithf "XML node '%s' not found" xpath + else + node.InnerText <- innerTextValue + doc + /// Selects a xml node value via XPath from the given document let XPathValue xpath (namespaces : #seq) (doc : XmlDocument) = let nsmgr = XmlNamespaceManager(doc.NameTable) @@ -132,6 +140,12 @@ let XmlPoke (fileName : string) xpath value = doc.Load fileName XPathReplace xpath value doc |> fun x -> x.Save fileName +/// Replaces the inner text of an xml node in a XML file at the location specified by a XPath expression. +let XmlPokeInnerText (fileName : string) xpath innerTextValue = + let doc = new XmlDocument() + doc.Load fileName + XPathReplaceInnerText xpath innerTextValue doc |> fun x -> x.Save fileName + /// Replaces text in a XML document specified by a XPath expression, with support for namespaces. let XPathReplaceNS xpath value (namespaces : #seq) (doc : XmlDocument) = let nsmgr = XmlNamespaceManager(doc.NameTable) @@ -142,12 +156,28 @@ let XPathReplaceNS xpath value (namespaces : #seq) (doc : XmlDo node.Value <- value doc +/// Replaces inner text in a XML document specified by a XPath expression, with support for namespaces. +let XPathReplaceInnerTextNS xpath innerTextValue (namespaces : #seq) (doc : XmlDocument) = + let nsmgr = XmlNamespaceManager(doc.NameTable) + namespaces |> Seq.iter nsmgr.AddNamespace + let node = doc.SelectSingleNode(xpath, nsmgr) + if node = null then failwithf "XML node '%s' not found" xpath + else + node.InnerText <- innerTextValue + doc + /// Replaces text in a XML file at the location specified by a XPath expression, with support for namespaces. let XmlPokeNS (fileName : string) namespaces xpath value = let doc = new XmlDocument() doc.Load fileName XPathReplaceNS xpath value namespaces doc |> fun x -> x.Save fileName +/// Replaces inner text of an xml node in a XML file at the location specified by a XPath expression, with support for namespaces. +let XmlPokeInnerTextNS (fileName : string) namespaces xpath innerTextValue = + let doc = new XmlDocument() + doc.Load fileName + XPathReplaceInnerTextNS xpath innerTextValue namespaces doc |> fun x -> x.Save fileName + /// Loads the given text into a XslCompiledTransform. let XslTransformer text = if isNullOrEmpty text then null From 8ddd8249664fc26758e2dd316b320f428b6db4c5 Mon Sep 17 00:00:00 2001 From: Joe Sauve Date: Wed, 7 Oct 2015 13:31:54 -0700 Subject: [PATCH 2/2] Added tests for XmlPokeInnerText and XmlPokeInnerTextNS --- src/test/Test.FAKECore/XmlSpecs.cs | 80 ++++++++++++++++++++++++++++-- 1 file changed, 77 insertions(+), 3 deletions(-) diff --git a/src/test/Test.FAKECore/XmlSpecs.cs b/src/test/Test.FAKECore/XmlSpecs.cs index 5733c36885d..3dd148cc294 100644 --- a/src/test/Test.FAKECore/XmlSpecs.cs +++ b/src/test/Test.FAKECore/XmlSpecs.cs @@ -58,6 +58,42 @@ public class when_poking_xml .ShouldEqual(TargetText.Replace("\r", "").Replace("\n", "")); } + public class when_poking_xml_innertext + { + const string OriginalText = + "" + + "" + + " \"Foligno" + + " This is Raphael's \"Foligno\" Madonna, painted in - ." + + " Oil on wood, transferred to canvas" + + " 320 cm × 194 cm (130 in × 76 in)" + + " Pinacoteca Vaticana, Vatican City" + + ""; + + const string XPath = "painting/note[@name='Location']"; + + static readonly string FileName = Path.Combine(TestData.TestDir, "test.xml"); + + static XmlDocument _doc; + static readonly string TargetText = OriginalText.Replace("Pinacoteca Vaticana, Vatican City", "Vatican City"); + + + Cleanup after = () => FileHelper.DeleteFile(FileName); + + Establish context = () => + { + StringHelper.WriteStringToFile(false, FileName, OriginalText); + _doc = new XmlDocument(); + _doc.LoadXml(OriginalText); + }; + + Because of = () => XMLHelper.XmlPokeInnerText(FileName, XPath, "Vatican City"); + + It should_equal_the_target_text = + () => StringHelper.ReadFileAsString(FileName).Replace("\r", "").Replace("\n", "") + .ShouldEqual(TargetText.Replace("\r", "").Replace("\n", "")); + } + public class when_modifying_xml_with_xpath { const string OriginalText = @@ -85,8 +121,6 @@ public class when_modifying_xml_with_xpath () => _resultDoc.OuterXml.ShouldEqual(_targetText); } - - public class when_poking_xml_and_ns { const string OriginalText = @@ -126,7 +160,47 @@ public class when_poking_xml_and_ns () => StringHelper.ReadFileAsString(FileName).Replace("\r", "").Replace("\n", "").Replace("'", "\"") .ShouldEqual(TargetText.Replace("\r", "").Replace("\n", "").Replace("'", "\"")); } - + + public class when_poking_xml_innertext_and_ns + { + const string OriginalText = + "" + + "" + + " " + + " " + + ""; + + const string XPath = "//asmv1:assembly/asmv1:assemblyDescription"; + + static readonly string FileName = Path.Combine(TestData.TestDir, "test.xml"); + + static XmlDocument _doc; + static readonly string TargetText = OriginalText.Replace("", "A really great assembly. Really."); + static List> _nsdecl; + + + Cleanup after = () => FileHelper.DeleteFile(FileName); + + Establish context = () => + { + StringHelper.WriteStringToFile(false, FileName, OriginalText); + _doc = new XmlDocument(); + _doc.LoadXml(OriginalText); + _nsdecl = new List> + { + new Tuple("", "urn:schemas-microsoft-com:asm.v1"), + new Tuple("asmv1", "urn:schemas-microsoft-com:asm.v1"), + new Tuple("asmv2", "urn:schemas-microsoft-com:asm.v2"), + new Tuple("xsi", "http://www.w3.org/2001/XMLSchema-instance") + }; + }; + + Because of = () => XMLHelper.XmlPokeInnerTextNS(FileName, _nsdecl, XPath, "A really great assembly. Really."); + + It should_equal_the_target_text = + () => StringHelper.ReadFileAsString(FileName).Replace("\r", "").Replace("\n", "").Replace("'", "\"") + .ShouldEqual(TargetText.Replace("\r", "").Replace("\n", "").Replace("'", "\"")); + } public class when_modifying_xml_with_xpath_and_ns {