XDocument.Save()删除我的
实体
本文关键字:amp#xA 实体 我的 删除 Save XDocument | 更新日期: 2023-09-27 18:19:53
我编写了一个工具,使用C#和Linq to XML修复一些XML文件(即插入一些丢失的属性/值)。该工具将现有的XML文件加载到XDocument对象中。然后,它向下解析节点以插入丢失的数据。之后,它调用XDocument.Save()将更改保存到另一个目录中。
所有这些都很好,除了一件事:任何&xAXML文件中文本中的实体将替换为换行符。当然,该实体表示一个新行,但我需要在XML中保留该实体,因为另一个消费者需要它。
是否有任何方法可以在不丢失&xA实体?
谢谢。


实体在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)
{
// 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+E000
–U+F8FF
)中的任何代码点。如果您想更加安全,请检查该字符是否已存在于文档中;如果是,则从所述范围中选择另一个字符。由于您只会在内部临时使用角色,因此使用哪一个并不重要。在文档中已经存在所有专用字符的极不可能的情况下,抛出异常;然而,我怀疑这种情况在实践中是否会发生。