我编写了一个工具来修复一些XML文件(即插入一些缺少的属性/值),使用C#和Linq-to-XML。该工具将现有XML文件加载到XDocument对象中。然后,它通过节点向下解析以插入缺失的数据。之后,它调用XDocument.Save()将更改保存到另一个目录。
所有这一切都很好,除了一件事:XML文件中文本中的任何& #xA; 实体都替换为换行符。当然,实体代表一个新行,但我需要在XML中保留实体,因为另一个消费者需要它。
有没有办法保存修改过的XDocument而不会丢失& #xA; 实体?
谢谢。
答案 0 :(得分:11)


实体在技术上被称为XML中的“数字字符引用”,并且在将原始文档加载到XDocument
时解析它们。这使得您的问题有待解决,因为在加载XDocument
之后,无法区分已解析的空白实体与无关紧要的空白(通常用于为纯文本查看器格式化XML文档)。因此,以下仅适用于您的文档没有任何无关紧要的空格的情况。
System.Xml
库允许通过将XmlWriterSettings
类的NewLineHandling
属性设置为Entitize
来保留空白实体。但是,在文本节点中,这只会将\r
授权给
,而不会\n
授权给

。
最简单的解决方案是从XmlWriter
类派生并覆盖其WriteString
方法,以手动将空白字符替换为其数字字符实体。 WriteString
方法恰好也是.NET授权不允许出现在文本节点中的字符的位置,例如语法标记&
,<
和{{1} },分别有权使用>
,&
和<
。
由于>
是抽象的,我们将从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)
{
// 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));
}
答案 1 :(得分:0)
如果您的文档包含要与

实体区分的无关紧要的空格,则可以使用以下(更简单)解决方案:将

字符引用临时转换为另一个字符(即尚未出现在您的文档中),执行XML处理,然后在输出结果中将字符转换回来。在下面的示例中,我们将使用私有字符U+E800
。
static string ProcessXml(string input)
{
input = input.Replace("
", "");
XDocument document = XDocument.Parse(input);
// TODO: Perform XML processing here.
string output = document.ToString();
return output.Replace("\uE800", "
");
}
请注意,由于XDocument
将数字字符引用解析为其对应的Unicode字符,因此""
实体将在输出中解析为'\uE800'
。
通常,您可以安全地使用Unicode的“私人使用区”(U+E000
- U+F8FF
)中的任何代码点。如果您想要更加安全,请检查文档中是否已存在该字符;如果是这样,从上述范围中选择另一个角色。由于您只是临时和内部使用该角色,因此您使用哪一个并不重要。在非常不可能的情况下,文档中已经存在所有私有使用字符,抛出异常;但是,我怀疑这在实践中是否会发生。