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

标签 c# xml entity linq-to-xml

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

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

有没有办法在不丢失 实体的情况下保存修改后的XDocument?

谢谢。

最佳答案


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

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

如果打算在生产环境中使用,您需要取消 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));
}

关于c# - XDocument.Save() 删除了我的 实体,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/8811873/

相关文章:

xml - Tokenize() 中的 XSL XPATH if 语句

android - 如何通过 Android 应用更新/修改现有实体(在 Google 应用引擎上存储为 JSON 对象)

c++ - 程序已触发断点,0x77239D11 (ntdll.dll) : 0xC0000374: A heap has been corrupted (parameters: 0x7726D8D0) 处未处理的异常

c# - 实体上下文不更新数据库

android - 我应该启动一个线程来解析一些 xml 吗?

android - Android 上的 XmlPullParser XML

c# - 如何合并/组合两个 SqlDataReader 对象

c# - C#中通过字符串查找控件的属性

c# - 在 winforms 中嵌入用户可自定义的 richtext 或 html

c# - 你能推荐一个更好的替代这个正则表达式的方法吗?