Der beste Weg, um InnerXml eines XElement zu erhalten?

147

Was ist der beste Weg, um den Inhalt des gemischten bodyElements im folgenden Code zu erhalten? Das Element kann entweder XHTML oder Text enthalten, aber ich möchte nur, dass der Inhalt in Zeichenfolgenform vorliegt. Der XmlElementTyp hat die InnerXmlEigenschaft, die genau das ist, wonach ich suche.

Der geschriebene Code macht fast das, was ich will, enthält aber das umgebende <body>... </body>Element, das ich nicht will.

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

Antworten:

208

Ich wollte sehen, welche dieser Lösungsvorschläge am besten funktioniert, und führte daher einige Vergleichstests durch. Aus Interesse habe ich auch die LINQ-Methoden mit der von Greg vorgeschlagenen einfachen System.Xml- Methode verglichen . Die Variation war interessant und nicht das, was ich erwartet hatte. Die langsamsten Methoden waren mehr als dreimal langsamer als die schnellsten .

Die Ergebnisse sortiert nach schnellsten bis langsamsten:

  1. CreateReader - Instance Hunter (0,113 Sekunden)
  2. Einfache alte System.Xml - Greg Hurlman (0,134 Sekunden)
  3. Aggregat mit String-Verkettung - Mike Powell (0,324 Sekunden)
  4. StringBuilder - Vin (0,333 Sekunden)
  5. String.Join on Array - Terry (0,360 Sekunden)
  6. String.Concat auf Array - Marcin Kosieradzki (0.364)

Methode

Ich habe ein einzelnes XML-Dokument mit 20 identischen Knoten verwendet (als "Hinweis" bezeichnet):

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

Die oben als Sekunden angezeigten Zahlen sind das Ergebnis des Extrahierens des "inneren XML" der 20 Knoten, 1000 Mal hintereinander, und des Durchschnitts (Mittelwerts) von 5 Läufen. Ich habe nicht die Zeit angegeben, die zum Laden und Parsen des XML in eine XmlDocument(für die System.Xml- Methode) oder XDocument(für alle anderen) benötigt wurde.

Die von mir verwendeten LINQ-Algorithmen waren: (C # - alle nehmen ein XElement"übergeordnetes Element" und geben die innere XML-Zeichenfolge zurück)

CreateReader:

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

return reader.ReadInnerXml();

Aggregat mit Zeichenfolgenverkettung:

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 auf Array:

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

Ich habe den Algorithmus "Plain old System.Xml" hier nicht gezeigt, da er nur .InnerXml auf Knoten aufruft.


Fazit

Wenn die Leistung wichtig ist (z. B. viel XML, häufig analysiert), würde ich jedes Mal Daniels CreateReaderMethode verwenden . Wenn Sie nur ein paar Abfragen durchführen, möchten Sie möglicherweise Mikes präzisere Aggregatmethode verwenden.

Wenn Sie XML für große Elemente mit vielen Knoten (möglicherweise 100) verwenden, werden Sie wahrscheinlich den Vorteil der Verwendung StringBuildergegenüber der Aggregatmethode erkennen, jedoch nicht über CreateReader. Ich denke nicht, dass die Methoden Joinund unter Concatdiesen Bedingungen jemals effizienter wären, da die Konvertierung einer großen Liste in ein großes Array die Strafe darstellt (auch hier bei kleineren Listen offensichtlich).

Luke Sampson
quelle
Die StringBuilder-Version kann in eine Zeile geschrieben werden: var result = parent.Elements (). Aggregate (new StringBuilder (), (sb, xelem) => sb.AppendLine (xelem.ToString ()), sb => sb.ToString ( ))
Softlion
7
Sie haben es verpasst parent.CreateNavigator().InnerXml(Notwendigkeit using System.Xml.XPathfür die Erweiterungsmethode).
Richard
Ich hätte nicht gedacht, dass Sie das .ToArray()Innere brauchen .Concat, aber es scheint es schneller zu machen
drzaus
Falls Sie nicht zum Ende dieser Antworten scrollen: Ziehen Sie in Betracht, den Container / die Wurzel .ToString()gemäß dieser Antwort zu entfernen . Scheint noch schneller ...
drzaus
2
Sie sollten das wirklich var reader = parent.CreateReader();in eine using-Anweisung einschließen.
BrainSlugs83
70

Ich denke, dies ist eine viel bessere Methode (in VB sollte es nicht schwer sein, sie zu übersetzen):

Bei einem XElement x:

Dim xReader = x.CreateReader
xReader.MoveToContent
xReader.ReadInnerXml
Instanzjäger
quelle
Nett! Dies ist viel schneller als einige der anderen vorgeschlagenen Methoden (ich habe sie alle getestet - siehe meine Antwort für Details). Obwohl alle den Job machen, macht dieser den schnellsten - sogar schneller als System.Xml.Node.InnerXml selbst!
Luke Sampson
4
XmlReader ist ein Einwegartikel. Vergessen Sie also nicht, ihn mit using zu verpacken (ich würde die Antwort selbst bearbeiten, wenn ich VB kenne).
Dmitry Fedorkov
19

Wie wäre es mit dieser "Erweiterungs" -Methode auf XElement? hat für mich gearbeitet!

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

ODER verwenden Sie ein wenig Linq

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

    return innerXml.ToString();
}

