2012-01-10 2 views
6

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

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

Есть ли способ сохранить измененный XDocument без потери & #xA; объектов?

спасибо.

+1

Заменяются ли при загрузке старого документа или при его сохранении? –

+0

@Arnold: Когда я сохраняю новый. – mahdaeng

+0

Идеальное решение - исправить потребителя вашего XML, чтобы он правильно обрабатывал XML. – svick

ответ

10

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

System.Xml библиотека позволяет сохранить пробельные объекты, установив NewLineHandling свойство XmlWriterSettings класса Entitize. Однако в текстовых узлах это будет давать только \r до 
, а не \n - 
.

Самое простое решение состоит в том, чтобы получить класс XmlWriter и переопределить его метод WriteString, чтобы вручную заменить символы пробелов на их числовые символьные сущности. Метод WriteString также случается быть местом, где .NET entitizes символы, которые не разрешено появляться в текстовых узлах, таких как маркеры синтаксиса &, < и >, которые соответственно к &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); 

Надеется, что это помогает!

Edit: Для справки, здесь является оптимизированной версией переопределен 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

Это один из самых подробных ответов, которые я когда-либо видел. Я собираюсь попробовать это. Даже если он не работает (и, вероятно, будет), вы получите мой голос. Спасибо, Дуглас! – mahdaeng

+0

Добро пожаловать :-) Не забывайте, что вышеизложенное будет работать, только если у вас нет незначительных пробелов в исходном XML. Если у вас есть незначительные пробелы, я бы предложил вам использовать код из другого ответа (см. Ниже). – Douglas

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 решает числовые ссылки на символы в соответствующие им символы Юникода, "&#xE800;" объекты были бы решены в '\uE800' на выходе.

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