3

.NET クラスをサポートする XPath で使用するには大きすぎる XML ファイルからノードを削除するための適切なロジック ループに苦労しています。

私が持っていた 1 行のコード (SelectNodes を XPath クエリ文字列で呼び出す) を、同じことを行うが XmTextReader を使用するコードに置き換えようとしています。

以前に使用した XPath クエリ (参照用) で示されているように、いくつかのレベルを下る必要があります。

ConfigurationRelease/Profiles/Profile[Name='MyProfileName']/Screens/Screen[Id='MyScreenId']/Settings/Setting[Name='MySettingName']

これは面倒だけど簡単だと思いました。ただし、ループを正しく取得できないようです。

ノードを取得し、その下のノードをチェックして、値がターゲット文字列と一致するかどうかを確認し、一致する場合はさらに下に進み、一致しない場合はそのブランチをスキップする必要があります。

実際、私の問題は、ブランチに興味がない場合、ブランチを無視する方法がわからないことだと思います。要素名が一意ではないため (XPath クエリで示されているように)、関係のない分岐をたどることはできません。

私は、Profile ノードにヒットしたときに true に設定される bool expectedProfileName など、いくつかのブール値を維持できると考えました。ただし、それが必要な特定のプロファイル ノードでない場合、そのブランチから抜け出すことはできません。

だから...うまくいけば、これは誰かにとって理にかなっています...私は問題を数時間見つめてきましたが、明らかな何かが欠けているだけかもしれません.....

ファイルの一部を投稿したいのですが、構造が大まかにわかりません。

ConfigRelease > Profiles > Profile > Name > Screens > Screen > Settings > Setting > Name

ProfileName、ScreenName、および SettingName がわかり、Setting ノードが必要です。

ファイル内の半分は使用されないため、アプリの起動時など、1 回のヒットでファイル全体を読み取らないようにしています。また、xmlファイルを生成するものを制御できないため、変更して複数の小さなファイルを生成することはできません。

どんなヒントでも大歓迎です。

アップデート

これで開き直りました。ポスターは、完璧なはずの XPathDocument を提案しました。残念ながら、これがモバイル アプリであり、XPathDocument がサポートされていないことについては言及していませんでした。

ファイルはほとんどの標準で大きくないため、システムはもともと XmlDocument を使用するようにコーディングされていました。現在 4MB であり、XmlDocument に読み込まれたときにモバイル アプリをクラッシュさせるのに十分な大きさです。ファイルがはるかに大きくなると予想されるので、おそらく今出てきたのと同じくらい良いでしょう。とにかく、私は現在 DataSet の提案を試みていますが、まだ他のアイデアを受け入れています。

更新 2

このサイズのファイルがシステムをクラッシュさせるとは思わないだろうと多くの人が言っているので、私は疑わしいと思いました. さらなる実験により、これは断続的なクラッシュであることが示されました。昨日は毎回クラッシュしましたが、今朝デバイスをリセットした後、再現できません。私は現在、信頼できる一連の生殖手順を見つけようとしています。また、まだそこにあると確信している問題を処理する最善の方法を決定します。アプリがこのファイルにアクセスできない場合、それは役に立たず、アプリの実行中にデバイスで他に何も実行できないことをユーザーに伝えることができないため、そのままにしておくことはできません... ....

4

6 に答える 6

10

をご覧くださいXPathDocument

XPathDocument は XmlDocument より軽量で、読み取り専用の XPath クエリ用に最適化されています。

于 2009-02-17T16:12:39.603 に答える
3

さて、私はこれに楽しまれていたので、いくつかのコードを一緒にハッキングしました。それはきれいではなく、この1つのユースケースを実際にサポートするだけですが、それはあなたが探している仕事をし、最初からまともなプラットフォームとして機能すると思います。私もそれを徹底的にテストしていません。最後に、内容を返すようにコードを変更する必要があります(Output()というメソッドを参照)。

コードは次のとおりです。

using System;

using System.Collections.Generic;
using System.IO;
using System.Text;
using System.Xml;

