La meilleure façon D'obtenir InnerXml d'un XElement?

Quelle est la meilleure façon d'obtenir le contenu de l'élément mixte body dans le code ci-dessous? L'élément peut contenir soit XHTML soit du texte, mais je veux juste son contenu sous forme de chaîne de caractères. Le type XmlElement a la propriété InnerXml qui est exactement ce que je cherche.

le code tel qu'écrit presque fait ce que je veux, mais inclut l'environnement <body> ... </body> élément, 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()
                };
137
demandé sur Amirhossein Mehrvarzi 2008-08-06 22:16:55

14 réponses

je voulais voir laquelle de ces solutions proposées fonctionnait le mieux, donc j'ai fait quelques tests comparatifs. Par intérêt, J'ai aussi comparé les méthodes LINQ à l'ancien système .La méthode Xml suggérée par Greg. La variation était intéressante et pas ce que je m'attendais, avec les méthodes les plus lentes étant plus de 3 fois plus lent que le plus rapide .

Les résultats commandés par le plus rapide à la plus lente:

  1. CreateReader-Instance Hunter (0.113 secondes)
  2. Vieux système.Xml-Greg Hurlman (0,134 secondes)
  3. global avec la concaténation de chaîne - Mike Powell (0.324 secondes)
  4. StringBuilder-Vin (0.333 seconds)
  5. de la Chaîne.Rejoindre sur le tableau - Terry (0.360 secondes)
  6. de la Chaîne.Concat on array-Marcin Kosieradzki (0.364)

méthode

j'ai utilisé un document XML unique avec 20 noeuds identiques (appelé '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 nombres affichés en secondes ci-dessus sont le résultat de l'extraction du" XML interne " des 20 noeuds, 1000 fois dans une rangée, et en prenant la moyenne de 5 passages. Je n'ai pas inclus le temps qu'il a fallu pour charger et analyser le XML dans un XmlDocument (pour le système ).XML ) ou XDocument (pour tous les autres).

LINQ algorithmes j'ai utilisées ont été: (C# - prendre un XElement "parent" et de retour à l'intérieure de la chaîne XML)

CreateReader:

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

return reader.ReadInnerXml();

global avec la concaténation de chaîne:

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

de la Chaîne.Rejoindre sur le tableau:

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

de la Chaîne.Concat sur le tableau:

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

Je n'ai pas montré le bon vieux système.Algorithme Xml " ici comme il est juste appel .InnerXml sur les noeuds.


Conclusion

si la performance est importante (par ex. de XML, souvent parlé), je utiliser la méthode de Daniel CreateReader chaque fois . Si vous faites juste quelques requêtes, vous pourriez vouloir utiliser la méthode D'agrégation plus concise de Mike.

si vous utilisez XML sur de grands éléments avec beaucoup de noeuds (peut-être 100's), vous commenceriez probablement à voir l'avantage d'utiliser StringBuilder sur la méthode de L'agrégat, mais pas plus de CreateReader . Je ne pense pas que les méthodes Join et Concat seraient jamais plus efficace dans ces conditions en raison de la pénalité de conversion d'une grande liste en un grand tableau (même évident ici avec des listes plus petites).

194
répondu Luke Sampson 2016-02-07 17:40:29

je pense que c'est une méthode bien meilleure (en VB, ne devrait pas être difficile à traduire):

Donné un XElement x:

Dim xReader = x.CreateReader
xReader.MoveToContent
xReader.ReadInnerXml
66
répondu Instance Hunter 2009-03-18 17:17:25

Que Diriez-vous d'utiliser cette méthode d'extension sur XElement? a fonctionné 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 utilisez 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();
}

Note : le code ci-dessus doit utiliser element.Nodes() par opposition à element.Elements() . Chose très importante à retenir la différence entre les deux. element.Nodes() vous donne tout comme XText , XAttribute etc, mais XElement seulement un élément.

17
répondu Vin 2016-02-07 17:40:51