Hinweis : Der obige Code muss element.Nodes()im Gegensatz zu verwendet werden element.Elements(). Sehr wichtig, um sich an den Unterschied zwischen den beiden zu erinnern. element.Nodes()gibt Ihnen alles , wie XText, XAttributeetc, aber XElementnur ein Element.

Vin
quelle
15

Bei aller Anerkennung für diejenigen, die den besten Ansatz entdeckt und bewiesen haben (danke!), Wird er hier in eine Erweiterungsmethode eingepackt:

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

Halten Sie es einfach und effizient:

String.Concat(node.Nodes().Select(x => x.ToString()).ToArray())
  • Aggregat ist Speicher und Leistung ineffizient, wenn Zeichenfolgen verkettet werden
  • Die Verwendung von Join ("", etw) verwendet ein zweimal größeres String-Array als Concat ... und sieht im Code ziemlich seltsam aus.
  • Die Verwendung von + = sieht sehr seltsam aus, ist aber anscheinend nicht viel schlimmer als die Verwendung von '+' - würde wahrscheinlich auf denselben Code optimiert, da das Zuweisungsergebnis nicht verwendet wird und möglicherweise vom Compiler sicher entfernt wird.
  • StringBuilder ist so wichtig - und jeder weiß, dass unnötiger "Zustand" scheiße ist.
Marcin Kosieradzki
quelle
7

Am Ende habe ich Folgendes verwendet:

Body = t.Element("body").Nodes().Aggregate("", (b, node) => b += node.ToString());
Mike Powell
quelle
Das wird eine Menge String-Verkettung bewirken - ich würde Vins Verwendung von StringBuilder selbst vorziehen. Das Handbuch foreach ist nicht negativ.
Marc Gravell
Diese Methode hat mich heute wirklich gerettet, als ich versuchte, ein XElement mit dem neuen Konstruktor zu schreiben, und keine der anderen Methoden bot sich dafür an, während diese dies tat. Vielen Dank!
Delliottg
3

Persönlich habe ich eine InnerXmlErweiterungsmethode mit der Aggregatmethode geschrieben:

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

Mein Client-Code ist dann genauso knapp wie beim alten System.Xml-Namespace:

var innerXml = myXElement.InnerXml();
Martin RL
quelle
2

@ Greg: Anscheinend haben Sie Ihre Antwort so bearbeitet, dass sie eine völlig andere Antwort ist. Auf die ich mit Ja antworte, könnte ich dies mit System.Xml tun, hatte aber gehofft, meine Füße mit LINQ to XML nass zu machen.

Ich werde meine ursprüngliche Antwort unten hinterlassen, falls sich jemand fragt, warum ich nicht einfach die .Value-Eigenschaft des XElement verwenden kann, um das zu erhalten, was ich brauche:

@Greg: Die Value-Eigenschaft verkettet den gesamten Textinhalt aller untergeordneten Knoten. Wenn das body-Element nur Text enthält, funktioniert es, aber wenn es XHTML enthält, wird der gesamte Text verkettet, aber keines der Tags.

Mike Powell
quelle
Ich bin auf genau das gleiche Problem <root>random text <sub1>child</sub1> <sub2>child</sub2></root>random text childchildXElement.Parse(...).Value
gestoßen
1

// Die Verwendung von Regex ist möglicherweise schneller, um das Anfangs- und Endelement-Tag einfach zu kürzen

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);
user950851
quelle
1
ordentlich. noch schneller zu benutzen IndexOf:var xml = root.ToString(); var begin = xml.IndexOf('>')+1; var end = xml.LastIndexOf('<'); return xml.Substring(begin, end-begin);
drzaus
0

Ist es möglich, die System.Xml-Namespace-Objekte zu verwenden, um den Job hier zu erledigen, anstatt LINQ zu verwenden? Wie Sie bereits erwähnt haben, ist XmlNode.InnerXml genau das, was Sie brauchen.

Greg Hurlman
quelle
0

Ich frage mich, ob (beachte, dass ich das b + = losgeworden bin und nur b + habe)

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

könnte etwas weniger effizient sein als

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

Nicht 100% sicher ... aber ein Blick auf Aggregate () und string.Join () in Reflector ... Ich glaube, ich habe es als Aggregat gelesen, indem ich nur einen Rückgabewert anhänge , also erhalten Sie im Wesentlichen:

string = string + string

versus string.Join, es gibt dort einige Erwähnungen von FastStringAllocation oder so, was mich dazu bringt, dass die Leute bei Microsoft dort möglicherweise einen zusätzlichen Leistungsschub eingebracht haben. Natürlich nennt mein .ToArray () mein Negieren so, aber ich wollte nur einen weiteren Vorschlag machen.


quelle
0

Wissen Sie? Das Beste, was Sie tun können, ist, zu CDATA zurückzukehren :( Ich suche hier nach Lösungen, aber ich denke, CDATA ist bei weitem die einfachste und billigste, nicht die bequemste, die man mit tho entwickeln kann

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

Erledigt den Job für Sie

Vinod Srivastav
quelle
-2
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();
}
Shivraj
quelle
Und auch wenn das Element Attribute oder nur ein Leerzeichen zu viel hat, schlägt die Logik fehl.
Christoph