XDocument.Save()删除我的  实体

时间:2012-01-10 23:07:06

标签: c# xml entity linq-to-xml

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

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

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

谢谢。

2 个答案:

答案 0 :(得分:11)


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

System.Xml库允许通过将XmlWriterSettings类的NewLineHandling属性设置为Entitize来保留空白实体。但是,在文本节点中,这只会将\r授权给
,而不会\n授权给


最简单的解决方案是从XmlWriter类派生并覆盖其WriteString方法,以手动将空白字符替换为其数字字符实体。 WriteString方法恰好也是.NET授权不允许出现在文本节点中的字符的位置,例如语法标记&<和{{1} },分别有权使用>&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; } } } } 部分,因为效率非常低。您可以通过对原始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);
    }

最后,要将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)

如果您的文档包含要与&#xA;实体区分的无关紧要的空格,则可以使用以下(更简单)解决方案:将&#xA;字符引用临时转换为另一个字符(即尚未出现在您的文档中),执行XML处理,然后在输出结果中将字符转换回来。在下面的示例中,我们将使用私有字符U+E800

static string ProcessXml(string input)
{
    input = input.Replace("&#xA;", "&#xE800;");
    XDocument document = XDocument.Parse(input);
    // TODO: Perform XML processing here.
    string output = document.ToString();
    return output.Replace("\uE800", "&#xA;");
}

请注意,由于XDocument将数字字符引用解析为其对应的Unicode字符,因此"&#xE800;"实体将在输出中解析为'\uE800'

通常,您可以安全地使用Unicode的“私人使用区”(U+E000 - U+F8FF)中的任何代码点。如果您想要更加安全,请检查文档中是否已存在该字符;如果是这样,从上述范围中选择另一个角色。由于您只是临时和内部使用该角色,因此您使用哪一个并不重要。在非常不可能的情况下,文档中已经存在所有私有使用字符,抛出异常;但是,我怀疑这在实践中是否会发生。