namespace XPathInCE
{
    class Program
    {
        static void Main(string[] args)
        {
            try
            {
                if (args.Length != 2)
                {
                    ShowUsage();
                }
                else
                {
                    Extract(args[0], args[1]);
                }
            }
            catch (Exception ex)
            {
                Console.WriteLine("{0} was thrown", ex.GetType());
                Console.WriteLine(ex.Message);
                Console.WriteLine(ex.StackTrace);
            }

            Console.WriteLine("Press ENTER to exit");
            Console.ReadLine();
        }

        private static void Extract(string filePath, string queryString)
        {
            if (!File.Exists(filePath))
            {
                Console.WriteLine("File not found! Path: {0}", filePath);
                return;
            }

            XmlReaderSettings settings = new XmlReaderSettings { IgnoreComments = true, IgnoreWhitespace = true };
            using (XmlReader reader = XmlReader.Create(filePath, settings))
            {
                XPathQuery query = new XPathQuery(queryString);
                query.Find(reader);
            }
        }

        static void ShowUsage()
        {
            Console.WriteLine("No file specified or incorrect number of parameters");
            Console.WriteLine("Args must be: Filename XPath");
            Console.WriteLine();
            Console.WriteLine("Sample usage:");
            Console.WriteLine("XPathInCE someXmlFile.xml ConfigurationRelease/Profiles/Profile[Name='MyProfileName']/Screens/Screen[Id='MyScreenId']/Settings/Setting[Name='MySettingName']");
        }

        class XPathQuery
        {
            private readonly LinkedList<ElementOfInterest> list = new LinkedList<ElementOfInterest>();
            private LinkedListNode<ElementOfInterest> currentNode;

            internal XPathQuery(string query)
            {
                Parse(query);
                currentNode = list.First;
            }

            internal void Find(XmlReader reader)
            {
                bool skip = false;
                while (true)
                {
                    if (skip)
                    {
                        reader.Skip();
                        skip = false;
                    }
                    else
                    {
                        if (!reader.Read())
                        {
                            break;
                        }
                    }
                    if (reader.NodeType == XmlNodeType.EndElement
                            && String.Compare(reader.Name, currentNode.Previous.Value.ElementName, StringComparison.CurrentCultureIgnoreCase) == 0)
                    {
                        currentNode = currentNode.Previous ?? currentNode;
                        continue;
                    }
                    if (reader.NodeType == XmlNodeType.Element)
                    {
                        string currentElementName = reader.Name;
                        Console.WriteLine("Considering element: {0}", reader.Name);

                        if (String.Compare(reader.Name, currentNode.Value.ElementName, StringComparison.CurrentCultureIgnoreCase) != 0)
                        {
                            // don't want
                            Console.WriteLine("Skipping");
                            skip = true;
                            continue;
                        }
                        if (!FindAttributes(reader))
                        {
                            // don't want
                            Console.WriteLine("Skipping");
                            skip = true;
                            continue;
                        }

                        // is there more?
                        if (currentNode.Next != null)
                        {
                            currentNode = currentNode.Next;
                            continue;
                        }

                        // we're at the end, this is a match! :D
                        Console.WriteLine("XPath match found!");
                        Output(reader, currentElementName);
                    }
                }
            }

            private bool FindAttributes(XmlReader reader)
            {
                foreach (AttributeOfInterest attributeOfInterest in currentNode.Value.Attributes)
                {
                    if (String.Compare(reader.GetAttribute(attributeOfInterest.Name), attributeOfInterest.Value,
                                       StringComparison.CurrentCultureIgnoreCase) != 0)
                    {
                        return false;
                    }
                }
                return true;
            }

            private static void Output(XmlReader reader, string name)
            {
                while (reader.Read())
                {
                    // break condition
                    if (reader.NodeType == XmlNodeType.EndElement
                        && String.Compare(reader.Name, name, StringComparison.CurrentCultureIgnoreCase) == 0)
                    {
                        return;
                    }

                    if (reader.NodeType == XmlNodeType.Element)
                    {
                        Console.WriteLine("Element {0}", reader.Name);
                        Console.WriteLine("Attributes");
                        for (int i = 0; i < reader.AttributeCount; i++)
                        {
                            reader.MoveToAttribute(i);
                            Console.WriteLine("Attribute: {0} Value: {1}", reader.Name, reader.Value);
                        }
                    }

                    if (reader.NodeType == XmlNodeType.Text)
                    {
                        Console.WriteLine("Element value: {0}", reader.Value);
                    }
                }
            }

