diff --git a/docs/standard/linq/preserve-white-space-serializing.md b/docs/standard/linq/preserve-white-space-serializing.md index 714b546cb125c..7d14477145f43 100644 --- a/docs/standard/linq/preserve-white-space-serializing.md +++ b/docs/standard/linq/preserve-white-space-serializing.md @@ -25,3 +25,31 @@ The following methods in the and as an argument, then the method will format (indent) the serialized XML. In this case, all insignificant white space in the XML tree is discarded. If the method does take as an argument, then you can specify that the method not format (indent) the serialized XML. In this case, all white space in the XML tree is preserved. + +## Roundtripping XML with carriage return entities + +The whitespace preservation discussed in this article is different from XML roundtripping. When XML contains carriage return entities (` `), LINQ to XML's standard serialization might not preserve them in a way that allows perfect roundtripping. + +Consider the following example XML that contains carriage return entities: + +```xml +a +b +c +``` + +When you parse this XML with `XDocument.Parse()`, the root element's value becomes `"a\r\nb\nc\r"`. However, if you reserialize it using LINQ to XML methods, the carriage returns are not entitized: + +:::code language="csharp" source="snippets/preserve-white-space-serializing/RoundtrippingProblem.cs" Id="XmlRoundTrip"::: + +The values are different: the original was `"a\r\nb\nc\r"` but after roundtripping it becomes `"a\nb\nc\n"`. + +### Solution: Use XmlWriter with NewLineHandling.Entitize + +To achieve true XML roundtripping that preserves carriage return entities, use with set to : + +:::code language="csharp" source="snippets/preserve-white-space-serializing/RoundtrippingSolution.cs" Id="XmlRoundTripFix"::: + +When you need to preserve carriage return entities for XML roundtripping, use with the appropriate instead of LINQ to XML's built-in serialization methods. + +For more information about and its settings, see . diff --git a/docs/standard/linq/snippets/preserve-white-space-serializing/RoundtrippingProblem.cs b/docs/standard/linq/snippets/preserve-white-space-serializing/RoundtrippingProblem.cs new file mode 100644 index 0000000000000..81637b26f46c4 --- /dev/null +++ b/docs/standard/linq/snippets/preserve-white-space-serializing/RoundtrippingProblem.cs @@ -0,0 +1,46 @@ +using System; +using System.Linq; +using System.Xml.Linq; + +class Program +{ + static void Main() + { + Console.WriteLine("XML Roundtripping Examples"); + Console.WriteLine("=========================="); + + Console.WriteLine("\n1. Roundtripping Problem:"); + RoundtrippingProblem.Example(); + + Console.WriteLine("\n2. Roundtripping Solution:"); + RoundtrippingSolution.Example(); + } +} + +public static class RoundtrippingProblem +{ + public static void Example() + { + // + string xmlWithCR = """ + a + b + c + """; + + XDocument doc = XDocument.Parse(xmlWithCR); + Console.WriteLine($"Original parsed value: {string.Join("", doc.Root!.Value.Select(c => c == '\r' ? "\\r" : c == '\n' ? "\\n" : c.ToString()))}"); + // Output: a\r\nb\nc\r + + string reserialized = doc.ToString(SaveOptions.DisableFormatting); + Console.WriteLine($"Reserialized XML: {reserialized}"); + // Output: a + // b + // c + + XDocument reparsed = XDocument.Parse(reserialized); + Console.WriteLine($"Reparsed value: {string.Join("", reparsed.Root!.Value.Select(c => c == '\r' ? "\\r" : c == '\n' ? "\\n" : c.ToString()))}"); + // Output: a\nb\nc\n + // + } +} \ No newline at end of file diff --git a/docs/standard/linq/snippets/preserve-white-space-serializing/RoundtrippingSolution.cs b/docs/standard/linq/snippets/preserve-white-space-serializing/RoundtrippingSolution.cs new file mode 100644 index 0000000000000..04edd2e585cb4 --- /dev/null +++ b/docs/standard/linq/snippets/preserve-white-space-serializing/RoundtrippingSolution.cs @@ -0,0 +1,46 @@ +using System; +using System.IO; +using System.Xml; +using System.Xml.Linq; + +public static class RoundtrippingSolution +{ + public static void Example() + { + // + string xmlWithCR = """ + a + b + c + """; + + XDocument doc = XDocument.Parse(xmlWithCR); + + // Create XmlWriter settings with NewLineHandling.Entitize + XmlWriterSettings settings = new XmlWriterSettings + { + NewLineHandling = NewLineHandling.Entitize, + OmitXmlDeclaration = true + }; + + // Serialize using XmlWriter + using StringWriter stringWriter = new StringWriter(); + using (XmlWriter writer = XmlWriter.Create(stringWriter, settings)) + { + doc.WriteTo(writer); + } + + string roundtrippedXml = stringWriter.ToString(); + Console.WriteLine($"Roundtripped XML: {roundtrippedXml}"); + // Output: a + // b + // c + + // Verify roundtripping preserves the original value + XDocument roundtrippedDoc = XDocument.Parse(roundtrippedXml); + bool valuesMatch = doc.Root!.Value == roundtrippedDoc.Root!.Value; + Console.WriteLine($"Values match after roundtripping: {valuesMatch}"); + // + // Output: True + } +} \ No newline at end of file diff --git a/docs/standard/linq/snippets/preserve-white-space-serializing/preserve-white-space-serializing.csproj b/docs/standard/linq/snippets/preserve-white-space-serializing/preserve-white-space-serializing.csproj new file mode 100644 index 0000000000000..c28018dbfdd4c --- /dev/null +++ b/docs/standard/linq/snippets/preserve-white-space-serializing/preserve-white-space-serializing.csproj @@ -0,0 +1,10 @@ + + + + Exe + net8.0 + enable + enable + + + \ No newline at end of file