Il modo migliore per ottenere InnerXml di un XElement?

Qual è il modo migliore per ottenere il contenuto dell’elemento body misti nel codice qui sotto? L’elemento potrebbe contenere sia XHTML o testo, ma voglio solo il suo contenuto in forma di stringa. Il tipo XmlElement ha la proprietà InnerXml che è esattamente ciò che sto InnerXml .

Il codice come scritto fa quasi ciò che voglio, ma include l’elemento che non mi interessa.

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

Volevo vedere quale di queste soluzioni suggerite funzionasse al meglio, quindi ho eseguito alcuni test comparativi. Non interessante, ho anche confrontato i metodi LINQ con il semplice metodo System.Xml suggerito da Greg. La variazione era interessante e non quello che mi aspettavo, con i metodi più lenti che erano più di 3 volte più lenti del più veloce .

I risultati ordinati dal più veloce al più lento:

  1. CreateReader – Instance Hunter (0,113 secondi)
  2. Semplice vecchio System.Xml – Greg Hurlman (0.134 secondi)
  3. Aggregato con concatenazione di stringhe – Mike Powell (0,324 secondi)
  4. StringBuilder – Vin (0.333 secondi)
  5. String.Join su array – Terry (0,360 secondi)
  6. String.Concat on array – Marcin Kosieradzki (0.364)

Metodo

Ho usato un singolo documento XML con 20 nodes identici (chiamato ‘suggerimento’):

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

I numeri mostrati come secondi sopra sono il risultato dell’estrazione del “XML interno” dei 20 nodes, 1000 volte di seguito, e della media (media) di 5 esecuzioni. Non ho incluso il tempo necessario per caricare e analizzare l’XML in un XmlDocument (per il metodo System.Xml ) o XDocument (per tutti gli altri).

Gli algoritmi LINQ che ho usato erano: (C # – tutti prendono un “genitore” XElement e restituiscono la stringa XML interna)

CreateReader:

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

Aggregato con concatenazione di stringhe:

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

 return String.Join("", parent.Nodes().Select(x => x.ToString()).ToArray()); 

String.Concat su array:

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

Non ho mostrato l’algoritmo “Plain old System.Xml” qui mentre sta chiamando .InnerXml sui nodes.


Conclusione

Se le prestazioni sono importanti (ad esempio un sacco di XML, analizzate frequentemente), utilizzerei il metodo di CreateReader di Daniel di Daniel ogni volta . Se stai solo facendo alcune domande, potresti voler utilizzare il metodo Aggregate più conciso di Mike.

Se stai usando XML su elementi di grandi dimensioni con molti nodes (forse 100), probabilmente inizierai a vedere il vantaggio di usare StringBuilder sul metodo Aggregate, ma non su CreateReader . Non penso che i metodi Join e Concat sarebbero mai più efficienti in queste condizioni a causa della penalità di convertire una grande lista in un array di grandi dimensioni (anche qui evidente con elenchi più piccoli).

Penso che questo sia un metodo molto migliore (in VB, non dovrebbe essere difficile da tradurre):

Dato un XElement x:

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

Che ne dici di usare questo metodo di “estensione” su XElement? ha funzionato per 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(); } 

O usa un po ‘di Linq

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

Nota : il codice sopra deve utilizzare element.Nodes() anziché element.Elements() . Cosa molto importante da ricordare la differenza tra i due. element.Nodes() ti dà tutto come XText , XAttribute etc, ma XElement solo un Element.

Con tutto il merito dovuto a coloro che hanno scoperto e dimostrato l’approccio migliore (grazie!), Qui è racchiuso in un metodo di estensione:

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

Mantenerlo semplice ed efficiente:

 String.Concat(node.Nodes().Select(x => x.ToString()).ToArray()) 
  • L’aggregazione è la memoria e le prestazioni inefficienti quando si concatenano le stringhe
  • L’utilizzo di Join (“”, sth) utilizza un array di stringhe due volte più grande di Concat … e sembra piuttosto strano nel codice.
  • Usare + = sembra molto strano, ma a quanto pare non è molto peggio di usare ‘+’ – probabilmente sarebbe ottimizzato per lo stesso codice, perché il risultato dell’assegnazione non è utilizzato e potrebbe essere rimosso in modo sicuro dal compilatore.
  • StringBuilder è così imperativo – e tutti sanno che lo “stato” non necessario fa schifo.

Ho finito per usare questo:

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

Personalmente, ho finito per scrivere un metodo di estensione InnerXml usando il metodo Aggregate:

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

Il mio codice cliente è quindi altrettanto preciso del vecchio spazio dei nomi System.Xml:

 var innerXml = myXElement.InnerXml(); 

@ Greg: Sembra che hai modificato la tua risposta per essere una risposta completamente diversa. A cui la mia risposta è sì, potrei farlo usando System.Xml ma speravo di avere i piedi bagnati con LINQ in XML.

Lascerò la mia risposta originale qui sotto nel caso in cui qualcun altro si chiedesse perché non posso semplicemente usare la proprietà .Value di XElement per ottenere ciò di cui ho bisogno:

@ Greg: la proprietà Value concatena tutto il contenuto del testo di qualsiasi nodo figlio. Quindi se l’elemento body contiene solo testo funziona, ma se contiene XHTML ottengo tutto il testo concatenato insieme ma nessuno dei tag.

// usare Regex potrebbe essere più veloce per tagliare semplicemente il tag dell’elemento begin e end

 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 () o doc.ToString (SaveOptions) esegue il lavoro. Vedi http://msdn.microsoft.com/en-us/library/system.xml.linq.xelement.tostring(v=vs.110).aspx

È ansible utilizzare gli oggetti dello spazio dei nomi System.Xml per eseguire il lavoro qui anziché utilizzare LINQ? Come hai già detto, XmlNode.InnerXml è esattamente ciò di cui hai bisogno.

Mi chiedo se (nota che mi sono sbarazzato di b + = e ho solo b +)

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

potrebbe essere leggermente meno efficiente di

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

Non sicuro al 100% … ma guardando Aggregate () e string.Join () in Reflector … Penso di averlo letto come Aggregate aggiungendo semplicemente un valore restituito, quindi in sostanza ottieni:

stringa = stringa + stringa

versus string.Join, ha qualche riferimento a FastStringAllocation o qualcosa del genere, il che mi fa capire che la gente di Microsoft potrebbe aver messo un po ‘di extra in termini di prestazioni. Ovviamente il mio .ToArray () chiama il mio negato, ma volevo solo offrire un altro suggerimento.

sai? la cosa migliore da fare è tornare a CDATA 🙁 Sto guardando le soluzioni qui, ma penso che CDATA sia di gran lunga il più semplice ed economico, non il più conveniente da sviluppare con

 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("", xElement.Name), ""); return innerXml.Trim(); }