XPath로 Java의 네임 스페이스를 사용하여 XML을 쿼리하는 방법은 무엇입니까?
내 XML이 다음과 같이 보일 때 (아니오 xmlns
) 다음과 같이 XPath로 쉽게 쿼리 할 수 있습니다./workbook/sheets/sheet[1]
<?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>
어떤 아이디어?
두 번째 예제 XML 파일에서 요소는 네임 스페이스에 바인딩됩니다. XPath가 기본 "no namespace"네임 스페이스에 바인딩 된 요소를 주소 지정하려고하므로 일치하지 않습니다.
선호되는 방법은 namespace-prefix를 사용하여 네임 스페이스를 등록하는 것입니다. XPath를 훨씬 쉽게 개발, 읽기 및 유지 관리 할 수 있습니다.
그러나 네임 스페이스를 등록하고 XPath에서 네임 스페이스 접두사를 사용해야하는 것은 아닙니다.
요소에 대한 일반 일치를 사용하는 XPath 표현식과 원하는 및에 대한 일치를 제한하는 조건 자 필터를 공식화 할 수 있습니다 . 예를 들면 :local-name()
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가 잘못된 요소와 일치하고 잘못된 콘텐츠를 선택할 수 있습니다.
문제는 기본 네임 스페이스입니다. XPath에서 네임 스페이스를 처리하는 방법은이 문서를 확인하십시오. http://www.edankert.com/defaultnamespaces.html
그들이 도출 한 결론 중 하나는 다음과 같습니다.
따라서 (기본) 네임 스페이스에 정의 된 XML 콘텐츠에 XPath 식을 사용할 수 있으려면 네임 스페이스 접두사 매핑을 지정해야합니다.
이것이 소스 문서를 어떤 식 으로든 변경해야한다는 의미는 아닙니다 (원하는 경우 네임 스페이스 접두사를 자유롭게 넣을 수 있음). 이상하게 들리 죠? 당신이 할 일은 자바 코드에서 네임 스페이스 접두사 매핑을 생성하고 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
변수에 요소가 저장되었습니다 .
경고 : 당신이 표준 JAXP 클래스와 DOM 같은 XML을 구문 분석하는 경우, 반드시 전화 setNamespaceAware(true)
당신에 DocumentBuilderFactory
. 그렇지 않으면이 코드가 작동하지 않습니다!
소스 XML에서 선택하려는 모든 네임 스페이스는 호스트 언어의 접두사와 연결되어야합니다. Java / JAXP에서이 작업은의 인스턴스를 사용하여 각 네임 스페이스 접두사에 대한 URI를 지정하여 수행됩니다 javax.xml.namespace.NamespaceContext
. 불행하게도, 존재하지 아니 구현 의 NamespaceContext
SDK에 제공되는.
다행히도 직접 작성하는 것은 매우 쉽습니다.
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);
Note that even though the first namespace does not specify a prefix in the source document (i.e. it is the default namespace) you must associate it with a prefix anyway. Your expression should then reference nodes in that namespace using the prefix you've chosen, like this:
/main:workbook/main:sheets/main:sheet[1]
The prefix names you choose to associate with each namespace are arbitrary; they do not need to match what appears in the source XML. This mapping is just a way to tell the XPath engine that a given prefix name in an expression correlates with a specific namespace in the source document.
If you are using Spring, it already contains 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);
Make sure that you are referencing the namespace in your 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" >
I've written a simple NamespaceContext
implementation (here), that takes a Map<String, String>
as input, where the key
is a prefix, and the value
is a namespace.
It follows the NamespaceContext spesification, and you can see how it works in the unit tests.
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"]
Note that it has a dependency on Google Guava
Startlingly, if I don't set factory.setNamespaceAware(true);
then the xpath you mentioned does work with and without namespaces at play. You just aren't able to select things "with namespace specified" only generic xpaths. Go figure. So this may be an option:
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
factory.setNamespaceAware(false);
Two things to add to the existing answers:
I don't know whether this was the case when you asked the question: With Java 10, your XPath actually works for the second document if you don't use
setNamespaceAware(true)
on the document builder factory (false
is the default).If you do want to use
setNamespaceAware(true)
, other answers have already shown how to do this using a namespace context. However, you don't need to provide the mapping of prefixes to namespaces yourself, as these answers do: It's already there in the document element, and you can use that for your namespace context:
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();
}
}
The rest of the code is as in the other answers. Then the XPath /:workbook/:sheets/:sheet[1]
yields the sheet element. (You could also use a non-empty prefix for the default namespace, as the other answers do, by replacing prefix.isEmpty()
by e.g. prefix.equals("spreadsheet")
and using the XPath /spreadsheet:workbook/spreadsheet:sheets/spreadsheet:sheet[1]
.)
P.S.: I just found here that there's actually a method Node.lookupNamespaceURI(String prefix)
, so you could use that instead of the attribute lookup:
public String getNamespaceURI(String prefix) {
return documentElement.lookupNamespaceURI(prefix.isEmpty() ? null : prefix);
}
Also, note that namespaces can be declared on elements other than the document element, and those wouldn't be recognized (by either version).
참고URL : https://stackoverflow.com/questions/6390339/how-to-query-xml-using-namespaces-in-java-with-xpath
'Nice programing' 카테고리의 다른 글
스파크 실행기 번호, 코어 및 실행기 메모리를 조정하는 방법은 무엇입니까? (0) | 2020.11.29 |
---|---|
Git에서 모든 파일을 수동으로 병합하는 방법은 무엇입니까? (0) | 2020.11.29 |
디버깅하는 동안 iOS 애플리케이션 샌드 박스에서 파일의 내용을 볼 수 있습니까? (0) | 2020.11.29 |
주어진 달의 첫날과 마지막 날을 얻는 방법 (0) | 2020.11.29 |
Bash에서 작은 따옴표 문자열에서 작은 따옴표를 이스케이프하는 방법은 무엇입니까? (0) | 2020.11.29 |