Ine*_*nez 61 java xml xpath xml-namespaces
当我的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>
Run Code Online (Sandbox Code Playgroud)
但是当它看起来像这样我就不能
<?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>
Run Code Online (Sandbox Code Playgroud)
有任何想法吗?
Mad*_*sen 66
在第二个示例XML文件中,元素绑定到命名空间.您的XPath正在尝试处理绑定到默认"无命名空间"命名空间的元素,因此它们不匹配.
首选方法是使用namespace-prefix注册命名空间.它使您的XPath更易于开发,阅读和维护.
但是,您必须注册命名空间并在XPath中使用namespace-prefix.
您可以制定一个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]
Run Code Online (Sandbox Code Playgroud)
正如您所看到的,它会产生一个非常冗长且冗长的XPath语句,该语句非常难以阅读(和维护).
您也可以只匹配local-name()元素并忽略命名空间.例如:
/*[local-name()='workbook']/*[local-name()='sheets']/*[local-name()='sheet'][1]
Run Code Online (Sandbox Code Playgroud)
但是,您冒着匹配错误元素的风险.如果您的XML具有使用相同的混合词汇表(对于此实例可能不是问题)local-name(),则您的XPath可能匹配错误的元素并选择错误的内容:
ste*_*vls 57
您的问题是默认命名空间.查看本文,了解如何处理XPath中的命名空间:http://www.edankert.com/defaultnamespaces.html
他们得出的结论之一是:
因此,为了能够在(默认)命名空间中定义的XML内容上使用XPath表达式,我们需要指定命名空间前缀映射
请注意,这并不意味着您必须以任何方式更改源文档(尽管您可以根据需要随意添加名称空间前缀).听起来很奇怪吧?什么,你会做的是在你的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);
Run Code Online (Sandbox Code Playgroud)
瞧...现在你已经将你的元素保存在result变量中了.
警告:如果您使用标准JAXP类将XML解析为DOM,请务必调用setNamespaceAware(true)您的DocumentBuilderFactory.否则,此代码将无法正常工作!
Way*_*ett 37
您打算在源XML中选择的所有命名空间必须与宿主语言中的前缀相关联.在Java/JAXP中,这是通过使用实例指定每个名称空间前缀的URI来完成的javax.xml.namespace.NamespaceContext.不幸的是,没有实现的NamespaceContextSDK中提供.
幸运的是,编写自己的代码非常简单:
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();
}
}
Run Code Online (Sandbox Code Playgroud)
像这样使用它:
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);
Run Code Online (Sandbox Code Playgroud)
请注意,即使第一个命名空间未在源文档中指定前缀(即它是默认命名空间),您仍必须将其与前缀相关联.然后,您的表达式应使用您选择的前缀引用该命名空间中的节点,如下所示:
/main:workbook/main:sheets/main:sheet[1]
Run Code Online (Sandbox Code Playgroud)
您选择与每个名称空间关联的前缀名称是任意的; 它们不需要匹配源XML中出现的内容.此映射只是告诉XPath引擎表达式中给定前缀名与源文档中的特定名称空间相关的一种方法.
如果您使用的是 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);
Run Code Online (Sandbox Code Playgroud)