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()
};
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:
- CreateReader-Instance Hunter (0.113 secondes)
- Vieux système.Xml-Greg Hurlman (0,134 secondes)
- global avec la concaténation de chaîne - Mike Powell (0.324 secondes)
- StringBuilder-Vin (0.333 seconds)
- de la Chaîne.Rejoindre sur le tableau - Terry (0.360 secondes)
- 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).
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
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.
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();
}
}
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.
j'ai fini par utiliser ceci:
Body = t.Element("body").Nodes().Aggregate("", (b, node) => b += node.ToString());
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();
@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.
/ / 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);
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
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.
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.
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
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();
}