Cara terbaik untuk mendapatkan InnerXml dari XElement?

147

Apa cara terbaik untuk mendapatkan konten bodyelemen campuran dalam kode di bawah ini? Elemen mungkin berisi XHTML atau teks, tapi saya hanya ingin isinya dalam bentuk string. The XmlElementtipe memiliki InnerXmlproperti yang persis apa yang saya setelah.

Kode yang ditulis hampir melakukan apa yang saya inginkan, tetapi termasuk elemen <body>... sekitarnya </body>, yang saya tidak inginkan.

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

Jawaban:

208

Saya ingin melihat solusi mana yang disarankan yang paling berhasil, jadi saya menjalankan beberapa tes perbandingan. Karena ketertarikan, saya juga membandingkan metode LINQ dengan metode System.Xml tua yang disarankan oleh Greg. Variasi itu menarik dan bukan yang saya harapkan, dengan metode paling lambat lebih dari 3 kali lebih lambat daripada yang tercepat .

Hasil yang dipesan paling cepat hingga paling lambat:

  1. CreateReader - Pemburu Instance (0,113 detik)
  2. System.Xml tua polos - Greg Hurlman (0,134 detik)
  3. Agregat dengan penggabungan string - Mike Powell (0,324 detik)
  4. StringBuilder - Vin (0,333 detik)
  5. String.Gabung di larik - Terry (0,360 detik)
  6. String.Compat pada larik - Marcin Kosieradzki (0.364)

metode

Saya menggunakan dokumen XML tunggal dengan 20 node identik (disebut 'hint'):

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

Angka-angka yang ditunjukkan sebagai detik di atas adalah hasil dari mengekstraksi "XML dalam" dari 20 node, 1000 kali berturut-turut, dan mengambil rata-rata (rata-rata) dari 5 run. Saya tidak memasukkan waktu yang diperlukan untuk memuat dan mem-parsing XML ke dalam XmlDocument(untuk metode System.Xml ) atau XDocument(untuk semua yang lain).

Algoritma LINQ yang saya gunakan adalah: (C # - semua mengambil XElement"induk" dan mengembalikan string XML dalam)

CreateReader:

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

return reader.ReadInnerXml();

Agregat dengan penggabungan 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.Gabung di array:

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

String.Concat pada array:

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

Saya belum menunjukkan algoritma "Plain old System.Xml" di sini karena hanya memanggil .InnerXml pada node.


Kesimpulan

Jika kinerjanya penting (misalnya banyak XML, sering diuraikan), saya akan menggunakan CreateReadermetode Daniel setiap waktu . Jika Anda hanya melakukan beberapa pertanyaan, Anda mungkin ingin menggunakan metode Agregat Mike yang lebih ringkas.

Jika Anda menggunakan XML pada elemen besar dengan banyak node (mungkin 100-an), Anda mungkin akan mulai melihat manfaat menggunakan StringBuilderlebih dari metode Agregat, tetapi tidak lebih CreateReader. Saya tidak berpikir Joindan Concatmetode akan lebih efisien dalam kondisi ini karena penalti mengubah daftar besar ke array besar (bahkan jelas di sini dengan daftar yang lebih kecil).

Luke Sampson
sumber
Versi StringBuilder dapat ditulis pada satu baris: var result = parent.Elements (). Agregat (StringBuilder baru (), (sb, xelem) => sb.AppendLine (xelem.ToString ()), sb => sb.ToString () ))
Softlion
7
Anda melewatkan parent.CreateNavigator().InnerXml(perlu using System.Xml.XPathuntuk metode ekstensi).
Richard
Saya tidak akan berpikir Anda membutuhkan bagian .ToArray()dalam .Concat, tetapi tampaknya membuatnya lebih cepat
drzaus
Jika Anda tidak menggulir ke bagian bawah jawaban ini: pertimbangkan hanya membuang wadah / root dari .ToString()per jawaban ini . Tampaknya lebih cepat ...
drzaus
2
Anda harus benar-benar membungkusnya var reader = parent.CreateReader();dalam pernyataan menggunakan.
BrainSlugs83
70

Saya pikir ini adalah metode yang jauh lebih baik (dalam VB, seharusnya tidak sulit untuk diterjemahkan):

Diberikan XElement x:

Dim xReader = x.CreateReader
xReader.MoveToContent
xReader.ReadInnerXml
Pemburu Instance
sumber
Bagus! Ini jauh lebih cepat daripada beberapa metode lain yang diusulkan (saya menguji semuanya - lihat jawaban saya untuk detail). Meskipun mereka semua melakukan pekerjaannya, ini yang paling cepat - bahkan terlihat lebih cepat daripada System.Xml.Node.InnerXml sendiri!
Luke Sampson
4
XmlReader adalah sekali pakai, jadi jangan lupa untuk membungkusnya dengan menggunakan, tolong (saya akan mengedit jawabannya sendiri jika saya tahu VB).
Dmitry Fedorkov
19

Bagaimana kalau menggunakan metode "ekstensi" ini di XElement? bekerja untukku!

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

ATAU gunakan sedikit Linq

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

    return innerXml.ToString();
}