avec tout le mérite de ceux qui ont découvert et prouvé la meilleure approche (merci!

public static string InnerXml(this XNode node) {
    using (var reader = node.CreateReader()) {
        reader.MoveToContent();
        return reader.ReadInnerXml();
    }
}
12
répondu Todd Menier 2013-01-04 20:21:41

Keep it simple et efficace:

String.Concat(node.Nodes().Select(x => x.ToString()).ToArray())
  • le total est de la mémoire et des performances inefficace lors de la concaténation de chaînes de caractères
  • utilisant Join ( "" , sth) utilise un tableau de chaînes de caractères deux fois plus grand que Concat... Et semble assez étrange dans le code.
  • en utilisant + = semble très étrange, mais n'est apparemment pas beaucoup pire que d'utiliser ' + ' - serait probablement optimisé pour le même code, becase résultat d'attribution est inutilisée et pourrait être retiré en toute sécurité par compilateur.
  • StringBuilder est tellement impératif - et tout le monde sait que "l'état" inutile craint.
9
répondu Marcin Kosieradzki 2009-10-31 17:22:59

j'ai fini par utiliser ceci:

Body = t.Element("body").Nodes().Aggregate("", (b, node) => b += node.ToString());
7
répondu Mike Powell 2008-08-07 10:10:16

personnellement, j'ai fini par écrire une méthode d'extension InnerXml utilisant la méthode agrégée:

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

mon code client est alors aussi concis qu'il le serait avec l'ancien système.Espace de noms Xml:

var innerXml = myXElement.InnerXml();
3
répondu Martin R-L 2010-03-17 08:47:45

@Greg: il semble que vous ayez modifié votre réponse pour être une réponse complètement différente. À laquelle ma réponse est oui, je pourrais le faire en utilisant le Système.Xml mais j'espérais me mouiller les pieds avec LINQ à XML.

je vais laisser ma réponse originale ci-dessous au cas où quelqu'un d'autre se demande pourquoi je ne peux pas juste utiliser les XElement .Valeur de la propriété pour obtenir ce dont j'ai besoin:

@Greg: la propriété Value concaténate tout le contenu de texte de tous les noeuds enfant. Donc si le corps l'élément ne contient que du texte qui fonctionne, mais s'il contient du XHTML, j'obtiens tout le texte concaténé, mais aucune des balises.

2
répondu Mike Powell 2008-08-07 02:18:19

/ / L'utilisation de Regex pourrait être plus rapide pour simplement couper l'étiquette d'élément de début et de fin

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);
1
répondu user950851 2014-02-08 04:55:35

doc.ToString () ou doc.ToString (SaveOptions) fait le travail. Voir http://msdn.microsoft.com/en-us/library/system.xml.linq.xelement.tostring (v=V110).aspx

1
répondu user1920925 2014-10-13 20:08:32

Est-il possible d'utiliser le Système.Objets XML namespace pour faire le travail ici au lieu D'utiliser LINQ? Comme vous l'avez déjà mentionné, XmlNode.InnerXml est exactement ce dont vous avez besoin.

0
répondu Greg Hurlman 2008-08-06 18:36:39

Demandais si (avis que je me suis débarrassé de la b+= et b+)

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

pourrait être légèrement moins efficace que

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

pas sûr à 100%...mais en regardant Aggregate() et string.Joindre () dans le réflecteur...I penser Je l'ai lu comme agrégat juste en ajoutant une valeur de retour, donc essentiellement vous obtenez:

string = string + chaîne

contre string.Rejoindre, il a quelques mention là-dedans de Fast Tringallocation ou quelque chose, ce qui me fait quelque chose les gens de Microsoft pourraient avoir mis un coup de pouce supplémentaire de performance là-dedans. Bien sûr, ma .ToArray () appelle cela ma négation, mais je voulais juste proposer une autre suggestion.

0
répondu 2009-03-18 16:57:12

tu sais? la meilleure chose à faire est de revenir à CDATA :( je regarde des solutions ici, mais je pense que CDATA est de loin le plus simple et le moins cher, pas le plus commode à développer avec tho

0
répondu Ayyash 2009-10-25 23:39:21
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();
}
-2
répondu Shivraj 2010-08-13 07:05:02