Question

Quelle est la meilleure façon d'obtenir le contenu du mélange body élément 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 XmlElement le type a le InnerXml propriété qui est exactement ce que je recherche.

Le code tel qu'écrit presque fait ce que je veux, mais inclut les autres <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()
                };
Était-ce utile?

La solution

Je voulais voir laquelle de ces solutions suggérées fonctionnait le mieux, j'ai donc effectué quelques tests comparatifs.Par intérêt, j'ai également comparé les méthodes LINQ aux anciennes méthodes Système.Xml méthode suggérée par Greg.La variation était intéressante et ne correspondait pas à ce à quoi je m'attendais, les méthodes les plus lentes étant plus de 3 fois plus lent que le plus rapide.

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

  1. CreateReader - Chasseur d'instances (0,113 secondes)
  2. Ancien System.Xml simple - Greg Hurlman (0,134 secondes)
  3. Agrégation avec concaténation de chaînes - Mike Powell (0,324 seconde)
  4. StringBuilder - Vin (0,333 secondes)
  5. String.Join sur le tableau - Terry (0,360 secondes)
  6. String.Concat sur tableau - Marcin Kosieradzki (0.364)

Méthode

J'ai utilisé un seul document XML avec 20 nœuds identiques (appelé « indice ») :

<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 nœuds, 1 000 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 XmlDocument (pour le Système.Xml méthode) ou XDocument (pour tous les autres).

Les algorithmes LINQ que j'ai utilisés étaient : (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éger avec concaténation de chaînes :

return parent.Nodes().Aggregate("", (b, node) => b += node.ToString());

Générateur de chaînes :

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é ici l'algorithme "Plain old System.Xml" car il appelle simplement .InnerXml sur les nœuds.


Conclusion

Si la performance est importante (par ex.beaucoup de XML, analysé fréquemment), je utiliser celui de Daniel CreateReader méthode à chaque fois.Si vous effectuez simplement quelques requêtes, vous souhaiterez peut-être utiliser la méthode Aggregate, plus concise, de Mike.

Si vous utilisez XML sur des éléments volumineux comportant de nombreux nœuds (peut-être des centaines), vous commencerez probablement à voir les avantages de l'utilisation de StringBuilder sur la méthode Aggregate, mais pas sur CreateReader.Je ne pense pas que Join et Concat les méthodes seraient toujours 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).

Autres conseils

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

Étant 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() te donne tout comme XText, XAttribute etc, mais XElement seulement un élément.

Tout le mérite revient à ceux qui ont découvert et prouvé la meilleure approche (merci !), elle est ici 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();
    }
}

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
  • Utiliser Join("", sth) utilise un tableau de chaînes deux fois plus grand que Concat...Et cela semble assez étrange dans le code.
  • Utiliser += semble très étrange, mais n'est apparemment pas bien pire que d'utiliser '+' - serait probablement optimisé pour le même code, car le résultat de l'affectation n'est pas utilisé et pourrait être supprimé en toute sécurité par le compilateur.
  • StringBuilder est tellement impératif - et tout le monde sait qu'un "état" inutile est nul.

J'ai fini par utiliser ceci :

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

Personnellement, j'ai fini par écrire un InnerXml méthode d'extension en utilisant la méthode Aggregate :

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 espace de noms System.Xml :

var innerXml = myXElement.InnerXml();

@Greg :Il semble que vous ayez modifié votre réponse pour qu'elle soit une réponse complètement différente.À quoi ma réponse est oui, je pourrais le faire en utilisant System.Xml mais j'espérais me familiariser avec LINQ to XML.

Je laisserai ma réponse originale ci-dessous au cas où quelqu'un d'autre se demanderait pourquoi je ne peux pas simplement utiliser la propriété .Value de XElement pour obtenir ce dont j'ai besoin :

@Greg :La propriété Value concatène tout le contenu textuel de tous les nœuds enfants.Donc, si l'élément body ne contient que du texte, cela fonctionne, mais s'il contient du XHTML, tout le texte est concaténé mais aucune des balises.

// utiliser Regex pourrait être plus rapide pour simplement couper la balise 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=vs.110).aspx

Est-il possible d'utiliser les objets d'espace de noms System.Xml pour effectuer le travail ici au lieu d'utiliser LINQ ?Comme vous l'avez déjà mentionné, XmlNode.InnerXml est exactement ce dont vous avez besoin.

Vous vous demandez si (remarquez que je me suis débarrassé du b+= et que j'ai juste 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 jetant un coup d'œil à Aggregate() et string.Join() dans Reflector...I pense Je l'ai lu comme un agrégat en ajoutant simplement une valeur de retour, donc essentiellement vous obtenez :

chaîne = chaîne + chaîne

par rapport à string.Join, il y a une mention de FastStringAllocation ou quelque chose du genre, ce qui me fait penser que les gens de Microsoft auraient pu y ajouter une amélioration supplémentaire des performances.Bien sûr, mon .ToArray() appelle mon annulation, mais je voulais juste proposer une autre suggestion.

Vous savez?la meilleure chose à faire est de revenir à CDATA :( je cherche des solutions ici mais je pense que CDATA est de loin le plus simple et le moins cher, pas le plus pratique à développer avec

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

Fera le travail pour vous

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();
}
Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top