Cel mai bun mod de a obține InnerXml al unui XElement?

Care este cel mai bun mod de a obține conținutul elementului mixt body în codul de mai jos? Elementul ar putea conține fie XHTML, fie text, dar vreau doar conținutul său în formă de șir. Tipul XmlElement are proprietatea InnerXml care este exact ceea ce am după.

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()
                };
0
adăugat editat
Vizualizări: 5

13 răspunsuri

Este posibil să utilizați obiectele din spațiul de nume System.Xml pentru a obține sarcina aici în loc să utilizați LINQ? După cum ați menționat deja, XmlNode.InnerXml este exact ceea ce aveți nevoie.

0
adăugat

@Greg: Se pare că ți-ai editat răspunsul pentru a fi un răspuns complet diferit. La care răspunsul meu este da, aș putea face acest lucru folosind System.Xml, dar a fost în speranța de a obține picioarele mele ude cu LINQ la XML.

Îmi voi lăsa răspunsul meu inițial mai jos în cazul în care cineva se întreabă de ce nu pot folosi proprietatea XElement .Value pentru a obține ceea ce am nevoie:

@Greg: Proprietatea Value concatetează tot conținutul textului oricărui nod copil. Deci, dacă elementul de corp conține doar text, funcționează, dar dacă conține XHTML, obțin textul concatenat împreună, dar nici unul dintre tag-uri.

0
adăugat
Am intrat în aceeași problemă și am crezut că a fost un bug: am avut conținut "mixt" (adică text aleator copil copil ) care a devenit childchild text aleator prin XElement.Parse (...)
adăugat autor drzaus, sursa

Am ajuns să folosesc acest lucru:

Body = t.Element("body").Nodes().Aggregate("", (b, node) => b += node.ToString());
0
adăugat
Această metodă m-a salvat cu adevărat astăzi, încercând să scriu un XElement cu noul constructor și nici una dintre celelalte metode nu se împrumuta cu ușurință, în timp ce acesta a făcut-o. Mulțumiri!
adăugat autor delliottg, sursa
Asta va face o mulțime de concatenare a șirului - aș prefera ca Vin să folosească StringBuilder. Prevenirea manuală nu este negativă.
adăugat autor Marc Gravell, sursa

Personal, am ajuns să scriu o metodă de extensie InnerXml folosind metoda Aggregate:

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

Codul clientului meu este la fel de tare ca și în vechiul spațiu de nume System.Xml:

var innerXml = myXElement.InnerXml();
0
adăugat

tu stii? cel mai bun lucru de făcut este să vă întoarceți la CDATA: (im uita la soluții aici, dar cred că CDATA este de departe cel mai simplu și mai ieftin, nu cel mai convenabil de a dezvolta cu tho

0
adăugat

Cu toată încrederea acordată celor care au descoperit și au dovedit cea mai bună abordare (mulțumită!), Aici este înfășurată într-o metodă de extensie:

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

Ce zici de folosirea acestei metode de "extensie" pe XElement? a lucrat pentru mine!

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

Sau foloseste un pic de 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.

0
adăugat

doc.ToString() or doc.ToString(SaveOptions) does the work. See http://msdn.microsoft.com/en-us/library/system.xml.linq.xelement.tostring(v=vs.110).aspx

0
adăugat

Cred că aceasta este o metodă mult mai bună (în VB, nu ar trebui să fie greu de tradus):

Având un XElement x:

Dim xReader = x.CreateReader
xReader.MoveToContent
xReader.ReadInnerXml
0
adăugat
XmlReader este de unică folosință, așa că nu uitați să-l înfășurați folosind, vă rog (aș edita răspunsul dacă știam VB).
adăugat autor Dmitry Fedorkov, sursa
+1 acesta ar fi trebuit să fie răspunsul acceptat. Răspunsul lui Luke Sampsons este frumos de citit, dar ați dat calea corectă de răspuns înaintea lui.
adăugat autor Bazzz, sursa
Frumos! Aceasta este mult mai rapidă decât unele dintre celelalte metode propuse (le-am testat pe toate - vezi răspunsul meu pentru detalii). Deși toți își fac treaba, aceasta o face cel mai rapid - chiar se vede mai repede decât System.Xml.Node.InnerXml în sine!
adăugat autor Luke Sampson, sursa
Acest fragment de cod mic a fost foarte util, acesta ar fi trebuit să fie răspunsul acceptat.
adăugat autor Frank Rosario, sursa

Păstrați-l simplu și eficient:

String.Concat(node.Nodes().Select(x => x.ToString()).ToArray())
  • Agregatul este memorie și performanță ineficiente atunci când concatenarea șirurilor
  • Folosind Join ("", sth) folosiți o matrice de șir de două ori mai mare decât Concat ... Și arată destul de ciudat în cod.
  • Folosind + = pare foarte ciudat, dar se pare că nu este mult mai rău decât să folosești "+" - probabil că ar fi optimizat la același cod, deoarece rezultatul alocării nu este folosit și ar putea fi înlăturat în siguranță de compilator.
  • StringBuilder este atât de imperativ - și toată lumea știe că "starea" inutilă este insuportabilă.
0
adăugat

Am vrut să văd care dintre aceste soluții sugerate a fost cea mai bună, așa că am făcut niște teste comparative. Din interes, am comparat, de asemenea, metodele LINQ cu metoda System.Xml veche, sugerată de Greg. Variația a fost interesantă și nu cea pe care am așteptat-o, cele mai lente metode fiind mai mult de 3 ori mai lent decât cele mai rapide .

Rezultatele comandate de cel mai rapid la cel mai lent:

  1. CreateReader - Hunter de instanță (0.113 secunde)
  2. Sistemul vechi System.Xml - Greg Hurlman (0.134 secunde)
  3. Agregat cu concatenare în șir - Mike Powell (0.324 secunde)
  4. StringBuilder - Vin (0.333 secunde)
  5. String.Join pe matrice - Terry (0.360 secunde)
  6. String.Concat pe matrice - Marcin Kosieradzki (0.364)

Metoda

Am folosit un singur document xml cu 20 de noduri identice (numit "hint"):


  Thinking of using a fake address?
  
Please don't. If we can't verify your address we might just have to reject your application.

Numerele afișate ca secunde de mai sus sunt rezultatul extragerii "interiorului XML" al celor 20 de noduri, de 1000 de ori la rând, și luarea mediei (mediei) a 5 runde. Nu am inclus timpul necesar pentru a încărca și analiza xml într-un XmlDocument (pentru metoda System.Xml ) sau XDocument pentru toți ceilalți).

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

