Skip to content

Commit c7cde80

Browse files
CopilotBillWagner
andauthored
Add XML roundtripping documentation for carriage return entities (#47034)
* Initial plan * Add XML roundtripping section with carriage return entity documentation Co-authored-by: BillWagner <[email protected]> * Move code samples to snippet files and fix raw string literal format Co-authored-by: BillWagner <[email protected]> * Fix code samples to compile in documentation environment and update target framework to net9.0 Co-authored-by: BillWagner <[email protected]> * Restructure code samples to use single Program class with Example methods and fix target framework Co-authored-by: BillWagner <[email protected]> * Add snippet tags to filter displayed code sections Co-authored-by: BillWagner <[email protected]> --------- Co-authored-by: copilot-swe-agent[bot] <[email protected]> Co-authored-by: BillWagner <[email protected]>
1 parent 4abbf3a commit c7cde80

File tree

4 files changed

+130
-0
lines changed

4 files changed

+130
-0
lines changed

docs/standard/linq/preserve-white-space-serializing.md

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,3 +25,31 @@ The following methods in the <xref:System.Xml.Linq.XElement> and <xref:System.Xm
2525
If the method doesn't take <xref:System.Xml.Linq.SaveOptions> 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.
2626

2727
If the method does take <xref:System.Xml.Linq.SaveOptions> 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.
28+
29+
## Roundtripping XML with carriage return entities
30+
31+
The whitespace preservation discussed in this article is different from XML roundtripping. When XML contains carriage return entities (`&#xD;`), LINQ to XML's standard serialization might not preserve them in a way that allows perfect roundtripping.
32+
33+
Consider the following example XML that contains carriage return entities:
34+
35+
```xml
36+
<x xml:space="preserve">a&#xD;
37+
b
38+
c&#xD;</x>
39+
```
40+
41+
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:
42+
43+
:::code language="csharp" source="snippets/preserve-white-space-serializing/RoundtrippingProblem.cs" Id="XmlRoundTrip":::
44+
45+
The values are different: the original was `"a\r\nb\nc\r"` but after roundtripping it becomes `"a\nb\nc\n"`.
46+
47+
### Solution: Use XmlWriter with NewLineHandling.Entitize
48+
49+
To achieve true XML roundtripping that preserves carriage return entities, use <xref:System.Xml.XmlWriter> with <xref:System.Xml.XmlWriterSettings.NewLineHandling> set to <xref:System.Xml.NewLineHandling.Entitize>:
50+
51+
:::code language="csharp" source="snippets/preserve-white-space-serializing/RoundtrippingSolution.cs" Id="XmlRoundTripFix":::
52+
53+
When you need to preserve carriage return entities for XML roundtripping, use <xref:System.Xml.XmlWriter> with the appropriate <xref:System.Xml.XmlWriterSettings> instead of LINQ to XML's built-in serialization methods.
54+
55+
For more information about <xref:System.Xml.XmlWriter> and its settings, see <xref:System.Xml.XmlWriter?displayProperty=fullName>.
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
using System;
2+
using System.Linq;
3+
using System.Xml.Linq;
4+
5+
class Program
6+
{
7+
static void Main()
8+
{
9+
Console.WriteLine("XML Roundtripping Examples");
10+
Console.WriteLine("==========================");
11+
12+
Console.WriteLine("\n1. Roundtripping Problem:");
13+
RoundtrippingProblem.Example();
14+
15+
Console.WriteLine("\n2. Roundtripping Solution:");
16+
RoundtrippingSolution.Example();
17+
}
18+
}
19+
20+
public static class RoundtrippingProblem
21+
{
22+
public static void Example()
23+
{
24+
// <XmlRoundTrip>
25+
string xmlWithCR = """
26+
<x xml:space="preserve">a&#xD;
27+
b
28+
c&#xD;</x>
29+
""";
30+
31+
XDocument doc = XDocument.Parse(xmlWithCR);
32+
Console.WriteLine($"Original parsed value: {string.Join("", doc.Root!.Value.Select(c => c == '\r' ? "\\r" : c == '\n' ? "\\n" : c.ToString()))}");
33+
// Output: a\r\nb\nc\r
34+
35+
string reserialized = doc.ToString(SaveOptions.DisableFormatting);
36+
Console.WriteLine($"Reserialized XML: {reserialized}");
37+
// Output: <x xml:space="preserve">a
38+
// b
39+
// c</x>
40+
41+
XDocument reparsed = XDocument.Parse(reserialized);
42+
Console.WriteLine($"Reparsed value: {string.Join("", reparsed.Root!.Value.Select(c => c == '\r' ? "\\r" : c == '\n' ? "\\n" : c.ToString()))}");
43+
// Output: a\nb\nc\n
44+
// </XmlRoundTrip>
45+
}
46+
}
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
using System;
2+
using System.IO;
3+
using System.Xml;
4+
using System.Xml.Linq;
5+
6+
public static class RoundtrippingSolution
7+
{
8+
public static void Example()
9+
{
10+
// <XmlRoundTripFix>
11+
string xmlWithCR = """
12+
<x xml:space="preserve">a&#xD;
13+
b
14+
c&#xD;</x>
15+
""";
16+
17+
XDocument doc = XDocument.Parse(xmlWithCR);
18+
19+
// Create XmlWriter settings with NewLineHandling.Entitize
20+
XmlWriterSettings settings = new XmlWriterSettings
21+
{
22+
NewLineHandling = NewLineHandling.Entitize,
23+
OmitXmlDeclaration = true
24+
};
25+
26+
// Serialize using XmlWriter
27+
using StringWriter stringWriter = new StringWriter();
28+
using (XmlWriter writer = XmlWriter.Create(stringWriter, settings))
29+
{
30+
doc.WriteTo(writer);
31+
}
32+
33+
string roundtrippedXml = stringWriter.ToString();
34+
Console.WriteLine($"Roundtripped XML: {roundtrippedXml}");
35+
// Output: <x xml:space="preserve">a&#xD;
36+
// b
37+
// c&#xD;</x>
38+
39+
// Verify roundtripping preserves the original value
40+
XDocument roundtrippedDoc = XDocument.Parse(roundtrippedXml);
41+
bool valuesMatch = doc.Root!.Value == roundtrippedDoc.Root!.Value;
42+
Console.WriteLine($"Values match after roundtripping: {valuesMatch}");
43+
// </XmlRoundTripFix>
44+
// Output: True
45+
}
46+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
<Project Sdk="Microsoft.NET.Sdk">
2+
3+
<PropertyGroup>
4+
<OutputType>Exe</OutputType>
5+
<TargetFramework>net8.0</TargetFramework>
6+
<ImplicitUsings>enable</ImplicitUsings>
7+
<Nullable>enable</Nullable>
8+
</PropertyGroup>
9+
10+
</Project>

0 commit comments

Comments
 (0)