Catatan : Kode di atas harus digunakan element.Nodes()sebagai lawan element.Elements(). Sangat penting untuk mengingat perbedaan antara keduanya. element.Nodes()memberi Anda segalanya seperti XText, XAttributedll, tetapi XElementhanya sebuah Elemen.

Vin
sumber
15

Dengan segala penghargaan kepada mereka yang menemukan dan membuktikan pendekatan terbaik (terima kasih!), Di sini dibungkus dengan metode ekstensi:

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

Tetap sederhana dan efisien:

String.Concat(node.Nodes().Select(x => x.ToString()).ToArray())
  • Agregat adalah memori dan kinerja tidak efisien ketika menggabungkan string
  • Menggunakan Join ("", sth) menggunakan string array dua kali lebih besar daripada Concat ... Dan terlihat aneh dalam kode.
  • Menggunakan + = terlihat sangat aneh, tetapi tampaknya tidak jauh lebih buruk daripada menggunakan '+' - mungkin akan dioptimalkan ke kode yang sama, karena hasil penugasan tidak digunakan dan mungkin dihapus dengan aman oleh kompiler.
  • StringBuilder sangat penting - dan semua orang tahu bahwa "keadaan" yang tidak perlu itu menyebalkan.
Marcin Kosieradzki
sumber
7

Saya akhirnya menggunakan ini:

Body = t.Element("body").Nodes().Aggregate("", (b, node) => b += node.ToString());
Mike Powell
sumber
Itu akan melakukan banyak penggabungan string - Saya lebih suka menggunakan StringBuilder dari Kevin sendiri. Foreach manual bukan negatif.
Marc Gravell
Metode ini benar-benar menyelamatkan saya hari ini, mencoba untuk menulis XElement dengan konstruktor baru dan tidak ada metode lain yang meminjamkan diri ke sana dengan mudah, sementara yang ini melakukannya. Terima kasih!
delliottg
3

Secara pribadi, saya akhirnya menulis InnerXmlmetode ekstensi menggunakan metode Agregat:

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

Kode klien saya kemudian sama singkatnya dengan namespace System.Xml yang lama:

var innerXml = myXElement.InnerXml();
Martin RL
sumber
2

@Reg: Tampaknya Anda telah mengedit jawaban Anda menjadi jawaban yang sama sekali berbeda. Untuk jawaban saya adalah ya, saya bisa melakukan ini dengan menggunakan System.Xml tetapi berharap kaki saya basah dengan LINQ ke XML.

Saya akan meninggalkan balasan asli saya di bawah ini jika ada orang lain yang bertanya-tanya mengapa saya tidak bisa hanya menggunakan properti .Nilai XElement untuk mendapatkan yang saya butuhkan:

@Reg: Properti Value merangkai semua konten teks dari simpul anak apa pun. Jadi, jika elemen tubuh hanya berisi teks berfungsi, tetapi jika mengandung XHTML saya mendapatkan semua teks disatukan tetapi tidak ada tag.

Mike Powell
sumber
Saya mengalami masalah yang sama persis dan saya pikir ini adalah bug: Saya punya konten 'campuran' (yaitu <root>random text <sub1>child</sub1> <sub2>child</sub2></root>) yang menjadi random text childchildviaXElement.Parse(...).Value
drzaus
1

// menggunakan Regex mungkin lebih cepat untuk hanya memotong tag elemen awal dan akhir

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);
pengguna950851
sumber
1
rapi. bahkan lebih cepat untuk hanya menggunakan IndexOf:var xml = root.ToString(); var begin = xml.IndexOf('>')+1; var end = xml.LastIndexOf('<'); return xml.Substring(begin, end-begin);
drzaus
0

Apakah mungkin untuk menggunakan objek namespace System.Xml untuk menyelesaikan pekerjaan di sini daripada menggunakan LINQ? Seperti yang sudah Anda sebutkan, XmlNode.InnerXml adalah persis apa yang Anda butuhkan.

Greg Hurlman
sumber
0

Ingin tahu jika (perhatikan saya menyingkirkan b + = dan hanya memiliki b +)

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

mungkin sedikit kurang efisien daripada

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

Tidak 100% yakin ... tapi melirik Aggregate () dan string.Join () di Reflector ... Saya pikir saya membacanya sebagai Aggregate hanya menambahkan nilai kembali, jadi pada dasarnya Anda mendapatkan:

string = string + string

versus string. Bergabunglah, ada beberapa yang menyebutkan di sana tentang FastString Allocation atau sesuatu, yang membuat saya merasa orang-orang di Microsoft mungkin telah memberikan beberapa peningkatan kinerja tambahan di sana. Tentu saja .ToArray () saya meniadakan negasi saya itu, tetapi saya hanya ingin menawarkan saran lain.


sumber
0

kamu tahu? hal terbaik untuk dilakukan adalah kembali ke CDATA :( saya melihat solusi di sini tetapi saya pikir CDATA sejauh ini adalah yang paling sederhana dan termurah, bukan yang paling nyaman untuk dikembangkan dengan tho

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

Akan melakukan pekerjaan untukmu

Vinod Srivastav
sumber
-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();
}
Shivraj
sumber
Dan juga jika elemen tersebut memiliki atribut atau bahkan hanya spasi terlalu banyak logika gagal.
Christoph