Linq-to-XML XElement.Remove () оставляет нежелательные пробелы - PullRequest
10 голосов
/ 28 июля 2011

У меня есть XDocument, который я создаю из байтового массива (полученного через tcp / ip).

Затем я ищу конкретные узлы xml (XElements) и после получения значения «выталкиваю» его изXdocument путем вызова XElement.Remove ().После того, как весь мой синтаксический анализ завершен, я хочу иметь возможность регистрировать xml, который я не анализировал (оставшийся xml в XDocument).Проблема в том, что при вызове XElement.Remove () остается дополнительный пробел.Я хочу знать, как лучше всего удалить этот лишний пробел, сохранив остальную часть формата в оставшемся xml.

Пример / Пример кода

Если я получуследующий xml через сокет:

<?xml version="1.0"?>
<catalog>
   <book id="bk101">
      <author>Gambardella, Matthew</author>
      <title>XML Developer's Guide</title>
      <genre>Computer</genre>
      <price>44.95</price>
      <publish_date>2000-10-01</publish_date>
      <description>An in-depth look at creating applications with XML.</description>
   </book>
</catalog>

И я использую следующий код, чтобы проанализировать этот xml и удалить несколько элементов XElements:

private void socket_messageReceived(object sender, MessageReceivedEventArgs e)
{
     XDocument xDoc;
     try
     {
         using (MemoryStream xmlStream = new MemoryStream(e.XmlAsBytes))
         using (XmlTextReader reader = new XmlTextReader(xmlStream))
         {
             xDoc = XDocument.Load(reader);
         }

         XElement Author = xDoc.Root.Descendants("author").FirstOrDefault();
         XElement Title  = xDoc.Root.Descendants("title").FirstOrDefault();
         XElement Genre  = xDoc.Root.Descendants("genre").FirstOrDefault();

         // Do something with Author, Title, and Genre here...

         if (Author != null) Author.Remove();
         if (Title  != null) Title.Remove();
         if (Genre  != null) Genre.Remove();

         LogUnparsedXML(xDoc.ToString());

     }
     catch (Exception ex)
     {
         // Exception Handling here...
     }
}

Затем полученная строка xml отправляется насообщение LogUnparsedXML будет выглядеть следующим образом:

<?xml version="1.0"?>
<catalog>
   <book id="bk101">



      <price>44.95</price>
      <publish_date>2000-10-01</publish_date>
      <description>An in-depth look at creating applications with XML.</description>
   </book>
</catalog>

В этом надуманном примере это может показаться не таким уж большим делом, но в моем реальном приложении оставшийся xml выглядит довольно небрежно.Я попытался использовать перегрузку XDocument.ToString, которая принимает перечисление SaveOptions безрезультатно.Я также пытался вызвать xDoc.Save для сохранения в файл, используя перечисление SaveOptions.Я попытался поэкспериментировать с несколькими различными запросами linq, которые использовали XElement.Nodes().OfType<XText>(), чтобы попытаться удалить пробел, но часто я заканчивал тем, что брал пробел, который хотел бы сохранить, вместе с пробелом, от которого я пытаюсь избавиться.

Заранее спасибо за помощь.

Джо

1 Ответ

4 голосов
/ 28 июля 2011

Нелегко ответить переносимым способом, потому что решение во многом зависит от того, как XDocument.Load() генерирует текстовые узлы с пробелами (и существует несколько реализаций LINQ to XML, которые могут не согласиться с этой тонкой деталью).

Тем не менее, похоже, что вы никогда не удаляете last child (<description>) из <book> элементов. Если это действительно так, то нам не нужно беспокоиться об отступе закрывающего тега родительского элемента, и мы можем просто удалить элемент и все последующие текстовые узлы, пока не достигнем другого элемента. TakeWhile () выполнит эту работу.

РЕДАКТИРОВАТЬ: Ну, кажется, вам нужно удалить последнего ребенка в конце концов. Поэтому все будет сложнее. Код ниже реализует следующий алгоритм:

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

Полученный код:

public static void RemoveWithNextWhitespace(this XElement element)
{
    IEnumerable<XText> textNodes
        = element.NodesAfterSelf()
                 .TakeWhile(node => node is XText).Cast<XText>();
    if (element.ElementsAfterSelf().Any()) {
        // Easy case, remove following text nodes.
        textNodes.ToList().ForEach(node => node.Remove());
    } else {
        // Remove trailing whitespace.
        textNodes.TakeWhile(text => !text.Value.Contains("\n"))
                 .ToList().ForEach(text => text.Remove());
        // Fetch text node containing newline, if any.
        XText newLineTextNode
            = element.NodesAfterSelf().OfType<XText>().FirstOrDefault();
        if (newLineTextNode != null) {
            string value = newLineTextNode.Value;
            if (value.Length > 1) {
                // Composite text node, trim until newline (inclusive).
                newLineTextNode.AddAfterSelf(
                    new XText(value.SubString(value.IndexOf('\n') + 1)));
            }
            // Remove original node.
            newLineTextNode.Remove();
        }
    }
    element.Remove();
}

Оттуда вы можете сделать:

if (Author != null) Author.RemoveWithNextWhitespace();
if (Title  != null) Title.RemoveWithNextWhitespace();
if (Genre  != null) Genre.RemoveWithNextWhitespace();

Хотя я бы посоветовал вам заменить вышеприведенное чем-то вроде цикла, передаваемого из массива, или вызовом метода params, чтобы избежать избыточности кода.

...