Xpath を使用して XML ファイル内の名前空間を取得する方法
-
02-07-2019 - |
質問
次のような XML ファイルがあります。
<Elements name="Entities" xmlns="XS-GenerationToolElements">
これらのファイルをたくさん開かなければなりません。これらはそれぞれ異なる名前空間を持っていますが、一度に持つ名前空間は 1 つだけです (1 つの XML ファイルに 2 つの名前空間が定義されているのは見つかりません)。
XPath を使用して、指定された名前空間を名前空間マネージャーに自動的に追加する方法が必要です。これまでのところ、xml ファイルを解析することによってのみ名前空間を取得できましたが、XPathNavigator インスタンスがあるので、名前空間を取得するための優れたクリーンな方法があるはずです。
- または -
名前空間が 1 つしかないことを考えると、XPath が XML に存在する唯一の名前空間を使用するようにして、常に名前空間を追加することでコードが乱雑になるのを避けます。
解決
試してみるとよいテクニックがいくつかあります。どちらを使用するかは、ドキュメントからどのような情報を取得する必要があるか、どの程度厳密にする必要があるか、および使用している XPath 実装がどの程度準拠しているかによって決まります。
特定のプレフィックスに関連付けられた名前空間 URI を取得する 1 つの方法は、 namespace::
軸。これにより、名前がプレフィックス、値が名前空間 URI である名前空間ノードが得られます。たとえば、次のパスを使用して、ドキュメント要素のデフォルトの名前空間 URI を取得できます。
/*/namespace::*[name()='']
これを使用して、XPathNavigator の名前空間の関連付けを設定できる場合があります。ただし、次のことに注意してください。 namespace::
axis は、常に実装されるわけではない XPath 1.0 のコーナーの 1 つです。
名前空間 URI を取得する 2 番目の方法は、 namespace-uri()
ドキュメント要素の関数(常にその名前空間にあると述べました)。表現:
namespace-uri(/*)
その名前空間を提供します。
別の方法としては、プレフィックスをその名前空間に関連付けるのを忘れて、パスを名前空間フリーにすることもできます。これを行うには、 local-name()
名前空間が不明な要素を参照する必要がある場合は常にこの関数を使用します。例えば:
//*[local-name() = 'Element']
本当に必要な場合は、さらに一歩進んで、要素の名前空間 URI をドキュメント要素の名前空間 URI と比較してテストすることもできます。
//*[local-name() = 'Element' and namespace-uri() = namespace-uri(/*)]
名前空間があなたにとって何の意味もないと思われる場合の最後のオプションは、名前空間を取り除くフィルターを通して XML を実行することです。そうすれば、XPath でそれらについてまったく心配する必要がなくなります。これを行う最も簡単な方法は、単純に削除することです。 xmlns
正規表現を使用して属性を設定できますが、他の整理を同時に行う必要がある場合は、より複雑なことを行うこともできます。
他のヒント
この 40 行の xslt 変換は、特定の XML ドキュメント内の名前空間に関するすべての有用な情報を提供します。:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:ext="http://exslt.org/common"
exclude-result-prefixes="ext"
>
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:key name="kNsByNsUri" match="ns" use="@uri"/>
<xsl:variable name="vXmlNS"
select="'http://www.w3.org/XML/1998/namespace'"/>
<xsl:template match="/">
<xsl:variable name="vrtfNamespaces">
<xsl:for-each select=
"//namespace::*
[not(. = $vXmlNS)
and
. = namespace-uri(..)
]">
<ns element="{name(..)}"
prefix="{name()}" uri="{.}"/>
</xsl:for-each>
</xsl:variable>
<xsl:variable name="vNamespaces"
select="ext:node-set($vrtfNamespaces)/*"/>
<namespaces>
<xsl:for-each select=
"$vNamespaces[generate-id()
=
generate-id(key('kNsByNsUri',@uri)[1])
]">
<namespace uri="{@uri}">
<xsl:for-each select="key('kNsByNsUri',@uri)/@element">
<element name="{.}" prefix="{../@prefix}"/>
</xsl:for-each>
</namespace>
</xsl:for-each>
</namespaces>
</xsl:template>
次の XML ドキュメントに適用すると、次のようになります。
<a xmlns="my:def1" xmlns:n1="my:n1"
xmlns:n2="my:n2" xmlns:n3="my:n3">
<b>
<n1:d/>
</b>
<n1:c>
<n2:e>
<f/>
</n2:e>
</n1:c>
<n2:g/>
</a>
必要な結果が生成されます。
<namespaces>
<namespace uri="my:def1">
<element name="a" prefix=""/>
<element name="b" prefix=""/>
<element name="f" prefix=""/>
</namespace>
<namespace uri="my:n1">
<element name="n1:d" prefix="n1"/>
<element name="n1:c" prefix="n1"/>
</namespace>
<namespace uri="my:n2">
<element name="n2:e" prefix="n2"/>
<element name="n2:g" prefix="n2"/>
</namespace>
</namespaces>
残念ながら、XPath には「デフォルトの名前空間」という概念がありません。XPath コンテキストにプレフィックスを付けて名前空間を登録し、それらのプレフィックスを XPath 式で使用する必要があります。これは非常に冗長な xpath を意味しますが、これは XPath 1 の基本的な欠点です。XPath 2 はこれに対処するようですが、現時点では役に立ちません。
プログラムで XML ドキュメントの名前空間を調べ、その名前空間を XPath コンテキストの接頭辞に関連付けてから、その接頭辞を xpath 式で使用することをお勧めします。