1

を使用してc#.NETで有効なXMLドキュメントを解析しようとしていますxPathNavigator。XMLドキュメントの開始は次のようになります。

<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
    <soapenv:Body>
        <ns1:myResponse xmlns:ns1="ExampleNS" soapenv:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">
            <myReturn href="#2" />

ご覧のとおり、ルート要素にはいくつかの名前空間が定義されていますが、ns1後で定義されます。xPathクエリを評価しようとすると、次/soapenv:Envelope/soapenv:Body/ns1:myResponse/myReturnのようになりますXPathException

Namespace prefix 'ns1' is not defined.

私がやっていることは、XMLドキュメントを文字列として取得し、それをXmlDocumentオブジェクトにロードすることです。次に、ナビゲーターを作成し、ルート要素に移動してを呼び出しGetNamespacesInScope(XmlNamespaceScope.All)ます。、、の名前空間を含むこのコレクションをループしxml、ルートsoapenvで定義されているように、それらを名前空間マネージャーに追加しますxsdxsi次に、を作成しxPathNavigatorて呼び出しEvaluate、例外を取得します。

コードはこれです:

string response = GetXMLString();
var xmlDocument = new XmlDocument();
xmlDocument.LoadXml(response);

var xmlDocumentNavigator = xmlDocument.CreateNavigator();
xmlDocumentNavigator.MoveToFollowing(XPathNodeType.Element);
var xnm = new XmlNamespaceManager(xmlDocument.NameTable);
var namespacesInScope = xmlDocumentNavigator.GetNamespacesInScope(XmlNamespaceScope.All);
if (namespacesInScope != null)
{
    foreach (var prefix in namespacesInScope.Keys)
    {
        xnm.AddNamespace(prefix, namespacesInScope[prefix]);
    }
}

var xPathDocument = new XPathDocument(new StringReader(response));
var xPathNavigator = xPathDocument.CreateNavigator();

xPathNavigator.Evaluate("/soapenv:Envelope/soapenv:Body/ns1:myResponse/myReturn", xnm);

GetNamespacesInScopeメソッドがうまくいかないのはなぜns1ですか?

4

2 に答える 2

3

問題はGetNamespacesInScope、ナビゲーターの現在のノードのスコープ内の名前空間のみを返すことです(メソッドが示唆していると思います:)。

すべての名前空間を取得する方法の 1 つは、すべての要素を調べて名前空間を取得し、一意のものだけを再構築することです。

JonSkeet 提供の辞書マージ コード

 XPathDocument xpd = new XPathDocument(new StringReader(s));
 XPathNavigator xpn = xpd.CreateNavigator();
 List<IDictionary<string, string>> xmlnsList = new List<IDictionary<string,string>>();
 while (xpn.MoveToFollowing(XPathNodeType.Element))
 {
     xmlnsList.Add(xpn.GetNamespacesInScope(XmlNamespaceScope.All));
 }
 var result = xmlnsList.SelectMany(dict => dict)
              .ToLookup(pair => pair.Key, pair => pair.Value)
              .ToDictionary(group => group.Key, group => group.First());
 if (result != null)
 {
     foreach (var prefix in result.Keys)
     {
         xnm.AddNamespace(prefix, result[prefix]);
     }
 }

編集

Paciv のコメントによると、事前に xml ドキュメント内の名前空間が本当にわからない場合は、名前空間に依存しない Xpath を使用し、名前空間のスニッフィングに関する問題を回避する (そして名前空間マネージャーの必要性を完全に排除する) こともできます。 .

あなたの例では、これは次のようになります。

xPathNavigator.Evaluate("/*[local-name()='Envelope']/*[local-name()='Body']/*[local-name()='myResponse']/*[local-name()='myReturn']");
于 2012-09-12T15:13:50.243 に答える
2

XPath クエリを実行する必要があることを事前に知っている名前空間を追加するだけではどうですか?

あなたはただ書くことができます

xnm.AddNamespace("MyNS", "ExampleNS");

その後

xPathNavigator
    .Evaluate("/soapenv:Envelope/soapenv:Body/MyNS:myResponse/myReturn", xnm);

プレフィックスによって設計された名前空間が同じである限り、プレフィックスが何であるかは問題ではありません。

ところで、SOAP 名前空間についても同じことを行う必要があります。これは、XPath クエリをプレフィックスに基づいて作成しているため、同じ名前空間を対象とするさまざまなプレフィックスを使用してまったく同じ XML を生成する可能性があるためです。

次のようなさまざまな SOAP ジェネレーターに適用できる、より堅牢なものを作成します。

string response = GetXMLString();
XmlDocument xmlDocument = new XmlDocument();
xmlDocument.LoadXml(response);
XmlNamespaceManager xnm = new XmlNamespaceManager(xmlDocument.NameTable);
xnm.AddNamespace("soapenv", "http://schemas.xmlsoap.org/soap/envelope/");
xnm.AddNamespace("ns1", "ExampleNS");

xmlDocument.CreateNavigator()
    .Evaluate("/soapenv:Envelope/soapenv:Body/ns1:myResponse/myReturn", xnm);
于 2012-09-12T16:10:59.657 に答える