Bir XElement'in InnerXml'sini almanın en iyi yolu?


147

bodyAşağıdaki kodda bulunan karma öğenin içeriğini almanın en iyi yolu nedir ? Eleman ya XHTML ya da metin içerebilir, ancak ben sadece içeriğinin dize biçiminde olmasını istiyorum. XmlElementTip vardır InnerXmlPeşinde olduğum tam olarak ne özelliği.

Yazılan kod neredeyse istediğimi yapıyor, ancak istemediğim çevreleyen <body>... </body>öğesini içeriyor .

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

Yanıtlar:


208

Bu önerilen çözümlerden hangisinin en iyi performansı gösterdiğini görmek istedim, bu yüzden bazı karşılaştırmalı testler yaptım. İlgi dışında, LINQ yöntemlerini Greg tarafından önerilen düz eski System.Xml yöntemiyle de karşılaştırdım . Varyasyon ilginçti ve beklediğim gibi değildi, en yavaş yöntemler en hızlıdan 3 kat daha yavaştı .

En hızlıdan en yavaşa sıralanan sonuçlar:

  1. CreateReader - Örnek Avcısı (0.113 saniye)
  2. Düz eski System.Xml - Greg Hurlman (0.134 saniye)
  3. Dize birleştirme ile toplama - Mike Powell (0,324 saniye)
  4. StringBuilder - Vin (0.333 saniye)
  5. String.Join on array - Terry (0.360 saniye)
  6. String.Concat on array - Marcin Kosieradzki (0.364)

Yöntem

20 özdeş düğüme sahip tek bir XML belgesi kullandım ('ipucu' adı verilir):

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

Yukarıda saniye olarak gösterilen sayılar, 20 düğümün "iç XML" sinin arka arkaya 1000 kez çıkarılmasının ve 5 çalışmanın ortalamasının (ortalama) alınmasının sonucudur. XML'yi yüklemek ve ayrıştırmak için geçen süreyi bir XmlDocument( System.Xml yöntemi için) veya XDocument(diğerlerinin tümü için) eklemedim .

Kullandığım LINQ algoritmaları şunlardı: (C # - hepsi bir XElement"ebeveyn" alır ve iç XML dizesini döndürür)

CreateReader:

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

return reader.ReadInnerXml();

Dize birleştirme ile toplayın:

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 on array:

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

String.Concat dizide:

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

Sadece düğümlerde .InnerXml'yi çağırdığı için "Düz eski System.Xml" algoritmasını burada göstermedim.


Sonuç

Performans önemliyse (örneğin, sık sık ayrıştırılan çok sayıda XML), Daniel'ınCreateReader yöntemini her seferinde kullanırdım . Yalnızca birkaç sorgu yapıyorsanız, Mike'ın daha özlü Aggregate yöntemini kullanmak isteyebilirsiniz.

XML'i çok sayıda düğüme (belki 100'ler) sahip büyük öğelerde kullanıyorsanız, muhtemelen StringBuilderAggregate yönteminden daha fazla kullanmanın faydasını görmeye başlarsınız , ancak bitmiyor CreateReader. Büyük bir listeyi büyük bir diziye dönüştürme cezası nedeniyle (burada daha küçük listelerde bile açık) Joinve Concatyöntemlerinin bu koşullarda daha verimli olacağını sanmıyorum .


StringBuilder sürümü tek satıra yazılabilir: var result = parent.Elements (). Aggregate (new StringBuilder (), (sb, xelem) => sb.AppendLine (xelem.ToString ()), sb => sb.ToString ( ))
Softlion

7
Kaçırdınız parent.CreateNavigator().InnerXml( using System.Xml.XPathuzatma yöntemine ihtiyacınız var ).
Richard

.ToArray()İçeriye ihtiyacın olduğunu düşünmemiştim .Concat, ama daha hızlı olacak gibi görünüyor
drzaus

Eğer bu cevapların altına gidin yoktur: sadece gelen konteyner / root sıyırma düşünün .ToString()başına bu cevap . Daha da hızlı görünüyor ...
drzaus

2
Bunu gerçekten var reader = parent.CreateReader();bir using ifadesine bağlamalısın.
BrainSlugs83

70

