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

mah*_*eng 6 c# xml entity linq-to-xml

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

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

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

谢谢.

Dou*_*las 11

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

System.Xml库允许通过将类的NewLineHandling属性设置为来保留空白实体.然而,文本节点内,这只会entitize 来的,而不是来.XmlWriterSettingsEntitize\r
\n


最简单的解决方案是从XmlWriter类派生并覆盖其WriteString方法,以手动将空白字符替换为其数字字符实体.该WriteString方法也恰好是其中.NET实体化有不允许出现在文本节点,如语法标记字符的地方&,<>,它们分别实体化于&amp;,&lt;,和&gt;.

既然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;
            }
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

如果打算在生产环境中使用,您需要取消该c.ToString()部件,因为它的效率非常低.您可以通过批处理原始的子字符串来优化代码,这些子字符串text不包含您要授权的任何字符,并将它们组合到一个base.WriteString调用中.

警告:以下天真实现不起作用,因为基本WriteString方法将替换任何&字符&amp;,从而导致\r扩展为&amp;#xA;.

    public override void WriteString(string text)
    {
        text = text.Replace("\r", "&#xD;");
        text = text.Replace("\n", "&#xA;");
        text = text.Replace("\t", "&#x9;");
        base.WriteString(text);
    }
Run Code Online (Sandbox Code Playgroud)

最后,要将您保存XDocument到目标文件或流中,只需使用以下代码段:

using (var textWriter = new StreamWriter(destination))
using (var xmlWriter = new EntitizingXmlWriter(textWriter))
    document.Save(xmlWriter);
Run Code Online (Sandbox Code Playgroud)

希望这可以帮助!

编辑:作为参考,这是被覆盖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));
}
Run Code Online (Sandbox Code Playgroud)