XDocument.Save () удаляет мой & # xA; юридические лица - PullRequest
6 голосов
/ 11 января 2012

Я написал инструмент для восстановления некоторых файлов XML (т. Е. Вставки некоторых отсутствующих атрибутов / значений) с использованием C # и Linq-to-XML.Инструмент загружает существующий файл XML в объект XDocument.Затем он анализирует узел, чтобы вставить отсутствующие данные.После этого он вызывает XDocument.Save () для сохранения изменений в другом каталоге.

Все это просто отлично, за исключением одного: любые сущности, которые есть в тексте вXML-файл заменяется символом новой строки.Конечно, сущность представляет новую строку, но мне нужно сохранить сущность в XML, потому что она нужна другому потребителю.

Есть ли способ сохранить измененный XDocument без потери сущностей?

Спасибо.

Ответы [ 2 ]

11 голосов
/ 15 января 2012

Субъекты 
 технически называются «ссылками на числовые символы» в XML и разрешаются при загрузке исходного документа в XDocument.Это делает вашу проблему проблематичной для решения, так как нет способа отличить разрешенные пробельные объекты от незначительного пробела (обычно используемого для форматирования документов XML для средств просмотра обычного текста) после загрузки XDocument.Таким образом, нижеприведенное применимо только в том случае, если в вашем документе нет незначительных пробелов.

Библиотека System.Xml позволяет сохранять пробельные объекты, устанавливая свойство NewLineHandling для XmlWriterSettings класс до 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));
}
0 голосов
/ 15 января 2012

Если ваш документ содержит незначительные пробелы, которые вы хотите отличить от ваших &#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' в выводе.

Как правило, вы можете безопасно использовать любую кодовую точку из «Частной области использования» Юникода (U+E000 - U+F8FF).Если вы хотите быть в большей безопасности, проверьте, что символ еще не присутствует в документе;если это так, выберите другого персонажа из указанного диапазона.Поскольку вы будете использовать персонажа только временно и внутри, не имеет значения, какой персонаж вы используете.В очень маловероятном случае, когда все символы частного использования уже присутствуют в документе, выдается исключение;однако я сомневаюсь, что это когда-либо случится на практике.

...