XDocument.Save()删除了我的
 实体

我编写了一个工具来修复一些XML文件(即插入一些缺少的属性/值),使用C#和Linq-to-XML。 该工具将现有XML文件加载到XDocument对象中。 然后,它通过节点向下解析以插入缺失的数据。 之后,它调用XDocument.Save()将更改保存到另一个目录。

所有这一切都很好,除了一件事:任何
 XML文件中文本中的实体将替换为换行符。 当然,实体代表一个新行,但我需要在XML中保留实体,因为另一个消费者需要它。

有没有办法保存修改过的XDocument而不会丢失
 实体?

谢谢。

实体在技术上被称为XML中的“数字字符引用”,并且在将原始文档加载到XDocument时它们被解析。 这使得您的问题有待解决,因为在加载XDocument之后,无法将已解析的空白实体与无意义的空白区域(通常用于为纯文本查看器格式化XML文档)区分开来。 因此,以下仅适用于您的文档没有任何无关紧要的空白。

System.Xml库允许通过将XmlWriterSettings类的NewLineHandling属性设置为NewLineHandling来保留空白实体。 但是,在文本节点中,这只会将\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) { // 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)); } 

如果您的文档中包含无关紧要的空格,您希望将其与 实体,您可以使用以下(更简单)解决方案:转换 字符临时引用另一个字符(文档中尚未存在),执行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+E000U+F8FF )中的任何代码点。 如果您想要更加安全,请检查文档中是否已存在该字符; 如果是这样,从上述范围中选择另一个角色。 由于您只是在临时和内部使用该角色,因此您使用哪一个并不重要。 在非常不可能的情况下,文档中已经存在所有私有使用字符,抛出exception; 但是,我怀疑这在实践中是否会发生。