            private void Parse(string query)
            {
                IList<string> elements = query.Split('/');
                foreach (string element in elements)
                {
                    ElementOfInterest interestingElement = null;
                    string elementName = element;
                    int attributeQueryStartIndex = element.IndexOf('[');
                    if (attributeQueryStartIndex != -1)
                    {
                        int attributeQueryEndIndex = element.IndexOf(']');
                        if (attributeQueryEndIndex == -1)
                        {
                            throw new ArgumentException(String.Format("Argument: {0} has a [ without a corresponding ]", query));
                        }
                        elementName = elementName.Substring(0, attributeQueryStartIndex);
                        string attributeQuery = element.Substring(attributeQueryStartIndex + 1,
                                    (attributeQueryEndIndex - attributeQueryStartIndex) - 2);
                        string[] keyValPair = attributeQuery.Split('=');
                        if (keyValPair.Length != 2)
                        {
                            throw new ArgumentException(String.Format("Argument: {0} has an attribute query that either has too many or insufficient = marks. We currently only support one", query));
                        }
                        interestingElement = new ElementOfInterest(elementName);
                        interestingElement.Add(new AttributeOfInterest(keyValPair[0].Trim().Replace("'", ""),
                            keyValPair[1].Trim().Replace("'", "")));
                    }
                    else
                    {
                        interestingElement = new ElementOfInterest(elementName);
                    }

                    list.AddLast(interestingElement);
                }
            }

            class ElementOfInterest
            {
                private readonly string elementName;
                private readonly List<AttributeOfInterest> attributes = new List<AttributeOfInterest>();

                public ElementOfInterest(string elementName)
                {
                    this.elementName = elementName;
                }

                public string ElementName
                {
                    get { return elementName; }
                }

                public List<AttributeOfInterest> Attributes
                {
                    get { return attributes; }
                }

                public void Add(AttributeOfInterest attribute)
                {
                    Attributes.Add(attribute);
                }
            }

            class AttributeOfInterest
            {
                private readonly string name;
                private readonly string value;

                public AttributeOfInterest(string name, string value)
                {
                    this.name = name;
                    this.value = value;
                }

                public string Value
                {
                    get { return value; }
                }

                public string Name
                {
                    get { return name; }
                }
            }
        }
    }
}

これは私が使用していたテスト入力です:

<?xml version="1.0" encoding="utf-8" ?>
<ConfigurationRelease>
  <Profiles>
    <Profile Name ="MyProfileName">
      <Screens>
        <Screen Id="MyScreenId">
          <Settings>
            <Setting Name="MySettingName">
              <Paydirt>Good stuff</Paydirt>
            </Setting>
          </Settings>
        </Screen>
      </Screens>
    </Profile>
    <Profile Name ="SomeProfile">
      <Screens>
        <Screen Id="MyScreenId">
          <Settings>
            <Setting Name="Boring">
              <Paydirt>NOES you should not find this!!!</Paydirt>
            </Setting>
          </Settings>
        </Screen>
      </Screens>
    </Profile>
    <Profile Name ="SomeProfile">
      <Screens>
        <Screen Id="Boring">
          <Settings>
            <Setting Name="MySettingName">
              <Paydirt>NOES you should not find this!!!</Paydirt>
            </Setting>
          </Settings>
        </Screen>
      </Screens>
    </Profile>
    <Profile Name ="Boring">
      <Screens>
        <Screen Id="MyScreenId">
          <Settings>
            <Setting Name="MySettingName">
              <Paydirt>NOES you should not find this!!!</Paydirt>
            </Setting>
          </Settings>
        </Screen>
      </Screens>
    </Profile>
  </Profiles>
