XElementのInnerXmlを取得する最良の方法は?


147

body以下のコードで混合要素のコンテンツを取得する最良の方法は何ですか?要素にはXHTMLまたはテキストのいずれかを含めることができますが、その内容を文字列形式で欲しいだけです。XmlElementタイプがありInnerXml、私は後だ、まさにあるプロパティを。

書かれたコードはほとんど私がしたいことをしますが、私が望まない周囲の<body>... </body>要素を含みます。

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

回答:


208

これらの提案されたソリューションのどれが最も効果的かを確認したかったので、いくつかの比較テストを実行しました。興味深いことに、私はLINQメソッドを、Gregによって提案されたプレーンな古いSystem.Xmlメソッドと比較しました。変化は興味深いものであり、予想したものではありませんでした。最も遅い方法は、最も速い方法よりも3倍以上遅くなっています

結果は、速いものから遅いものの順に並べられました。

  1. CreateReader-インスタンスハンター(0.113秒)
  2. プレーンな古いSystem.Xml-Greg Hurlman(0.134秒)
  3. 文字列連結による集計-Mike Powell(0.324秒)
  4. StringBuilder-Vin(0.333秒)
  5. String.Join on array-Terry(0.360秒)
  6. 配列のString.Concat-Marcin Kosieradzki(0.364)

方法

20個の同一のノード(「ヒント」と呼ばれる)を含む単一のXMLドキュメントを使用しました。

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

上記の秒数は、20ノードの「内部XML」を1000回続けて抽出し、5回の実行の平均(平均)をとった結果です。XMLをXmlDocumentSystem.Xmlメソッドの場合)またはXDocument(その他すべての場合)にロードして解析するのにかかる時間は含めませんでした。

私が使用したLINQアルゴリズムは次のとおりです:(C#-すべてXElement「親」を取り、内部XML文字列を返します)

CreateReader:

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

return reader.ReadInnerXml();

文字列を連結して集計:

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:

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

配列のString.Concat:

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

ノードで.InnerXmlを呼び出すだけなので、ここでは「プレーンな古いSystem.Xml」アルゴリズムを示していません。


結論

パフォーマンスが重要である場合(たとえば、大量のXMLが頻繁に解析される場合)、毎回DanielのCreateReader方法を使用します。いくつかのクエリを実行しているだけの場合は、Mikeのより簡潔なAggregateメソッドを使用することをお勧めします。

多数のノード(おそらく100のノード)を持つ大きな要素でXMLを使用StringBuilderしている場合、Aggregateメソッドではなく、ではなくAggregateメソッドを使用することの利点がわかるでしょうCreateReader。大きなリストを大きな配列に変換するペナルティ(ここでは小さなリストでも明らかです)が原因で、これらの条件ではJoinand Concatメソッドが効率的になるとは思いません。


StringBuilderバージョンは1行で記述できます:var result = parent.Elements()。Aggregate(new StringBuilder()、(sb、xelem)=> sb.AppendLine(xelem.ToString())、sb => sb.ToString( ))
Softlion、2011

7
あなたは見逃しましたparent.CreateNavigator().InnerXmlusing System.Xml.XPath拡張メソッドが必要です)。
リチャード

私はあなたが.ToArray()内部が必要だとは思っていなかったでしょう.Concat、しかしそれはそれをより速くするようです
drzaus

ケースでは、これらの答えの一番下までスクロールしません。ただのコンテナ/ルートをストリッピング考える.ToString()あたり、この答え。さらに高速に見える...
drzaus 2014年

2
あなたは本当にそれvar reader = parent.CreateReader();をusingステートメントでラップするべきです。
BrainSlugs83 2015年

70

これははるかに優れた方法だと思います(VBでは、翻訳するのは難しくありません)。

XElement xが与えられた場合:

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

いいね!これは、提案されている他のいくつかの方法よりもはるかに高速です(私はそれらすべてをテストしました-詳細は私の回答を参照してください)。それらのすべてが仕事をしますが、これは最も速く実行されます-System.Xml.Node.InnerXml自体よりも速く見えます!
ルークサンプソン

4
XmlReaderは使い捨てなので、使用してラップすることを忘れないでください(VBを知っていれば自分で回答を編集します)。
Dmitry Fedorkov 2013年

