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

Как лучше всего получить содержимое смешанного 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)

Решение

I wanted to see which of these suggested solutions performed best, so I ran some comparative tests. Out of interest, I also compared the LINQ methods to the plain old System.Xml method suggested by Greg. The variation was interesting and not what I expected, with the slowest methods being more than 3 times slower than the fastest.

The results ordered by fastest to slowest:

  1. CreateReader - Instance Hunter (0.113 seconds)
  2. Plain old System.Xml - Greg Hurlman (0.134 seconds)
  3. Aggregate with string concatenation - Mike Powell (0.324 seconds)
  4. StringBuilder - Vin (0.333 seconds)
  5. String.Join on array - Terry (0.360 seconds)
  6. String.Concat on array - Marcin Kosieradzki (0.364)

Method

I used a single XML document with 20 identical nodes (called 'hint'):

<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>

The numbers shown as seconds above are the result of extracting the "inner XML" of the 20 nodes, 1000 times in a row, and taking the average (mean) of 5 runs. I didn't include the time it took to load and parse the XML into an XmlDocument (for the System.Xml method) or XDocument (for all the others).

The LINQ algorithms I used were: (C# - all take an XElement "parent" and return the inner XML string)

CreateReader:

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

return reader.ReadInnerXml();

Aggregate with string concatenation:

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 on array:

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

I haven't shown the "Plain old System.Xml" algorithm here as it's just calling .InnerXml on nodes.


Conclusion

If performance is important (e.g. lots of XML, parsed frequently), I'd use Daniel's CreateReader method every time. If you're just doing a few queries, you might want to use Mike's more concise Aggregate method.

If you're using XML on large elements with lots of nodes (maybe 100's), you'd probably start to see the benefit of using StringBuilder over the Aggregate method, but not over CreateReader . I don't think the Join and Concat methods would ever be more efficient in these conditions because of the penalty of converting a large list to a large array (even obvious here with smaller lists).

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

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

Body = t.Element("body").Nodes().Aggregate("", (b, node) => b += node.ToString());
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();
}

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

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

// с помощью 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);

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

var innerXmlAsText= XElement.Parse(xmlContent)
                    .Descendants()
                    .Where(n => n.Name.LocalName == "template")
                    .Elements()
                    .Single()
                    .ToString();

Сделаю работу за вас

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

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

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

Как насчет использования этого метода «расширения» в 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 только элемент.

Интересно, если (обратите внимание, я избавился от 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, просто добавляя возвращаемое значение, поэтому по сути вы получаете:

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

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

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

Учитывая XElement x:

Dim xReader = x.CreateReader
xReader.MoveToContent
xReader.ReadInnerXml

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

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

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

Лично я закончил тем, что написал 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();