</ConfigurationRelease>

そして、これは私が得た出力です。

C:\Sandbox\XPathInCE\XPathInCE\bin\Debug>XPathInCE MyXmlFile.xml ConfigurationRe
lease/Profiles/Profile[Name='MyProfileName']/Screens/Screen[Id='MyScreenId']/Set
tings/Setting[Name='MySettingName']
Considering element: ConfigurationRelease
Considering element: Profiles
Considering element: Profile
Considering element: Screens
Considering element: Screen
Considering element: Settings
Considering element: Setting
XPath match found!
Element Paydirt
Attributes
Element value: Good stuff
Considering element: Profile
Skipping
Considering element: Profile
Skipping
Considering element: Profile
Skipping
Press ENTER to exit

デスクトップで実行しましたが、生成したCF 2.00 .exeであったため、CEで正常に動作するはずです。ご覧のとおり、一致しない場合はスキップされるため、ファイル全体がウォークされません。

特にコードをより簡潔にするためのポインタがある場合は、誰からのフィードバックも歓迎します。

于 2009-02-23T22:44:27.083 に答える
2

ファイルをデータセットにロードしてみてください。

DataSet ds = new Dataset();
ds.ReadXml("C:\MyXmlFile.xml")

その後、linq を使用して検索できます。

于 2009-02-17T16:09:12.043 に答える
2

問題が解決したため、これを追加しますが、選択したソリューションはこれまでにリストされたものと一致しません。

当社のテクニカル アーキテクトがこの問題を引き継ぎ、そもそも Xml を実装すべきではないと判断しました。この決定は、一部にはこの問題によるものでしたが、データ転送料金のレベルに関する苦情もありました。

彼の判断は、クエリのサイズと速度に合わせて最適化されたカスタム ファイル形式 (インデックス付き) を実装すべきだったというものです。

したがって、その作業が承認され、適切に仕様が定められるまで、問題は保留されます。

とりあえず終わり。

于 2009-02-25T19:29:22.623 に答える
0

サックスベースのパーサーを実装して、XMLを解析するときに関心のあるブランチのみを取得できるようにすることができます。これは、xml全体をドキュメントとしてロードしないため、最良のアプローチです。

最適には、必要に応じてニーズに合わせてカスタムパーサーを設計し、すべての解析を1回のパスで実行します。たとえば、特定のノードに後で関心がある場合は、後でそれらのノードへの参照を保存して、後で開始できるようにします。再度解析またはトラバースするのではなく。

ここでの欠点は、カスタムプログラミングが少しあることです。

利点は、興味のあるものだけを読み、要件に基づいてxmlドキュメントを処理することです。ドキュメントのパスを完了する前に、結果の処理を開始することもできます。これは、ドキュメントの内容に基づいてワーカースレッドを開始するのに最適です。例:要素のコンテンツ全体を別のXMLドキュメントのルートとして取得し、それを個別にロードすることができます(xpathなどを使用)。内容をバッファにコピーしてから、それをワーカーに渡して処理することができます。

私はずっと前にこれをC用のlibxml2を使用して使用しましたが、C#バインディング(および他の多くの言語)もあります。

于 2009-03-09T07:35:41.550 に答える
0

それをデータセットにロードしてもうまくいきません - それはさらに多くのメモリを消費します。

同様の状況に直面したとき、私は XmlReader を使用し、読み込み時にメモリ内インデックスを作成しました。インデックスを提示し、ユーザーがリンクをクリックするか検索を開始すると、XmlReader を使用して XML ドキュメントを再読み込みし、適切なサブセットを読み込みます。

これは骨の折れるように聞こえますが、ある意味ではそうだと思います。CPUサイクルをメモリと交換します。しかし、それは機能し、アプリは十分に反応します. データサイズはわずか2MBで、それほど大きくありません。しかし、データセットでOOMを取得していました。それから XmlSerializer に行き、それはしばらくの間うまくいきましたが、再び OOM にぶつかりました。そこで、最終的にこのカスタム インデックスに戻りました。

于 2009-03-09T06:43:27.073 に答える