Best way to get InnerXml of an XElement?

Viewed 86995

What's the best way to get the contents of the mixed body element in the code below? The element might contain either XHTML or text, but I just want its contents in string form. The XmlElement type has the InnerXml property which is exactly what I'm after.

The code as written almost does what I want, but includes the surrounding <body>...</body> element, which I don't want.

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 Answers

I think this is a much better method (in VB, shouldn't be hard to translate):

Given an XElement x:

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

How about using this "extension" method on XElement? worked for me !

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();
}

OR use a little bit of Linq

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

    return innerXml.ToString();
}

Note: The code above has to use element.Nodes() as opposed to element.Elements(). Very important thing to remember the difference between the two. element.Nodes() gives you everything like XText, XAttribute etc, but XElement only an Element.

I ended up using this:

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

Personally, I ended up writing an InnerXml extension method using the Aggregate method:

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

My client code is then just as terse as it would be with the old System.Xml namespace:

var innerXml = myXElement.InnerXml();

@Greg: It appears you've edited your answer to be a completely different answer. To which my answer is yes, I could do this using System.Xml but was hoping to get my feet wet with LINQ to XML.

I'll leave my original reply below in case anyone else wonders why I can't just use the XElement's .Value property to get what I need:

@Greg: The Value property concatenates all the text contents of any child nodes. So if the body element contains only text it works, but if it contains XHTML I get all the text concatenated together but none of the tags.

Is it possible to use the System.Xml namespace objects to get the job done here instead of using LINQ? As you already mentioned, XmlNode.InnerXml is exactly what you need.

Wondering if (notice I got rid of the b+= and just have b+)

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

might be slightly less efficient than

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

Not 100% sure...but glancing at Aggregate() and string.Join() in Reflector...I think I read it as Aggregate just appending a returning value, so essentially you get:

string = string + string

versus string.Join, it has some mention in there of FastStringAllocation or something, which makes me thing the folks at Microsoft might have put some extra performance boost in there. Of course my .ToArray() call my negate that, but I just wanted to offer up another suggestion.

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

Will do the job for you

Related