Melhor maneira de obter InnerXml de um XElement?

Qual é a melhor maneira de obter o conteúdo do elemento body misto no código abaixo? O elemento pode conter XHTML ou texto, mas eu só quero seu conteúdo em forma de string. O tipo XmlElement tem a propriedade InnerXml , que é exatamente o que eu estou procurando.

O código escrito quase faz o que eu quero, mas inclui o elemento , que eu não quero.

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

Eu queria ver quais dessas soluções sugeridas funcionavam melhor, então fiz alguns testes comparativos. Por interesse, também comparei os methods LINQ ao método antigo System.Xml sugerido por Greg. A variação foi interessante e não o que eu esperava, com os methods mais lentos sendo mais de 3 vezes mais lentos que o mais rápido .

Os resultados ordenados pelo mais rápido para o mais lento:

  1. CreateReader – Instance Hunter (0.113 segundos)
  2. Plain antigo System.Xml – Greg Hurlman (0.134 segundos)
  3. Agregado com concatenação de string – Mike Powell (0.324 segundos)
  4. StringBuilder – Vin (0.333 segundos)
  5. String.Join na matriz – Terry (0.360 segundos)
  6. String.Concat no array – Marcin Kosieradzki (0,364)

Método

Eu usei um único documento XML com 20 nós idênticos (chamado ‘dica’):

  Thinking of using a fake address? 
Please don't. If we can't verify your address we might just have to reject your application.

Os números mostrados como segundos acima são o resultado da extração do “XML interno” dos 20 nós, 1000 vezes seguidas, e da média (média) de 5 execuções. Eu não incluí o tempo que levou para carregar e analisar o XML em um XmlDocument (para o método System.Xml ) ou XDocument (para todos os outros).

Os algoritmos LINQ que usei foram: (C # – todos pegam um XElement “parent” e retornam a string XML interna)

CreateReader:

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

Agregar com concatenação de string:

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

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

String.Concat no array:

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

Eu não mostrei o algoritmo “Plain old System.Xml” aqui, pois ele está apenas chamando .InnerXml nos nós.


Conclusão

Se o desempenho é importante (por exemplo, lotes de XML, analisado com freqüência), eu usaria o método CreateReader de Daniel toda vez . Se você está apenas fazendo algumas consultas, convém usar o método agregado mais conciso de Mike.

Se você estiver usando XML em elementos grandes com vários nós (talvez 100), provavelmente começará a ver o benefício de usar StringBuilder sobre o método Aggregate, mas não sobre CreateReader . Eu não acho que os methods Join e Concat seriam sempre mais eficientes nessas condições por causa da penalidade de converter uma lista grande em um array grande (mesmo óbvio aqui com listas menores).

Eu acho que este é um método muito melhor (em VB, não deve ser difícil de traduzir):

Dado um XElement x:

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

Que tal usar este método de “extensão” no XElement? trabalhou para mim!

 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 use um pouco 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 : O código acima tem que usar element.Nodes() em oposição a element.Elements() . Coisa muito importante para lembrar a diferença entre os dois. element.Nodes() lhe dá tudo como XText , XAttribute etc, mas XElement apenas um Element.

Com todo o crédito devido àqueles que descobriram e provaram a melhor abordagem (obrigada!), Aqui está embrulhado em um método de extensão:

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

Mantenha-o simples e eficiente:

 String.Concat(node.Nodes().Select(x => x.ToString()).ToArray()) 
  • Agregado é a memory e o desempenho ineficientes ao concatenar cadeias de caracteres
  • Usando Join (“”, sth) está usando uma matriz de strings duas vezes maior que Concat … E parece bastante estranho no código.
  • Usar + = parece muito estranho, mas aparentemente não é muito pior do que usar ‘+’ – provavelmente seria otimizado para o mesmo código, porque o resultado da atribuição não é usado e pode ser removido com segurança pelo compilador.
  • O StringBuilder é tão imperativo – e todo mundo sabe que o “estado” desnecessário é uma droga.

Acabei usando isso:

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

Pessoalmente, acabei escrevendo um método de extensão InnerXml usando o método Aggregate:

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

Meu código de cliente é tão conciso quanto seria com o namespace System.Xml antigo:

 var innerXml = myXElement.InnerXml(); 

@Greg: Parece que você editou sua resposta para ser uma resposta completamente diferente. Para o que minha resposta é sim, eu poderia fazer isso usando System.Xml, mas estava esperando para obter meus pés molhados com LINQ para XML.

Deixarei minha resposta original abaixo caso alguém mais se pergunte por que não posso simplesmente usar a propriedade .Value do XElement para obter o que preciso:

@Greg: A propriedade Value concatena todo o conteúdo de texto de qualquer nós filho. Portanto, se o elemento body contiver apenas texto, ele funcionará, mas se ele contiver XHTML, receberei todo o texto concatenado junto, mas nenhuma das tags.

// usando o Regex pode ser mais rápido simplesmente aparar a tag do elemento inicial e final

 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) faz o trabalho. Consulte http://msdn.microsoft.com/pt-br/library/system.xml.linq.xelement.tostring(v=vs.110).aspx

É possível usar os objects de espaço para nome System.Xml para fazer o trabalho aqui em vez de usar o LINQ? Como você já mencionou, o XmlNode.InnerXml é exatamente o que você precisa.

Imaginando se (note que me livrei do b + = e só tenho b +)

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

pode ser um pouco menos eficiente do que

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

Não 100% de certeza … mas olhando para Aggregate () e string.Join () no Reflector … Eu acho que eu li como Agregado apenas adicionando um valor de retorno, então essencialmente você tem:

string = string + string

Em vez disso, tem alguma menção lá de FastStringAllocation ou algo assim, o que me faz pensar que o pessoal da Microsoft pode ter colocado algum impulso extra de desempenho lá. É claro que meu .ToArray () chama isso de negar, mas eu só queria oferecer outra sugestão.

você sabe? A melhor coisa a fazer é voltar ao CDATA 🙁 estou olhando para soluções aqui, mas eu acho que CDATA é de longe o mais simples e mais barato, não o mais conveniente para desenvolver com 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("", xElement.Name), ""); return innerXml.Trim(); }