Лучший способ получить InnerXml XElement?

136

Какой лучший способ получить содержимое смешанного элемента 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()
                };
Теги:
xelement
innerxml

14 ответов

184
Лучший ответ

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

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

  • CreateReader - Охотник за экземпляром (0.113 секунд)
  • Обычная старая System.Xml - Грег Херлман (0.134 секунды)
  • Совокупность с конкатенацией строк - Майк Пауэлл (0,324 секунды).
  • StringBuilder - Vin (0.333 секунд)
  • String.Join on array - Terry (0.360 секунд)
  • 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, которые я использовал, были: (С# - все принимают родительский элемент 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 on array:

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, часто анализируется), я бы использовал метод Daniel CreateReader каждый раз. Если вы просто выполняете несколько запросов, вы можете использовать более сжатый метод Aggregate Майка.

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

  • 12
    Вау, интересные вещи. Спасибо, что нашли время, чтобы запустить их!
  • 0
    Версия StringBuilder может быть записана в одну строку: var result = parent.Elements (). Aggregate (new StringBuilder (), (sb, xelem) => sb.AppendLine (xelem.ToString ()), sb => sb.ToString ( ))
Показать ещё 5 комментариев
63

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

Учитывая XElement x:

Dim xReader = x.CreateReader
xReader.MoveToContent
xReader.ReadInnerXml
  • 0
    Ницца! Это намного быстрее, чем некоторые другие предложенные методы (я проверил их все - подробности см. В моем ответе). Хотя все они выполняют свою работу, этот делает это быстрее всего - даже быстрее, чем сам System.Xml.Node.InnerXml!
  • 4
    Этот небольшой фрагмент кода был очень полезен, это должен был быть принятый ответ.
Показать ещё 2 комментария
17

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

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

    foreach (XNode node in element.Nodes())
    {
        // append node 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 только элемент.

12

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

public static string InnerXml(this XNode node) {
    using (var reader = node.CreateReader()) {
        reader.MoveToContent();
        return reader.ReadInnerXml();
    }
}
9

Держите его простым и эффективным:

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

В итоге я использовал это:

Body = t.Element("body").Nodes().Aggregate("", (b, node) => b += node.ToString());
  • 0
    Это сделает много конкатенации строк - я бы предпочел, чтобы Вин сам использовал StringBuilder. Руководство foreach не является отрицательным.
  • 0
    Этот метод действительно спас меня сегодня, пытаясь выписать XElement с новым конструктором, и ни один из других методов не пригодился ему, а этот сделал. Спасибо!
3

Лично я написал метод расширения 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

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

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

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

  • 0
    Я столкнулся с точно такой же проблемой и подумал, что это ошибка: у меня было «смешанное» содержимое (то есть <root>random text <sub1>child</sub1> <sub2>child</sub2></root> ), которое стало random text childchild через XElement.Parse(...).Value
1

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

1

//использование 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);
  • 1
    аккуратный. еще быстрее просто использовать IndexOf : var xml = root.ToString(); var begin = xml.IndexOf('>')+1; var end = xml.LastIndexOf('<'); return xml.Substring(begin, end-begin);
0

ты знаешь? самое лучшее, что нужно сделать, это вернуться на CDATA:( im смотрю на решения здесь, но я думаю, что CDATA на сегодняшний день является самым простым и дешевым, не самым удобным для разработки с помощью

0

Удивление, если (заметили, что я избавился от b + = и просто получил b +)

t.Element( "body" ).Nodes()
 .Aggregate( "", ( b, node ) => b + node.ToString() );

может быть немного менее эффективным, чем

string.Join( "", t.Element.Nodes()
                  .Select( n => n.ToString() ).ToArray() );

Не уверен на 100%... но смотря на Aggregate() и string.Join() в Reflector... Я думаю, что я прочитал его как Aggregate, просто добавляя возвращаемое значение, так что по существу вы получаете:

string = строка + строка

и string.Join, в нем есть некоторые упоминания о FastStringAllocation или что-то еще, что делает меня тем, что люди в Microsoft, возможно, добавили дополнительный прирост производительности. Конечно, мой .ToArray() вызывает мое отрицание, но я просто хотел предложить другое предложение.

0

Можно ли использовать объекты пространства имен System.Xml для выполнения задания здесь вместо использования LINQ? Как вы уже упоминали, XmlNode.InnerXml - это именно то, что вам нужно.

-3
public static string InnerXml(this XElement xElement)
{
    //remove start tag
    string innerXml = xElement.ToString().Trim().Replace(string.Format("<{0}>", xElement.Name), "");
    ////remove end tag
    innerXml = innerXml.Trim().Replace(string.Format("</{0}>", xElement.Name), "");
    return innerXml.Trim();
}
  • 0
    Поговорим о вложенных элементах с тем же именем ...

Ещё вопросы

Сообщество Overcoder
Наверх
Меню