diff --git a/CHANGELOG.md b/CHANGELOG.md
index 48ee9b792..3fe200a2a 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -17,6 +17,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
## [Unreleased]
### Added
+- [SIL.Core] Added optional parameter, preserveNamespaces, to XmlUtils.WriteNode
- [SIL.Core] Added optional parameter, includeSystemLibraries, to AcknowledgementsProvider.CollectAcknowledgements
- [SIL.Windows.Forms] Added ability to select which SIL logo(s) to use in SILAboutBox.
- [SIL.Windows.Forms] Added public enum Widgets.SilLogoVariant
diff --git a/SIL.Core.Tests/Xml/XmlUtilsTests.cs b/SIL.Core.Tests/Xml/XmlUtilsTests.cs
index 8cf58f101..1ee82f04e 100644
--- a/SIL.Core.Tests/Xml/XmlUtilsTests.cs
+++ b/SIL.Core.Tests/Xml/XmlUtilsTests.cs
@@ -176,6 +176,62 @@ public void WriteNode_DoesNotIndentChildWhenSuppressed()
}
Assert.That(output.ToString(), Is.EqualTo(expectedOutput));
}
+
+ [Test]
+ public void WriteNode_PreserveNamespacesArePreserved()
+ {
+ string input = @" ";
+ string expectedOutput =
+ "\r\n"
+ + "\r\n"
+ + " \r\n"
+ + " \r\n"
+ + " \r\n"
+ + "";
+ var output = new StringBuilder();
+ var preserveNamespace = new HashSet();
+ preserveNamespace.Add("xml");
+ using (var writer = XmlWriter.Create(output, CanonicalXmlSettings.CreateXmlWriterSettings()))
+ {
+ writer.WriteStartDocument();
+ writer.WriteStartElement("root");
+ XmlUtils.WriteNode(writer, input, new HashSet(), preserveNamespace);
+ writer.WriteEndElement();
+ writer.WriteEndDocument();
+ }
+ Assert.That(output.ToString(), Is.EqualTo(expectedOutput));
+ }
+
+ [Test]
+ public void WriteNode_ProtectsAgainstXmlnsFormatThrashing()
+ {
+ string input = @" ";
+ string expectedOutput =
+ "\r\n"
+ + "\r\n"
+ + " \r\n"
+ + " \r\n"
+ + " \r\n"
+ + "";
+ var output = new StringBuilder();
+ var preserveNamespace = new HashSet();
+ preserveNamespace.Add("xml");
+ preserveNamespace.Add("xmlns");
+ preserveNamespace.Add("fw");
+ using (var writer = XmlWriter.Create(output, CanonicalXmlSettings.CreateXmlWriterSettings()))
+ {
+ writer.WriteStartDocument();
+ writer.WriteStartElement("root");
+ Assert.Throws(()=> XmlUtils.WriteNode(writer, input, new HashSet(), preserveNamespace));
+ }
+ }
+
///
/// This verifies that suppressing pretty-printing of children works for spans nested in spans nested in text.
///
@@ -191,8 +247,10 @@ public void WriteNode_DoesNotIndentChildWhenTwoLevelsSuppressed()
+ " class=\"italic\">bitbt\r\n"
+ "";
var output = new StringBuilder();
- var suppressIndentingChildren = new HashSet();
- suppressIndentingChildren.Add("text");
+ var suppressIndentingChildren = new HashSet
+ {
+ "text"
+ };
using (var writer = XmlWriter.Create(output, CanonicalXmlSettings.CreateXmlWriterSettings()))
{
writer.WriteStartDocument();
diff --git a/SIL.Core/Xml/XmlUtils.cs b/SIL.Core/Xml/XmlUtils.cs
index 5f5bbe362..8f0987431 100644
--- a/SIL.Core/Xml/XmlUtils.cs
+++ b/SIL.Core/Xml/XmlUtils.cs
@@ -970,10 +970,11 @@ public static string GetTitleOfHtml(XmlDocument dom, string defaultIfMissing)
///
///
///
- public static void WriteNode(XmlWriter writer, string dataToWrite, HashSet suppressIndentingChildren)
+ /// a set of namespaces to preserve when writing out elements
+ public static void WriteNode(XmlWriter writer, string dataToWrite, HashSet suppressIndentingChildren, HashSet preserveNamespaces = null)
{
XElement element = XDocument.Parse(dataToWrite).Root;
- WriteNode(writer, element, suppressIndentingChildren);
+ WriteNode(writer, element, suppressIndentingChildren, preserveNamespaces);
}
///
@@ -984,11 +985,12 @@ public static void WriteNode(XmlWriter writer, string dataToWrite, HashSet
///
///
- public static void WriteNode(XmlWriter writer, XElement dataToWrite, HashSet suppressIndentingChildren)
+ /// a set of namespaces to preserve when writing out elements
+ public static void WriteNode(XmlWriter writer, XElement dataToWrite, HashSet suppressIndentingChildren, HashSet preserveNamespaces = null)
{
if (dataToWrite == null)
return;
- WriteElementTo(writer, dataToWrite, suppressIndentingChildren);
+ WriteElementTo(writer, dataToWrite, suppressIndentingChildren, preserveNamespaces);
}
///
@@ -997,11 +999,33 @@ public static void WriteNode(XmlWriter writer, XElement dataToWrite, HashSet
///
///
- private static void WriteElementTo(XmlWriter writer, XElement element, HashSet suppressIndentingChildren)
+ /// a set of namespaces to preserve when writing out elements
+ private static void WriteElementTo(XmlWriter writer, XElement element, HashSet suppressIndentingChildren, HashSet preserveNamespaces = null)
{
writer.WriteStartElement(element.Name.LocalName);
foreach (var attr in element.Attributes())
- writer.WriteAttributeString(attr.Name.LocalName, attr.Value);
+ {
+ // if we are preserving namespaces, we may need to write the attribute with the prefix
+ if (preserveNamespaces != null && !string.IsNullOrEmpty(attr.Name.NamespaceName))
+ {
+ var attrPrefix = element.GetPrefixOfNamespace(attr.Name.NamespaceName);
+ if (preserveNamespaces.Contains(attrPrefix))
+ {
+ if (attrPrefix == "xmlns")
+ {
+ // If you need to write out the xmlns attribute consistently between platforms custom code will need to be written
+ // I'm leaving it unimplemented until needed
+ throw new ArgumentException("The 'xmlns' local namespace declarations are handled differently in framework. Using it could cause thrashing.");
+ }
+ writer.WriteAttributeString(attrPrefix, attr.Name.LocalName, attr.Name.NamespaceName, attr.Value);
+ }
+ else
+ writer.WriteAttributeString(attr.Name.LocalName, attr.Value);
+ }
+ else
+ writer.WriteAttributeString(attr.Name.LocalName, attr.Value);
+ }
+
// The writer automatically suppresses indenting children for any element that it detects has text children.
// However, it won't do this for the first child if that is an element, even if it later encounters text children.
// Also, there may be a parent where text including white space is significant, yet it is possible for the
@@ -1018,7 +1042,7 @@ private static void WriteElementTo(XmlWriter writer, XElement element, HashSet