2

誰かがこれを説明できたら驚くだろうが、他の人が私が経験している奇妙さを再現できるかどうかを知ることは興味深い.

多くのフォームを処理する InfoPath に基づくものがあります。フォーム データは XSD に準拠する必要がありますが、InfoPath は独自のメタデータをいわゆる "my-fields" の形式で追加し続けます。my-field を削除したいので、次の簡単な方法を書きました。

string StripMyFields(string xml)
{
    var doc = new XmlDocument();
    doc.LoadXml(xml);

    var matches = doc.SelectNodes("//node()").Cast<XmlNode>().Where(n => n.NamespaceURI.StartsWith("http://schemas.microsoft.com/office/infopath/"));
    Dbug("Found {0} nodes to remove.", matches.Count());
    foreach (var m in matches)
        m.ParentNode.RemoveChild(m);

    return doc.OuterXml;
}

今、本当に奇妙なものが来ます!このコードを実行すると、予想どおりに動作し、InfoPath 名前空間にあるすべてのノードが削除されます。ただし、Dbug の呼び出しをコメント アウトすると、コードは完了しますが、XML には 1 つの「my-field」が残ります。

便利な Dbug メソッドの内容をコメントアウトしましたが、それでも同じように動作します。

void Dbug(string s, params object[] args)
{
    //if (args.Length > 0)
    //    s = string.Format(s, args);
    //Debug.WriteLine(s);
}

入力 XML:

<?xml version="1.0" encoding="UTF-8"?>
<skjema xmlns:my="http://schemas.microsoft.com/office/infopath/2003/myXSD/2008-03-03T22:25:25" xml:lang="en-us">
    <Field-1643 orid="1643">data.</Field-1643>
    <my:myFields>
        <my:field1>Al</my:field1>
        <my:group1>
            <my:group2>
                <my:field2 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">2009-01-01</my:field2>
                <Field-1611 orid="1611">More data.</Field-1611>
                <my:field3>true</my:field3>
            </my:group2>
            <my:group2>
                <my:field2>2009-01-31</my:field2>
                <my:field3>false</my:field3>
            </my:group2>
        </my:group1>
    </my:myFields>
    <Field-1612 orid="1612">Even more data.</Field-1612>
    <my:field3>Blah blah</my:field3>
</skjema>

"my:field3" 要素 (下部のテキスト "Blah blah") は、Dbug を呼び出さない限り削除されません。

宇宙がこのようなものであってはならないことは明らかですが、他の人が再現できるかどうか知りたいです.

Win8 Enterprise 6.2.9200 で VS2012 Premium (11.0.50727.1 RTMREL) と FW 4.5.50709 を使用しています。

4

4 に答える 4

3

まず最初に。LINQ は、遅延実行と呼ばれる概念を使用します。これは、実際にクエリを具体化するまで(たとえば、列挙を介して)、結果がフェッチされないことを意味します。

ノードの削除の問題が問題になるのはなぜですか? コードで何が起こるか見てみましょう。

  1. SelectNodesによって返されるデータをフィードするXPathNodeIteratorによって使用されます。XPathNavigatorXmlNodeListSelectNodes
  2. XPathNodeIterator提供された XPath 式に基づいて xml ドキュメント ツリーをウォークします
  3. CastandWhereによって返されたノードXPathNodeIteratorが最終結果に参加するかどうかを決定するだけです

DBugメソッド呼び出しの直前に到着します。しばらくの間、それがないと仮定してください。この時点では、実際にはまだも起こっていません。マテリアライズされていないLINQ クエリのみを取得しました。

繰り返し始めると状況が変わります。すべてのイテレータ (独自のイテレータも取得) がローリングを開始しますCast。item を要求し、最終的に最初のノードを返すものを要求します ( )。残念ながら、これはテストに失敗したため、次のものを求めます。でさらに運が良ければ、それは一致です - 削除します。WhereWhereIteratorCastIteratorXPathNodeIteratorField-1643Wheremy:myFields

すぐにmy:field1(再びWhereIteratorCastIteratorXPathNodeIterator ) に進みますが、これも削除されます。ここでちょっと立ち止まってください。削除my:field1すると、その親から切り離され、その ( my:field1) 兄弟が設定されます (null削除されたノードの前後に他のノードはありません)。

現在の状況はどうですか?現在の要素が削除されたばかりのノードであることをXPathNodeIterator知っています。親から切り離されmy:field1たように削除されましたが、反復子は参照を保持しています。いいですね、次のノードを聞いてみましょう。何が?アイテムをチェックし、 (最初に歩く子がいないため) を要求します。そして、これは繰り返しが終わったことを意味します。ジョブ完了。XPathNodeIteratorCurrentNextSiblingnull

その結果、反復中にコレクション構造を変更することで、ドキュメントから 2 つのノードのみを削除しました (2 番目に削除されたノードは、既に削除されたノードの子ノードであったため、実際には 1 つだけでした)。

はるかに単純な XML でも同じ動作が見られます。

<Root>
    <James>Bond</James>
    <Jason>Bourne</Jason>
    <Jimmy>Keen</Jimmy>
    <Tom />
    <Bob />
</Root>

で始まるノードを取り除き、J正直な人の名前だけを含むドキュメントを作成するとします。

var doc = new XmlDocument();
doc.LoadXml(xml);

var matches = doc
    .SelectNodes("//node()")
    .Cast<XmlNode>()
    .Where(n => n.Name.StartsWith("J"));

foreach (var node in matches)
{
    node.ParentNode.RemoveChild(node);
}

Console.WriteLine(doc.InnerXml);

残念ながら、ジェイソンジミーは残っています。Jamesの次の兄弟 (イテレータによって返されるもの) はもともとJasonになる予定でしたが、 Jamesをツリーから切り離すとすぐに兄弟がなくなり、反復が終了します。

では、なぜそれが動作するのDBugでしょうか? Countcall はクエリをマテリアライズします。イテレータが実行され、ループを開始するときに必要なすべてのノードにアクセスできるようになりました。ToList直後に呼び出されたWhere場合、またはデバッグ中に結果を検査した場合も同じことが起こります(VS は、結果を検査するとコレクションが列挙されることを通知します)。

于 2013-07-08T23:09:24.460 に答える
0

jimmy_keen のソリューションは私にとってはうまくいきました。私はただ単純なものを持っていました

//d is an XmlDocument
XmlNodeList t = d.SelectNodes(xpath);
foreach (XmlNode x in t)
{
    x.ParentNode.RemoveChild(x);
}
d.Save(outputpath);

デバッグモードでステップスルーすると1000以上のノードが削除されるのに対し、これは3つのノードのみを削除します。

foreach が問題を解決する前に Count を追加するだけです:

var カウント = t.Count;

于 2016-11-08T13:28:10.263 に答える
0

非常に奇妙です。デバッグ中に実際に結果を表示した場合にのみ、最後のノードが削除されます。ちなみに、結果をリストに変換してからループすることもできます。

List<XmlNode> matches = doc.SelectNodes("//node()").Cast<XmlNode>().Where(n =>   n.NamespaceURI.StartsWith("http://schemas.microsoft.com/office/infopath/")).ToList();
        foreach (var m in matches)
        {
            m.ParentNode.RemoveChild(m);
        }
于 2013-07-08T14:26:08.033 に答える