147 votes

Meilleur moyen d'obtenir InnerXml d'un XElement ?

Quel est le meilleur moyen d'obtenir le contenu de l'échantillon mixte body dans le code ci-dessous ? L'élément peut contenir du XHTML ou du texte, mais je veux juste que son contenu soit sous forme de chaîne. Le site XmlElement a le type InnerXml ce qui est exactement ce que je recherche.

Le code tel qu'il est écrit presque fait ce que je veux, mais inclut l'environnement <body> ... </body> ce que je ne veux pas.

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

210voto

Luke Sampson Points 3359

Je voulais savoir laquelle de ces solutions était la plus performante, et j'ai donc effectué quelques tests comparatifs. Par curiosité, j'ai également comparé les méthodes LINQ à la bonne vieille méthode System.Xml méthode suggérée par Greg. La variation était intéressante et n'était pas ce à quoi je m'attendais, les méthodes les plus lentes étant les suivantes plus de 3 fois plus lent que le plus rapide .

Les résultats sont classés du plus rapide au plus lent :

  1. CreateReader - Chasseur d'instance (0.113 secondes)
  2. Plain old System.Xml - Greg Hurlman (0.134 secondes)
  3. Agrégation avec concaténation de chaînes de caractères - Mike Powell (0.324 secondes)
  4. StringBuilder - Vin (0.333 secondes)
  5. String.Join sur tableau - Terry (0.360 secondes)
  6. String.Concat sur un tableau - Marcin Kosieradzki (0.364)

Méthode

J'ai utilisé un seul document XML comportant 20 nœuds identiques (appelés "hint") :

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

Les chiffres indiqués en secondes ci-dessus sont le résultat de l'extraction du "XML interne" des 20 nœuds, 1000 fois de suite, et de la moyenne de 5 exécutions. Je n'ai pas inclus le temps nécessaire pour charger et analyser le XML dans un fichier de type XmlDocument (pour le System.Xml ) ou XDocument (pour tous les autres).

Les algorithmes LINQ que j'ai utilisés sont : (C# - tous prennent un XElement "parent" et renvoie la chaîne XML interne)

Créer un lecteur :

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

return reader.ReadInnerXml();

Agrégation avec concaténation de chaînes de caractères :

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 sur le tableau :

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

String.Concat sur le tableau :

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

Je n'ai pas montré l'algorithme "Plain old System.Xml" ici, car il s'agit simplement d'appeler .InnerXml sur les nœuds.


Conclusion

Si les performances sont importantes (par exemple, beaucoup d'XML, analysé fréquemment), je voudrais d'utiliser la méthode de Daniel CreateReader chaque fois que la méthode . Si vous n'effectuez que quelques requêtes, il est préférable d'utiliser la méthode Aggregate de Mike, plus concise.

Si vous utilisez XML sur de grands éléments avec beaucoup de nœuds (peut-être des centaines), vous commencerez probablement à voir l'avantage d'utiliser StringBuilder par rapport à la méthode de l'agrégat, mais pas par rapport à la méthode de l'eau. CreateReader . Je ne pense pas que le Join y Concat ne seraient jamais plus efficaces dans ces conditions, en raison de la pénalité liée à la conversion d'une grande liste en un grand tableau (même évidente ici avec des listes plus petites).

0 votes

La version du StringBuilder peut être écrite sur une seule ligne : var result = parent.Elements().Aggregate(new StringBuilder(), (sb, xelem) => sb.AppendLine(xelem.ToString()), sb => sb.ToString())

7 votes

Vous avez manqué parent.CreateNavigator().InnerXml (besoin using System.Xml.XPath pour la méthode d'extension).

0 votes

Je n'aurais pas pensé que tu aurais besoin de la .ToArray() à l'intérieur de .Concat mais cela semble le rendre plus rapide

71voto

Instance Hunter Points 4733

Je pense que cette méthode est bien meilleure (en VB, ça ne devrait pas être difficile à traduire) :

Étant donné un XElement x :

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

1 votes

Joli ! C'est beaucoup plus rapide que certaines des autres méthodes proposées (je les ai toutes testées - voir ma réponse pour plus de détails). Bien qu'elles fassent toutes le travail, celle-ci le fait le plus rapidement - même beaucoup plus rapidement que System.Xml.Node.InnerXml lui-même !

4 votes

XmlReader est jetable, n'oubliez donc pas de l'envelopper de using, s'il vous plaît (j'éditerais moi-même la réponse si je connaissais VB).

20voto

Vin Points 3945

Et si vous utilisiez cette méthode d'"extension" sur XElement ? cela a marché pour moi !

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

OU utiliser un peu 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();
}

Nota : Le code ci-dessus doit utiliser element.Nodes() à l'opposé de element.Elements() . Il est très important de se rappeler la différence entre les deux. element.Nodes() vous donne tout comme XText , XAttribute etc., mais XElement seulement un élément.

16voto

Todd Menier Points 3599

Avec tout le crédit dû à ceux qui ont découvert et prouvé la meilleure approche (merci !), la voici enveloppée dans une méthode d'extension :

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

10voto

Restez simple et efficace :

String.Concat(node.Nodes().Select(x => x.ToString()).ToArray())
  • L'agrégat est inefficace en termes de mémoire et de performances lors de la concaténation de chaînes de caractères.
  • Utiliser Join("", qqch) revient à utiliser un tableau de chaînes deux fois plus grand que Concat... Et cela semble assez étrange dans le code.
  • L'utilisation de += semble très étrange, mais apparemment ce n'est pas beaucoup plus mauvais que l'utilisation de '+' - le code serait probablement optimisé de la même manière, car le résultat de l'assignation est inutilisé et peut être supprimé en toute sécurité par le compilateur.
  • StringBuilder est tellement impératif - et tout le monde sait que les "états" inutiles sont nuls.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X