Лучший способ получить InnerXml XElement? - PullRequest
140 голосов
/ 06 августа 2008

Как лучше всего получить содержимое смешанного элемента body в приведенном ниже коде? Элемент может содержать либо XHTML, либо текст, но я просто хочу его содержимое в виде строки. Тип XmlElement обладает свойством InnerXml, и это именно то, что мне нужно.

Код как написано почти делает то, что я хочу, но включает окружающий элемент <body> ... </body>, который я не хочу.

XDocument doc = XDocument.Load(new StreamReader(s));
var templates = from t in doc.Descendants("template")
                where t.Attribute("name").Value == templateName
                select new
                {
                   Subject = t.Element("subject").Value,
                   Body = t.Element("body").ToString()
                };

Ответы [ 15 ]

200 голосов
/ 10 ноября 2009

Я хотел посмотреть, какое из этих предложенных решений работает лучше всего, поэтому я провел несколько сравнительных испытаний. Из интереса я также сравнил методы LINQ с простым старым System.Xml методом, предложенным Грегом. Вариация была интересной и не такой, как я ожидал: самые медленные методы были более чем в 3 раза медленнее, чем самые быстрые .

Результаты, упорядоченные от самого быстрого до самого медленного:

  1. CreateReader - Instance Hunter (0,113 секунды)
  2. Простой старый System.Xml - Грег Херлман (0,134 секунды)
  3. Агрегирование с конкатенацией строк - Майк Пауэлл (0,324 секунды)
  4. StringBuilder - Vin (0,333 секунды)
  5. Строка. Присоединиться к массиву - Терри (0,360 секунды)
  6. String.Concat в массиве - Марчин Косерадзки (0,364)

Метод

Я использовал один XML-документ с 20 одинаковыми узлами (называемый «подсказка»):

<hint>
  <strong>Thinking of using a fake address?</strong>
  <br />
  Please don't. If we can't verify your address we might just
  have to reject your application.
</hint>

Числа, показанные в секундах выше, являются результатом извлечения «внутреннего XML» из 20 узлов, 1000 раз подряд и получения среднего (среднего) из 5 запусков. Я не включил время, необходимое для загрузки и анализа XML в XmlDocument (для метода System.Xml ) или XDocument (для всех остальных).

