实体在XML中技术上被称为“数字字符引用”,并且它们在原始文档加载到
XDocument
时被解析。这使得解决您的问题变得棘手,因为在
XDocument
被加载后,无法区分已解析的空格实体和不重要的空格(通常用于为纯文本查看器格式化XML文档)。因此,如果您的文档没有任何不重要的空格,则下面的内容仅适用。
System.Xml
库允许通过将XmlWriterSettings
类的NewLineHandling
属性设置为Entitize
来保留空格实体。然而,在文本节点内,这只会将\r
转换为
,而不是将\n
转换为

。
最简单的解决方案是从XmlWriter
类派生并覆盖其WriteString
方法,以手动替换空格字符为它们的数字字符实体。WriteString
方法也是.NET实体化不允许出现在文本节点中的字符(例如语法标记&
、<
和>
)的地方,它们分别被实体化为&
、<
和>
。
由于XmlWriter
是抽象的,因此我们将派生自XmlTextWriter
,以避免不得不实现前者类的所有抽象方法。这里是一个快速而简单的实现:
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;
}
}
}
}
如果打算在生产环境中使用,您需要放弃 c.ToString() 部分,因为它非常低效。您可以通过批处理原始 text中不包含任何要实体化的字符的子字符串,并将它们一起馈送到单个 base.WriteString 调用中进行优化代码。
警告:以下天真的实现将无法正常工作,因为基本的 WriteString 方法会将任何 &字符替换为 & ,从而导致 \r 被扩展为 &#xA; 。
public override void WriteString(string text)
{
text = text.Replace("\r", "
");
text = text.Replace("\n", "
");
text = text.Replace("\t", "	");
base.WriteString(text);
}
最后,将 XDocument
保存到目标文件或流中,只需使用以下代码片段:
using (var textWriter = new StreamWriter(destination))
using (var xmlWriter = new EntitizingXmlWriter(textWriter))
document.Save(xmlWriter)
希望这能帮到您!
编辑:供参考,以下是重写的
WriteString
方法的优化版本:
public override void WriteString(string text)
{
int start = 0;
for (int curr = 0; curr < text.Length; ++curr)
{
char chr = text[curr];
if (chr == '\r' || chr == '\n' || chr == '\t')
{
if (start < curr)
base.WriteString(text.Substring(start, curr - start));
base.WriteCharEntity(chr);
start = curr + 1;
}
}
if (start < text.Length)
base.WriteString(text.Substring(start, text.Length - start));
}