Agregat cu concatenare șir:

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 pe matrice:

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

Nu am arătat algoritmul "Câmpie veche System.Xml" aici, deoarece el doar a sunat .InnerXml pe noduri.


Concluzie

Dacă performanța este importantă (de ex. O mulțime de XML, este analizată frecvent), aș folosi metoda CreateReader de fiecare dată când . Dacă faceți doar câteva întrebări, vă recomandăm să utilizați metoda Aggregate mai concisă a lui Mike.

Dacă utilizați xml pe elemente mari cu numeroase noduri (poate 100), probabil că veți începe să vedeți beneficiul folosirii StringBuilder prin metoda Aggregate, dar nu peste CreateReader . Nu cred că metodele Alăturați-vă și Concat ar fi mai eficiente în aceste condiții datorită pedepsii de a converti o listă mare într-o gamă largă liste mai mici).

0
adăugat
Uau, lucruri interesante. Vă mulțumim că ați luat timp să le executați!
adăugat autor Mike Powell, sursa
N-aș fi crezut că ai nevoie de .ToArray() în .Concat , dar pare să o facă mai repede
adăugat autor drzaus, sursa
În cazul în care nu derulați în partea de jos a acestor răspunsuri: luați în considerare eliminarea containerului / rădăcină de la .ToString() pe acest răspuns . Se pare chiar mai rapid ...
adăugat autor drzaus, sursa
Versiunea StringBuilder poate fi scrisă pe o singură linie: var result = parent.Elements() Aggregate (new StringBuilder (), (sb, xelem) => sb.AppendLine (xelem.ToString ()), sb => sb.ToString ))
adăugat autor Softlion, sursa
Comentariu al lui @Richard. parent.CreateNavigator() .InnerXml este deosebit de frumos pentru proiectare, deoarece este inline.
adăugat autor ccook, sursa
Ai pierdut parent.CreateNavigator() InnerXml (trebuie folosind System.Xml.XPath pentru metoda extensiei).
adăugat autor Richard, sursa
Ar trebui să înfășurați cu adevărat var reader = parent.CreateReader (); într-o instrucțiune utilizată.
adăugat autor BrainSlugs83, sursa
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
adăugat
Discutați despre elemente imbricate cu același nume ...
adăugat autor Lucero, sursa

// utilizarea Regex ar putea fi mai rapidă pentru a decupa pur și simplu eticheta elementului de început și sfârșit

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);
0
adăugat
elegant. chiar mai rapid pentru a folosi IndexOf : var xml = root.ToString (); var începe = xml.IndexOf ('>') + 1; var sfârșitul = xml.LastIndexOf ('<'); returnați xml.Substring (începe, sfarsit);
adăugat autor drzaus, sursa