19

XElementでこの「拡張」メソッドを使用するのはどうですか?私のために働いた!

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

またはLinqを少し使用する

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

    return innerXml.ToString();
}

:上記のコードでは、ではなくを使用する必要element.Nodes()がありelement.Elements()ます。2つの違いを覚えておくことが非常に重要です。element.Nodes()あなたと同じようにすべて与えXTextXAttributeなどが、XElement唯一の要素を。


15

最善のアプローチを発見して証明した人たちに感謝の意を表して(ありがとう!)、ここでは拡張メソッドにラップされています。

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

10

シンプルかつ効率的にしてください:

String.Concat(node.Nodes().Select(x => x.ToString()).ToArray())
  • 集約は、文字列を連結するときにメモリとパフォーマンスが非効率的です
  • Join( ""、sth)を使用すると、Concatの2倍の文字列配列が使用されます...そして、コードではかなり奇妙に見えます。
  • + =の使用は非常に奇妙に見えますが、明らかに '+'の使用よりもそれほど悪くはありません-割り当て結果が未使用であり、コンパイラによって安全に削除される可能性があるため、おそらく同じコードに最適化されます。
  • StringBuilderは非常に不可欠です-そして、誰もが不必要な "状態"が悪いことを知っています。

7

私はこれを使ってしまいました:

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

それは多くの文字列連結を行います-私はVinがStringBuilderを自分で使用することを好みます。マニュアルforeachは否定的ではありません。
Marc Gravell

このメソッドは今日私を本当に救い、新しいコンストラクターでXElementを書き出そうとしましたが、他のメソッドはどれも手軽に利用できませんでしたが、このメソッドはそうしました。ありがとう!
delliottg 2014

3

個人的にはInnerXml、Aggregateメソッドを使用して拡張メソッドを作成することになりました。

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

クライアントコードは、古いSystem.Xml名前空間の場合と同じくらい簡潔になります。

var innerXml = myXElement.InnerXml();

2

@グレッグ:あなたはあなたの答えを完全に異なる答えに編集したようです。私の答えは「はい」です。System.Xmlを使用してこれを実行できましたが、LINQ to XMLで足を濡らしたいと思っていました。

XElementの.Valueプロパティを使用して必要なものを取得できない理由を他の誰かが疑問に思った場合に備えて、元の返答を以下に残しておきます。

@Greg:Valueプロパティは、子ノードのすべてのテキストコンテンツを連結します。そのため、body要素にテキストのみが含まれている場合は機能しますが、XHTMLが含まれている場合は、すべてのテキストが連結されますが、タグは含まれません。


私はこれとまったく同じ問題に遭遇し、それをバグだと思いました。私は「混合」コンテンツ(つまり<root>random text <sub1>child</sub1> <sub2>child</sub2></root>)をrandom text childchild経由していたXElement.Parse(...).Value
drzaus

1

// Regexを使用すると、要素の開始タグと終了タグを単純にトリムする方が速い場合があります

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
きちんとした。使用するだけでさらに高速IndexOfvar xml = root.ToString(); var begin = xml.IndexOf('>')+1; var end = xml.LastIndexOf('<'); return xml.Substring(begin, end-begin);
drzaus 2014年


0

LINQを使用する代わりに、System.Xml名前空間オブジェクトを使用してここでジョブを実行することは可能ですか?すでに述べたように、XmlNode.InnerXmlはまさに必要なものです。


0

(b + =を取り除き、b +を持っていることに注意してください)

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

よりわずかに効率が悪いかもしれません

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

100%確実ではありません...しかし、ReflectorのAggregate()とstring.Join()をちらっと見ます...私、戻り値を追加するだけのAggregateとして読んだと思います。

文字列=文字列+文字列

string.Joinと比較すると、FastStringAllocationなどについて言及されているため、Microsoftの担当者がパフォーマンスをさらに向上させた可能性があります。もちろん、.ToArray()はそれを否定しますが、別の提案をしたかっただけです。


0

ええと?最善の方法は、CDATAに戻ることです:(私はここで解決策を検討していますが、CDATAははるかに単純で最も安価であり、


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

あなたのために仕事をします


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

また、要素に属性がある場合、またはスペースのみの場合でも、ロジックは失敗します。
Christoph
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.