web-dev-qa-db-ja.com

XPathでJavaの名前空間を使用してXMLをクエリする方法は?

XMLがこのように見える(xmlnsなし)場合、/workbook/sheets/sheet[1]のようなXPathで簡単にクエリできます。

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<workbook>
  <sheets>
    <sheet name="Sheet1" sheetId="1" r:id="rId1"/>
  </sheets>
</workbook>

しかし、それがこのように見えるとき、私はできません

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<workbook xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main" xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships">
  <sheets>
    <sheet name="Sheet1" sheetId="1" r:id="rId1"/>
  </sheets>
</workbook>

何か案は?

61
Inez

2番目のXMLファイルの例では、要素は名前空間にバインドされています。 XPathは、デフォルトの「名前空間なし」名前空間にバインドされている要素をアドレス指定しようとしているため、一致しません。

推奨される方法は、名前空間を名前空間プレフィックスで登録することです。これにより、XPathの開発、読み取り、および保守がはるかに簡単になります。

ただし、ネームスペースを登録し、XPathでネームスペースプレフィックスを使用することは必須ではありません。

あなたはcan要素の一般的な一致と、目的のlocal-name()の一致を制限する述語フィルターを使用するXPath式を定式化できます。およびnamespace-uri()。例えば:

_/*[local-name()='workbook'
    and namespace-uri()='http://schemas.openxmlformats.org/spreadsheetml/2006/main']
  /*[local-name()='sheets'
      and namespace-uri()='http://schemas.openxmlformats.org/spreadsheetml/2006/main']
  /*[local-name()='sheet'
      and namespace-uri()='http://schemas.openxmlformats.org/spreadsheetml/2006/main'][1]
_

ご覧のとおり、非常に長くて詳細なXPathステートメントが生成され、読み取り(および保守)が非常に困難です。

また、要素のlocal-name()で一致させ、名前空間を無視することもできます。例えば:

_/*[local-name()='workbook']/*[local-name()='sheets']/*[local-name()='sheet'][1]
_

ただし、間違った要素と一致するリスクがあります。 XMLに、同じlocal-name()を使用する混合語彙(このインスタンスでは問題にならない可能性がある)がある場合、XPath間違った要素で一致し、間違ったコンテンツを選択する可能性があります。

65
Mads Hansen

あなたの問題はデフォルトの名前空間です。 XPathで名前空間を処理する方法については、この記事をご覧ください。 http://www.edankert.com/defaultnamespaces.html

彼らが描く結論の一つは:

そのため、(デフォルトの)名前空間で定義されたXMLコンテンツでXPath式を使用できるようにするには、名前空間プレフィックスマッピングを指定する必要があります

これは、ソースドキュメントを何らかの方法で変更する必要があることを意味するものではないことに注意してください(ただし、必要に応じて名前空間プレフィックスを自由に挿入できます)。奇妙に聞こえますよね? willが行うことは、Javaコードでネームスペースプレフィックスマッピングを作成し、XPath式でこのプレフィックスを使用することです。ここで作成しますspreadsheetからデフォルト名前空間へのマッピング。

XPathFactory factory = XPathFactory.newInstance();
XPath xpath = factory.newXPath();

// there's no default implementation for NamespaceContext...seems kind of silly, no?
xpath.setNamespaceContext(new NamespaceContext() {
    public String getNamespaceURI(String prefix) {
        if (prefix == null) throw new NullPointerException("Null prefix");
        else if ("spreadsheet".equals(prefix)) return "http://schemas.openxmlformats.org/spreadsheetml/2006/main";
        else if ("xml".equals(prefix)) return XMLConstants.XML_NS_URI;
        return XMLConstants.NULL_NS_URI;
    }

    // This method isn't necessary for XPath processing.
    public String getPrefix(String uri) {
        throw new UnsupportedOperationException();
    }

    // This method isn't necessary for XPath processing either.
    public Iterator getPrefixes(String uri) {
        throw new UnsupportedOperationException();
    }
});

// note that all the elements in the expression are prefixed with our namespace mapping!
XPathExpression expr = xpath.compile("/spreadsheet:workbook/spreadsheet:sheets/spreadsheet:sheet[1]");

// assuming you've got your XML document in a variable named doc...
Node result = (Node) expr.evaluate(doc, XPathConstants.NODE);

そして出来上がり...あなたはあなたの要素をresult変数に保存しました。

注意:XMLを標準JAXPクラスを使用してDOMとして解析する場合は、DocumentBuilderFactorysetNamespaceAware(true)を呼び出してください。そうしないと、このコードは機能しません!

57
stevevls

ソースXMLで選択するすべてのネームスペースは、ホスト言語のプレフィックスに関連付ける必要があります。 Java/JAXPでは、javax.xml.namespace.NamespaceContextのインスタンスを使用して、各名前空間プレフィックスのURIを指定することによりこれを行います。残念ながら、SDKで提供されるNamespaceContext実装はありません

幸いなことに、自分で簡単に書くことができます。

import Java.util.HashMap;
import Java.util.Iterator;
import Java.util.Map;
import javax.xml.namespace.NamespaceContext;

public class SimpleNamespaceContext implements NamespaceContext {

    private final Map<String, String> PREF_MAP = new HashMap<String, String>();

    public SimpleNamespaceContext(final Map<String, String> prefMap) {
        PREF_MAP.putAll(prefMap);       
    }

    public String getNamespaceURI(String prefix) {
        return PREF_MAP.get(prefix);
    }

    public String getPrefix(String uri) {
        throw new UnsupportedOperationException();
    }

    public Iterator getPrefixes(String uri) {
        throw new UnsupportedOperationException();
    }

}

次のように使用します。

XPathFactory factory = XPathFactory.newInstance();
XPath xpath = factory.newXPath();
HashMap<String, String> prefMap = new HashMap<String, String>() {{
    put("main", "http://schemas.openxmlformats.org/spreadsheetml/2006/main");
    put("r", "http://schemas.openxmlformats.org/officeDocument/2006/relationships");
}};
SimpleNamespaceContext namespaces = new SimpleNamespaceContext(prefMap);
xpath.setNamespaceContext(namespaces);
XPathExpression expr = xpath
        .compile("/main:workbook/main:sheets/main:sheet[1]");
Object result = expr.evaluate(doc, XPathConstants.NODESET);

最初のネームスペースはソースドキュメントでプレフィックスを指定していません(つまり、 デフォルトネームスペースとにかくプレフィックスに関連付ける必要がありますであることに注意してください。次に、式は、次のように、選択したプレフィックスを使用してその名前空間のノードを参照する必要があります。

/main:workbook/main:sheets/main:sheet[1]

各ネームスペースに関連付けるプレフィックス名は任意です。ソースXMLに表示されるものと一致する必要はありません。このマッピングは、式の特定のプレフィックス名がソースドキュメントの特定のネームスペースと相関することをXPathエンジンに伝えるための単なる方法です。

37
Wayne Burkett

Springを使用している場合は、すでにorg.springframework.util.xml.SimpleNamespaceContextが含まれています。

        import org.springframework.util.xml.SimpleNamespaceContext;
        ...

        XPathFactory xPathfactory = XPathFactory.newInstance();
        XPath xpath = xPathfactory.newXPath();
        SimpleNamespaceContext nsc = new SimpleNamespaceContext();

        nsc.bindNamespaceUri("a", "http://some.namespace.com/nsContext");
        xpath.setNamespaceContext(nsc);

        XPathExpression xpathExpr = xpath.compile("//a:first/a:second");

        String result = (String) xpathExpr.evaluate(object, XPathConstants.STRING);
4
kasi

単純なNamespaceContext実装( here )を作成しました。これはMap<String, String>入力として。ここで、keyはプレフィックスで、valueは名前空間です。

NamespaceContext 仕様に続き、 ユニットテスト でどのように機能するかを確認できます。

Map<String, String> mappings = new HashMap<>();
mappings.put("foo", "http://foo");
mappings.put("foo2", "http://foo");
mappings.put("bar", "http://bar");

context = new SimpleNamespaceContext(mappings);

context.getNamespaceURI("foo");    // "http://foo"
context.getPrefix("http://foo");   // "foo" or "foo2"
context.getPrefixes("http://foo"); // ["foo", "foo2"]

Google Guavaに依存していることに注意してください

1
tomaj

XSLTで名前空間を参照していることを確認してください

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
             xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main"
             xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships"       >
1
cordsen

驚くべきことに、factory.setNamespaceAware(true);を設定しない場合、あなたが言及したxpathは、名前空間の有無に関わらず動作します。 「名前空間が指定された状態で」一般的なxpathのみを選択することはできません。図を移動します。したがって、これはオプションかもしれません:

 DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
 factory.setNamespaceAware(false);
0
rogerdpack

既存の回答に追加する2つのこと:

  • 質問をしたときにこれが当てはまるかどうかはわかりません:Java 10の場合、setNamespaceAware(true)を使用しない場合、XPathは実際に2番目のドキュメントに対して機能しますドキュメントビルダーファクトリー(falseがデフォルトです)。

  • setNamespaceAware(true)を使用したい場合、他の回答は名前空間コンテキストを使用してこれを行う方法をすでに示しています。ただし、これらの回答が行うように、自分で名前空間へのプレフィックスのマッピングを提供する必要はありません。それは既にドキュメント要素にあり、それを名前空間コンテキストに使用できます。

_import Java.util.Iterator;

import javax.xml.namespace.NamespaceContext;

import org.w3c.dom.Document;
import org.w3c.dom.Element;

public class DocumentNamespaceContext implements NamespaceContext {
    Element documentElement;

    public DocumentNamespaceContext (Document document) {
        documentElement = document.getDocumentElement();
    }

    public String getNamespaceURI(String prefix) {
        return documentElement.getAttribute(prefix.isEmpty() ? "xmlns" : "xmlns:" + prefix);
    }

    public String getPrefix(String namespaceURI) {
        throw new UnsupportedOperationException();
    }

    public Iterator<String> getPrefixes(String namespaceURI) {
        throw new UnsupportedOperationException();
    }
}
_

残りのコードは他の回答と同じです。次に、XPath _/:workbook/:sheets/:sheet[1]_はシート要素を生成します。 (prefix.isEmpty()prefix.equals("spreadsheet")に置き換え、XPath _/spreadsheet:workbook/spreadsheet:sheets/spreadsheet:sheet[1]_を使用することにより、他の回答と同様に、デフォルトの名前空間に空でないプレフィックスを使用することもできます。)

PShere メソッドNode.lookupNamespaceURI(String prefix)が実際にあるので、代わりにそれを使用できます属性ルックアップの:

_    public String getNamespaceURI(String prefix) {
        return documentElement.lookupNamespaceURI(prefix.isEmpty() ? null : prefix);
    }
_

また、ドキュメント要素以外の要素で名前空間を宣言でき、それらは(どちらのバージョンでも)認識されないことに注意してください。

0
joriki