Я использовал следующие алгоритмы LINQ: (C # - все принимают XElement «родителя» и возвращают внутреннюю строку XML)

CreateReader:

var reader = parent.CreateReader();
reader.MoveToContent();

return reader.ReadInnerXml();

Агрегирование с конкатенацией строк:

return parent.Nodes().Aggregate("", (b, node) => b += node.ToString());

StringBuilder:

StringBuilder sb = new StringBuilder();

foreach(var node in parent.Nodes()) {
    sb.Append(node.ToString());
}

return sb.ToString();

String.Join для массива:

return String.Join("", parent.Nodes().Select(x => x.ToString()).ToArray());

String.Concat в массиве:

return String.Concat(parent.Nodes().Select(x => x.ToString()).ToArray());

Я не показал здесь алгоритм "Обычная старая System.Xml", так как он просто вызывает .InnerXml на узлах.


Заключение

Если важна производительность (например, много XML, часто анализируемый), я бы использовал метод Даниеля CreateReader каждый раз . Если вы просто делаете несколько запросов, возможно, вы захотите использовать более краткий метод Майка Aggregate.

Если вы используете XML на больших элементах с большим количеством узлов (возможно, 100), вы, вероятно, начнете видеть преимущество использования StringBuilder по сравнению с методом Aggregate, но не над CreateReader. Я не думаю, что методы Join и Concat были бы когда-либо более эффективными в этих условиях из-за штрафных санкций за преобразование большого списка в большой массив (даже в случае с небольшими списками)

67 голосов
/ 18 марта 2009

Я думаю, что это гораздо лучший метод (в VB его не должно быть сложно перевести):

Учитывая XElement x:

Dim xReader = x.CreateReader
xReader.MoveToContent
xReader.ReadInnerXml
18 голосов
/ 20 августа 2008

Как насчет использования этого метода "extension" в XElement? работал на меня!

public static string InnerXml(this XElement element)
{
    StringBuilder innerXml = new StringBuilder();

    foreach (XNode node in element.Nodes())
    {
        // append node's xml string to innerXml
        innerXml.Append(node.ToString());
    }

    return innerXml.ToString();
}

ИЛИ использовать немного Linq

public static string InnerXml(this XElement element)
{
    StringBuilder innerXml = new StringBuilder();
    doc.Nodes().ToList().ForEach( node => innerXml.Append(node.ToString()));

    return innerXml.ToString();
}

Примечание : Приведенный выше код должен использовать element.Nodes() вместо element.Elements(). Очень важно запомнить разницу между ними. element.Nodes() дает вам все, как XText, XAttribute и т. Д., Но XElement только элемент.

13 голосов
/ 05 января 2013

При всем уважении к тем, кто нашел и доказал лучший подход (спасибо!), Здесь он заключен в метод расширения:

public static string InnerXml(this XNode node) {
    using (var reader = node.CreateReader()) {
        reader.MoveToContent();
        return reader.ReadInnerXml();
    }
}
10 голосов
/ 31 октября 2009

Сохраняйте это простым и эффективным:

String.Concat(node.Nodes().Select(x => x.ToString()).ToArray())
  • Агрегирование неэффективно при использовании памяти и производительности при конкатенации строк
  • Использование Join ("", sth) использует в два раза больший массив строк, чем Concat ... И выглядит довольно странно в коде.
  • Использование + = выглядит очень странно, но, видимо, не намного хуже, чем использование '+' - возможно, оно будет оптимизировано под тот же код, поскольку результат присваивания не используется и может быть безопасно удален компилятором.
  • StringBuilder так необходим - и все знают, что ненужное «состояние» - отстой.
7 голосов
/ 06 августа 2008

Я использовал это:

Body = t.Element("body").Nodes().Aggregate("", (b, node) => b += node.ToString());
3 голосов
/ 17 марта 2010

Лично я закончил писать метод расширения InnerXml, используя метод Aggregate:

public static string InnerXml(this XElement thiz)
{
   return thiz.Nodes().Aggregate( string.Empty, ( element, node ) => element += node.ToString() );
}

Мой клиентский код будет таким же лаконичным, как и в старом пространстве имен System.Xml:

var innerXml = myXElement.InnerXml();
2 голосов
/ 06 августа 2008

@ Грег: Похоже, вы отредактировали свой ответ, чтобы он был совершенно другим. На что мой ответ - да, я мог бы сделать это с помощью System.Xml, но надеялся получить удовольствие от LINQ to XML.

Я оставлю свой исходный ответ ниже на тот случай, если кто-нибудь еще удивится, почему я не могу просто использовать свойство .Value XElement, чтобы получить то, что мне нужно:

@ Greg: свойство Value объединяет все текстовое содержимое любых дочерних узлов. Поэтому, если элемент body содержит только текст, он работает, но если он содержит XHTML, я получаю объединенный весь текст, но ни один из тегов.

1 голос
/ 14 октября 2014

doc.ToString () или doc.ToString (SaveOptions) выполняет свою работу. Смотри http://msdn.microsoft.com/en-us/library/system.xml.linq.xelement.tostring(v=vs.110).aspx

1 голос
/ 08 февраля 2014

// использование Regex может быть быстрее, просто обрезать начальный и конечный тег элемента

var content = element.ToString();
var matchBegin = Regex.Match(content, @"<.+?>");
content = content.Substring(matchBegin.Index + matchBegin.Length);          
var matchEnd = Regex.Match(content, @"</.+?>", RegexOptions.RightToLeft);
content = content.Substring(0, matchEnd.Index);
...