Objects 
 technically called "numeric symbolic links" in XML, and they are resolved when the source document is loaded into XDocument . This makes it difficult to solve the problem because it is impossible to distinguish between allowed white space objects and a small space (usually used to format XML documents for plain text viewers) after loading XDocument . Therefore, the following applies only if your document does not have any minor spaces.
The System.Xml library allows System.Xml to save whitespace by setting the NewLineHandling property of the XmlWriterSettings class to Entitize . However, in text nodes this will only mean \r to 
 , not \n before 
 .
The simplest solution is to infer from the XmlWriter class and override its WriteString method to manually replace whitespace characters with their numeric characters. The WriteString method WriteString also the place where .NET includes characters that are not allowed to appear in text nodes, for example, syntax markers & , < and > , which are respectively designated as & , < and > .
Since XmlWriter is abstract, we get from XmlTextWriter to avoid having to implement all the abstract methods of the previous class. Here is a quick and dirty implementation:
public class EntitizingXmlWriter : XmlTextWriter { public EntitizingXmlWriter(TextWriter writer) : base(writer) { } public override void WriteString(string text) { foreach (char c in text) { switch (c) { case '\r': case '\n': case '\t': base.WriteCharEntity(c); break; default: base.WriteString(c.ToString()); break; } } } }
If it is intended for use in a production environment, you want to get rid of the c.ToString() , since it is very inefficient. You can optimize the code by substituting a substring of the source text that does not contain any of the characters you want to give, and combining them into a single call to base.WriteString .
A word of warning: the following naive implementation will not work, since the basic WriteString method will replace any & characters with & , thereby increasing \r to &#xA; .
public override void WriteString(string text) { text = text.Replace("\r", "
"); text = text.Replace("\n", "
"); text = text.Replace("\t", "	"); base.WriteString(text); }
Finally, to save your XDocument to a destination file or stream, simply use the following snippet:
using (var textWriter = new StreamWriter(destination)) using (var xmlWriter = new EntitizingXmlWriter(textWriter)) document.Save(xmlWriter);
Hope this helps!
Change For reference: optimized version of the overridden WriteString method:
public override void WriteString(string text) { // The start index of the next substring containing only non-entitized characters. int start = 0; // The index of the current character being checked. for (int curr = 0; curr < text.Length; ++curr) { // Check whether the current character should be entitized. char chr = text[curr]; if (chr == '\r' || chr == '\n' || chr == '\t') { // Write the previous substring of non-entitized characters. if (start < curr) base.WriteString(text.Substring(start, curr - start)); // Write current character, entitized. base.WriteCharEntity(chr); // Next substring of non-entitized characters tentatively starts // immediately beyond current character. start = curr + 1; } } // Write the trailing substring of non-entitized characters. if (start < text.Length) base.WriteString(text.Substring(start, text.Length - start)); }