Bunun çok daha iyi bir yöntem olduğunu düşünüyorum (VB'de çevrilmesi zor olmamalı):

Bir XElement x verildiğinde:

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

Güzel! Bu, önerilen diğer yöntemlerden çok daha hızlıdır (hepsini test ettim - ayrıntılar için cevabıma bakın). Hepsi işi yapsa da, bu en hızlı olanı yapar - hatta System.Xml.Node.InnerXml'in kendisinden daha hızlı görür!
Luke Sampson

4
XmlReader tek kullanımlıktır, bu yüzden onu kullanarak sarmayı unutmayın, lütfen (VB'yi bilseydim yanıtı kendim düzenlerdim).
Dmitry Fedorkov

19

XElement'te bu "uzantı" yöntemini kullanmaya ne dersiniz? benim için çalıştı!

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

VEYA biraz Linq kullanın

public static string InnerXml(this XElement element)
{
    StringBuilder innerXml = new StringBuilder();
    doc.Nodes().ToList().ForEach( node => innerXml.Append(node.ToString()));

    return innerXml.ToString();
}

Not : Yukarıdaki kodun element.Nodes()aksine kullanılması gerekir element.Elements(). İkisi arasındaki farkı hatırlamak çok önemli. element.Nodes()Size gibi her şeyi verir XText, XAttributevs, ama XElementsadece bir Eleman.


15

En iyi yaklaşımı keşfeden ve kanıtlayanlara tüm övgülerle (teşekkürler!), Burada bir uzatma yöntemiyle özetlenmiştir:

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

10

Basit ve verimli tutun:

String.Concat(node.Nodes().Select(x => x.ToString()).ToArray())
  • Toplama, dizeleri birleştirirken bellek ve performans açısından verimsizdir
  • Join ("", sth) kullanmak, Concat'tan iki kat daha büyük dizge dizisi kullanıyor ... Ve kodda oldukça garip görünüyor.
  • + = Kullanmak çok tuhaf görünüyor, ancak görünüşe göre '+' kullanmaktan çok daha kötü değil - muhtemelen aynı koda göre optimize edilecektir, çünkü atama sonucu kullanılmıyor ve derleyici tarafından güvenli bir şekilde kaldırılabilir.
  • StringBuilder çok önemlidir - ve herkes gereksiz "durumun" berbat olduğunu bilir.

7

Bunu kullandım:

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

Bu çok fazla dizi birleştirme yapacak - Vin'in StringBuilder'ı kullanmasını kendim tercih ederim. Manuel foreach olumsuz değildir.
Marc Gravell

Bu yöntem beni bugün gerçekten kurtardı, yeni kurucu ile bir XElement yazmaya çalışırken ve diğer yöntemlerden hiçbiri bu işe yaramazken bu işe yaramadı. Teşekkürler!
delliottg

3

Şahsen, InnerXmlAggregate yöntemini kullanarak bir uzantı yöntemi yazdım :

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

İstemci kodum daha sonra eski System.Xml ad alanında olduğu kadar kısa:

var innerXml = myXElement.InnerXml();

2

@Greg: Görünüşe göre cevabınızı tamamen farklı bir cevap olacak şekilde düzenlediniz. Cevabımın evet olduğu için, bunu System.Xml kullanarak yapabilirdim ama LINQ to XML ile ayaklarımı ıslatmayı umuyordum.

Başka birinin neden ihtiyacım olanı elde etmek için XElement'in .Value özelliğini kullanamayacağımı merak etmesi durumunda orijinal cevabımı aşağıya bırakacağım:

@Greg: Value özelliği, tüm alt düğümlerin tüm metin içeriklerini birleştirir. Bu nedenle, gövde öğesi yalnızca metin içeriyorsa çalışır, ancak XHTML içeriyorsa, tüm metni birleştirilmiş halde alırım ancak etiketlerin hiçbirini almam.


Bu tam aynı sorunu koştum ve bir hata olduğunu düşündüm: Ben 'karma' içeriği (yani vardı <root>random text <sub1>child</sub1> <sub2>child</sub2></root>oldu) random text childchildaracılığıylaXElement.Parse(...).Value
drzaus

1

// Normal ifade kullanmak, başlangıç ​​ve bitiş öğesi etiketini kırpmak için daha hızlı olabilir

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

1
temiz. kullanımı daha da hızlı IndexOf:var xml = root.ToString(); var begin = xml.IndexOf('>')+1; var end = xml.LastIndexOf('<'); return xml.Substring(begin, end-begin);
drzaus


0

LINQ kullanmak yerine işin burada yapılması için System.Xml ad alanı nesnelerini kullanmak mümkün müdür? Daha önce de belirttiğiniz gibi, XmlNode.InnerXml tam da ihtiyacınız olan şey.


0

Merak ediyorum (b + = 'den kurtulduğuma ve sadece b +' ya sahip olduğuma dikkat edin)

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

şundan biraz daha az verimli olabilir

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

% 100 emin değilim ... ama Aggregate () ve string.Join () in Reflector'a bakıyorum ... I düşünüyorum onu Aggregate olarak sadece dönen bir değer ekleyerek okudum, yani esasen şunu elde edersiniz:

string = string + string

Join'e karşılık, FastStringAllocation veya başka bir şeyden bahsediliyor, bu da bana Microsoft'taki insanların ekstra performans artışı koymuş olabileceği anlamına geliyor. Tabii ki .ToArray () benim olumsuzlamamı çağırıyor, ama ben sadece başka bir öneri sunmak istedim.


0

Bilirsin? Yapılacak en iyi şey, CDATA'ya geri dönmek :( buradaki çözümlere bakıyorum, ancak bence CDATA, açık ara en basit ve en ucuz,


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

İşi senin için yapacak


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

Ayrıca öğenin herhangi bir özelliği varsa veya yalnızca çok fazla boşluk varsa, mantık başarısız olur.
Christoph
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.