.net xml xelement innerxml

.net - ¿La mejor manera de obtener InnerXml de un XElement?



(15)

¿Cuál es la mejor manera de obtener el contenido del elemento de body mixto en el código a continuación? El elemento puede contener XHTML o texto, pero solo quiero su contenido en forma de cadena. El tipo XmlElement tiene la propiedad InnerXml , que es exactamente lo que busco.

El código tal como está escrito casi hace lo que quiero, pero incluye el elemento <body> ... </body> que lo rodea, que no quiero.

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


¿Es posible usar los objetos de espacio de nombres System.Xml para hacer el trabajo aquí en lugar de usar LINQ? Como ya mencionó, XmlNode.InnerXml es exactamente lo que necesita.


¿Qué tal usar este método de "extensión" en XElement? trabajó para mi !

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

O use un poco 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 : El código anterior tiene que usar element.Nodes() en lugar de element.Elements() . Algo muy importante para recordar la diferencia entre los dos. element.Nodes() le ofrece todo como XText , XAttribute , etc., pero XElement solo es un Element.


¿ya sabes? Lo mejor que puede hacer es volver a CDATA :( Estoy buscando soluciones aquí, pero creo que CDATA es, con mucho, el más simple y económico, no el más conveniente para desarrollar con


// el uso de Regex podría ser más rápido para simplemente recortar la etiqueta de elemento de inicio y 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);


@ Greg: Parece que has editado tu respuesta para que sea una respuesta completamente diferente. A lo que mi respuesta es sí, podría hacerlo usando System.Xml pero esperaba mojarme los pies con LINQ to XML.

Dejaré mi respuesta original a continuación en caso de que alguien más se pregunte por qué no puedo usar la propiedad .Value de XElement para obtener lo que necesito:

@Greg: la propiedad Value concatena todo el contenido de texto de cualquier nodo secundario. Entonces, si el elemento del cuerpo contiene solo texto, funciona, pero si contiene XHTML obtengo todo el texto concatenado, pero ninguna de las etiquetas.


Con todo el crédito debido a aquellos que descubrieron y probaron el mejor enfoque (¡gracias!), Aquí está envuelto en un método de extensión:

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


Creo que este es un método mucho mejor (en VB, no debería ser difícil de traducir):

Dado un XElement x:

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


Mantenlo simple y eficiente:

String.Concat(node.Nodes().Select(x => x.ToString()).ToArray())

  • El agregado es ineficaz para la memoria y el rendimiento al concatenar cadenas
  • Usar Join ("", sth) es usar una matriz de cadenas dos veces más grande que Concat ... Y se ve bastante extraño en el código.
  • Usar + = parece muy extraño, pero aparentemente no es mucho peor que usar ''+''; probablemente se optimizaría con el mismo código, porque el resultado de la asignación no se usa y el compilador puede eliminarlo de forma segura.
  • StringBuilder es tan imperativo, y todos saben que el "estado" innecesario apesta.

Personalmente, terminé escribiendo un método de extensión InnerXml usando el método Agregado:

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

Mi código de cliente es tan breve como lo sería con el antiguo espacio de nombres System.Xml:

var innerXml = myXElement.InnerXml();


Preguntándome si (observe que me deshice de b + = y solo tengo b +)

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

podría ser un poco menos eficiente que

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

No estoy 100% seguro ... pero mirando Aggregate () y string.Join () en Reflector ... Creo que lo leí como Aggregate solo agregando un valor de retorno, así que esencialmente obtienes:

cadena = cadena + cadena

versus string.Join, tiene alguna mención allí de FastStringAllocation o algo así, lo que me hace pensar que la gente de Microsoft podría haber puesto un aumento de rendimiento adicional allí. Por supuesto, mi .ToArray () llama a mi negado eso, pero solo quería ofrecer otra sugerencia.


Quería ver cuál de estas soluciones sugeridas funcionaba mejor, así que realicé algunas pruebas comparativas. Por interés, también comparé los métodos LINQ con el antiguo método System.Xml sugerido por Greg. La variación fue interesante y no era lo que esperaba, ya que los métodos más lentos son más de 3 veces más lentos que los más rápidos .

Los resultados ordenados por el más rápido al más lento:

  1. CreateReader - Instance Hunter (0.113 segundos)
  2. Sistema antiguo simple.Xml - Greg Hurlman (0.134 segundos)
  3. Agregado con concatenación de cadenas - Mike Powell (0.324 segundos)
  4. StringBuilder - Vin (0.333 segundos)
  5. String.Join en matriz - Terry (0.360 segundos)
  6. String.Concat on array - Marcin Kosieradzki (0.364)

Método

Usé un solo documento XML con 20 nodos idénticos (llamado ''sugerencia''):

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

Los números que se muestran como segundos arriba son el resultado de extraer el "XML interno" de los 20 nodos, 1000 veces seguidas, y tomar el promedio (media) de 5 ejecuciones. No XmlDocument el tiempo que tardó en cargar y analizar el XML en un XmlDocument (para el método System.Xml ) o XDocument (para todos los demás).

Los algoritmos LINQ que utilicé fueron: (C #: todos toman un "padre" XElement y devuelven la cadena XML interna)

CreateReader:

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

Agregado con concatenación de cadenas:

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 en matriz:

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

String.Concat en la matriz:

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

No he mostrado el algoritmo "Plain old System.Xml" aquí, ya que solo está llamando a .InnerXml en los nodos.

Conclusión

Si el rendimiento es importante (p. Ej., Mucho XML, analizado con frecuencia), utilizaría el método CreateReader de Daniel cada vez . Si solo está haciendo algunas consultas, es posible que desee utilizar el método Agregado más conciso de Mike.

Si está utilizando XML en elementos grandes con muchos nodos (quizás 100), probablemente comenzará a ver el beneficio de usar StringBuilder sobre el método Aggregate, pero no sobre CreateReader . No creo que los métodos Join y Concat sean más eficientes en estas condiciones debido a la penalización de convertir una lista grande en una matriz grande (incluso obvio aquí con listas más pequeñas).


Terminé usando esto:

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



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


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

Hará el trabajo por ti