Pergunta

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

O código conforme escrito quase faz o que eu quero, mas inclui o entorno <body>...</body> elemento, o 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()
                };
Foi útil?

Solução

Eu queria ver qual dessas soluções sugeridas tinha melhor desempenho, então fiz alguns testes comparativos.Por curiosidade, também comparei os métodos LINQ com os métodos antigos Sistema.Xml método sugerido por Greg.A variação foi interessante e não o que eu esperava, sendo os métodos mais lentos mais de 3 vezes mais lento que o mais rápido.

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

  1. CreateReader - Caçador de instâncias (0,113 segundos)
  2. System.Xml simples e antigo - Greg Hurlman (0,134 segundos)
  3. Agregação com concatenação de strings - Mike Powell (0,324 segundos)
  4. StringBuilder - Vin (0,333 segundos)
  5. String.Join na matriz - Terry (0,360 segundos)
  6. String.Concat na matriz - Marcin Kosieradzki (0,364)

Método

Usei um único documento XML com 20 nós idênticos (chamados de 'dica'):

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

Os números mostrados em segundos acima são o resultado da extração do "XML interno" dos 20 nós, 1.000 vezes consecutivas, e da média (média) de 5 execuções.Não incluí o tempo necessário para carregar e analisar o XML em um XmlDocument (para o Sistema.Xml método) ou XDocument (para todos os outros).

Os algoritmos LINQ que usei foram: (C# - todos pegam um XElement "parent" e retornar a string XML interna)

CriarLeitor:

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

return reader.ReadInnerXml();

Agregar com concatenação de strings:

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

Construtor de strings:

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

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

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


Conclusão

Se o desempenho for importante (por ex.muito XML, analisado com frequência), eu use o Daniel CreateReader método sempre.Se você estiver fazendo apenas algumas consultas, talvez queira usar o método Aggregate mais conciso de Mike.

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

Outras dicas

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 esse método de “extensão” no XElement?funcionou 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();
}

Observação:O código acima deve usar element.Nodes() em oposição a element.Elements().Coisa muito importante lembrar a diferença entre os dois. element.Nodes() te dá tudo como XText, XAttribute etc, mas XElement apenas um elemento.

Com todo o crédito àqueles que descobriram e provaram a melhor abordagem (obrigado!), aqui está tudo encerrado 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 é ineficiente em termos de memória e desempenho ao concatenar strings
  • Usar Join("", sth) está usando um array 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 é utilizado e pode ser removido com segurança pelo compilador.
  • StringBuilder é tão imperativo - e todo mundo sabe que "estado" desnecessário é uma droga.

Acabei usando isso:

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

Pessoalmente, acabei escrevendo um InnerXml método de extensão 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 antigo namespace System.Xml:

var innerXml = myXElement.InnerXml();

@Greg:Parece que você editou sua resposta para ser uma resposta completamente diferente.Minha resposta é sim, eu poderia fazer isso usando System.Xml, mas esperava começar com LINQ to XML.

Deixarei minha resposta original abaixo caso alguém 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ó filho.Portanto, se o elemento body contém apenas texto, ele funciona, mas se contém XHTML, concateno todo o texto, mas nenhuma das tags.

// usar Regex pode ser mais rápido simplesmente cortar 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.Ver http://msdn.microsoft.com/en-us/library/system.xml.linq.xelement.tostring(v=vs.110).aspx

É possível usar os objetos de namespace System.Xml para realizar o trabalho aqui em vez de usar o LINQ?Como você já mencionou, XmlNode.InnerXml é exatamente o que você precisa.

Querendo saber se (observe 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 tenho 100% de certeza... mas olhando para Aggregate() e string.Join() no Reflector...I pensar Eu li como Aggregate apenas anexando um valor de retorno, então essencialmente você obtém:

sequência = sequência + sequência

versus string.Join, há alguma menção a FastStringAllocation ou algo assim, o que me faz pensar que o pessoal da Microsoft pode ter colocado algum aumento extra de desempenho lá.É claro que meu .ToArray() chama isso de negação, mas eu só queria oferecer outra sugestão.

você sabe?a melhor coisa a fazer é voltar ao CDATA :( estou procurando soluções aqui, mas acho que o CDATA é de longe o mais simples e barato, não o mais conveniente para desenvolver com ele

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

Fará o trabalho para você

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();
}
Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top