0

次の XML インスタンスがあるとします。

<entities>
    <person><name>Jack</name></person>
    <person><name></name></person>
    <person></person>
</entities>

次のコードを使用して、(a) 人物を反復処理し、(b) 各人物の名前を取得します。

XPathExpression expr = xpath.compile("/entities/person");
NodeList nodes = (NodeList) expr.evaluate(doc, XPathConstants.NODESET);
for (int i = 0 ; i < nodes.getLength() ; i++) {
    Node node = nodes.item(i);
    String innerXPath = "name/text()";
    String name  = xpath.compile(innerXPath).evaluate(node);
    System.out.printf("%2d -> name is %s.\n", i, name);
}

上記のコードは、2 人称の場合 (名前の空の文字列) と 3 人称の場合 (name 要素がまったくない) を区別できず、単純に次のように出力します。

0 -> name is Jack.
1 -> name is .
2 -> name is .

innerXPath別の表現を使用してこれら 2 つのケースを区別する方法はありますか? このSOの質問では、XPathの方法は空のリストを返すようですが、私もそれを試しました:

String innerXPath = "if (name) then name/text() else ()";

...そして、出力は同じです。

innerXPathでは、これら 2 つのケースを異なる表現で区別する方法はありますか? クラスパスに Saxon HE があるので、XPath 2.0 機能も使用できます。

アップデート

したがって、受け入れられた回答に基づいて私ができる最善のことは次のとおりです。

XPathExpression expr = xpath.compile("/entities/person");                                                                                                                                                                                 
NodeList nodes = (NodeList) expr.evaluate(doc, XPathConstants.NODESET);                                                                                                                                                                   
for (int i = 0 ; i < nodes.getLength() ; i++) {                                                                                                                                                                                           
    Node node = nodes.item(i);                                                                                                                                                                                                            
    String innerXPath = "name";                                                                                                                                                                                                           
    NodeList names = (NodeList) xpath.compile(innerXPath).evaluate(node, XPathConstants.NODESET);                                                                                                                                         
    String nameValue = null;                                                                                                                                                                                                              
    if (names.getLength()>1) throw new RuntimeException("impossible");                                                                                                                                                                    
    if (names.getLength()==1)                                                                                                                                                                                                             
        nameValue = names.item(0).getFirstChild()==null?"":names.item(0).getFirstChild().getNodeValue();                                                                                                                                  
    System.out.printf("%2d -> name is [%s]\n", i, nameValue);                                                                                                                                                                             
} 

上記のコードは次を出力します。

0 -> name is [Jack]
1 -> name is []
2 -> name is [null]

私の見解では、ロジックはXPathJavaコードの両方に広がっており、ホスト言語および API に依存しない表記法としてのXPathの有用性を制限しているため、これはあまり満足のいくものではありません。私の特定の使用例は、プロパティ ファイルに XPath のコレクションを保持し、実行時にそれらを評価して、その場しのぎの追加処理なしで必要な情報を取得することでした。どうやらそれは不可能です。

4

2 に答える 2

0

Saxon XSLT の長年のユーザーとして、Michael Kay の推奨事項がここで気に入っていることを再確認できて嬉しく思います。一般的に、私はクエリのコレクションを返すパターンが好きです。多くても 1 つのインスタンスしか返さないことが予想されるクエリの場合でもそうです。

私がやりたくないのは、バンドルされたインターフェイスを開いて特定のニーズを解決しようとした後、元のインターフェイスで処理されていたものの多くを再実装する必要があることに気付くことです。

したがって、このスレッドの他のコメントで推奨されている Node-to-String 変換を再実装するコストを回避しながら、Michael の推奨事項を使用する方法を次に示します。

@Nonnull
public Optional<String> findString( @Nonnull final String expression )
{
    try
    {
        // for XpathConstants.STRING XPath returns an empty string for both values of no length
        // and for elements that are not present.

        // therefore, ask for a NODESET and then retrieve the first Node if any

        final FluentIterable<Node> matches = 
                IterableNodeList.from( (NodeList) xpath.evaluate( expression, node, XPathConstants.NODESET ) );

        if ( matches.isEmpty() )
        {
            return Optional.absent();
        }

        final Node firstNode = matches.first().get();

        // now let XPath process a known-to-exist Node to retrieve its String value         
        return Optional.fromNullable( (String) xpath.evaluate( ".", firstNode, XPathConstants.STRING ) );
    }
    catch ( XPathExpressionException xee )
    {
        return Optional.absent();
    }
}

ここで、XPath.evaluate が 2 度目に呼び出され、最初に見つかった Node を要求された String 値に変換するために通常行うことは何でも行います。これがないと、再実装によって、同じソース ノードおよび同じ式に対する XPathConstant.STRING の直接呼び出しとは異なる結果が生じるリスクがあります。

もちろん、このコードは Guava Optional と FluentIterable を使用して意図をより明確にしています。Guava が必要ない場合は、Java 8 を使用するか、null と NodeList 独自のコレクション メソッドを使用して実装をリファクタリングしてください。

于 2016-10-14T19:10:02.070 に答える