Domanda

Qual è il modo migliore per ottenere il contenuto del misto body elemento nel codice seguente?L'elemento potrebbe contenere XHTML o testo, ma voglio solo che il suo contenuto sia sotto forma di stringa.IL XmlElement il tipo ha il InnerXml proprietà che è esattamente quello che sto cercando.

Il codice così come è scritto Quasi fa quello che voglio, ma include l'ambiente circostante <body>...</body> elemento, che non voglio.

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()
                };
È stato utile?

Soluzione

Volevo vedere quale delle soluzioni suggerite funzionava meglio, quindi ho eseguito alcuni test comparativi.Per interesse, ho anche confrontato i metodi LINQ con quelli semplici System.Xml metodo suggerito da Greg.La variazione era interessante e non era quella che mi aspettavo, essendo i metodi più lenti più di 3 volte più lento del più veloce.

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

  1. CreateReader - Cacciatore di istanze (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 sull'array - Marcin Kosieradzki (0.364)

Metodo

Ho utilizzato un singolo documento XML con 20 nodi identici (chiamato 'suggerimento'):

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

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

Gli algoritmi LINQ che ho utilizzato erano: (C# - tutti prendono un XElement "parent" e restituisce la stringa XML interna)

Crea Lettore:

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 sull'array:

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

String.Concat sull'array:

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

Non ho mostrato qui l'algoritmo "Plain old System.Xml" poiché chiama semplicemente .InnerXml sui nodi.


Conclusione

Se la prestazione è importante (es.un sacco di XML, analizzati frequentemente), lo farei usa quello di Daniel CreateReader metodo ogni volta.Se stai facendo solo alcune query, potresti voler utilizzare il metodo Aggregate più conciso di Mike.

Se stai utilizzando XML su elementi di grandi dimensioni con molti nodi (forse centinaia), probabilmente inizierai a vedere il vantaggio dell'utilizzo StringBuilder rispetto al metodo Aggregato, ma non oltre CreateReader.Non penso che Join E Concat i metodi sarebbero più efficienti in queste condizioni a causa della penalità di convertire un elenco di grandi dimensioni in un array di grandi dimensioni (anche ovvio qui con elenchi più piccoli).

Altri suggerimenti

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

OPPURE 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 essere utilizzato element.Nodes() al contrario di element.Elements().Cosa molto importante da ricordare la differenza tra i due. element.Nodes() ti dà tutto come XText, XAttribute ecc, ma XElement solo un Elemento.

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

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

Mantienilo semplice ed efficiente:

String.Concat(node.Nodes().Select(x => x.ToString()).ToArray())
  • L'aggregato è inefficiente in termini di memoria e prestazioni durante la concatenazione di stringhe
  • L'uso 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 che 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.

Alla fine ho usato questo:

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

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

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

Il mio codice client è quindi altrettanto conciso come lo sarebbe con il vecchio spazio dei nomi System.Xml:

var innerXml = myXElement.InnerXml();

@Greg:Sembra che tu abbia modificato la tua risposta in modo che sia una risposta completamente diversa.A cui la mia risposta è sì, potrei farlo utilizzando System.Xml ma speravo di bagnarmi i piedi con LINQ to XML.

Lascerò la mia risposta originale di seguito nel caso qualcun altro si chieda perché non posso semplicemente utilizzare la proprietà .Value di XElement per ottenere ciò di cui ho bisogno:

@Greg:La proprietà Value concatena tutto il contenuto di 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.

// l'uso di Regex potrebbe essere più veloce per tagliare semplicemente il tag dell'elemento iniziale e finale

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

È possibile utilizzare gli oggetti dello spazio dei nomi System.Xml per portare a termine il lavoro qui invece di 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 dando un'occhiata ad Aggregate() e string.Join() in Reflector...I pensare Lo leggo come Aggregato semplicemente aggiungendo un valore restituito, quindi essenzialmente ottieni:

stringa = stringa + stringa

rispetto a string.Join, c'è qualche menzione di FastStringAllocation o qualcosa del genere, il che mi fa pensare che la gente di Microsoft potrebbe aver aggiunto un ulteriore incremento delle prestazioni.Ovviamente il mio .ToArray() mi chiama negandolo, ma volevo solo offrire un altro suggerimento.

Sai?la cosa migliore da fare è tornare a CDATA :( sto cercando delle soluzioni qui ma penso che CDATA sia di gran lunga il più semplice ed economico, non il più conveniente da sviluppare con

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

Farò il lavoro per te

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();
}
Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top