当我的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>
有什么想法吗?
答案 0 :(得分:65)
在第二个示例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]
正如您所看到的,它产生了一个非常冗长且冗长的XPath语句,很难阅读(和维护)。
您也可以匹配元素的local-name()
并忽略命名空间。例如:
/*[local-name()='workbook']/*[local-name()='sheets']/*[local-name()='sheet'][1]
但是,您存在匹配错误元素的风险。如果您的XML具有使用相同local-name()
的混合词汇表(对于此实例可能不是问题),则XPath可以匹配错误的元素并选择错误的内容:
答案 1 :(得分:57)
您的问题是默认命名空间。查看本文,了解如何处理XPath中的命名空间:http://www.edankert.com/defaultnamespaces.html
他们得出的结论之一是:
所以,能够使用XPath 关于XML定义的XML内容的表达式 一个(默认)命名空间,我们需要 指定名称空间前缀映射
请注意,这并不意味着您必须以任何方式更改源文档(尽管您可以根据需要随意添加名称空间前缀)。听起来很奇怪吧? 将做的是在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
变量中了。
警告:如果您使用标准JAXP类将XML解析为DOM,请务必在setNamespaceAware(true)
上调用DocumentBuilderFactory
。否则,此代码将无效!
答案 2 :(得分:35)
您要在源XML中选择的所有命名空间必须与宿主语言中的前缀相关联。在Java / JAXP中,这是通过使用javax.xml.namespace.NamespaceContext
的实例为每个名称空间前缀指定URI来完成的。不幸的是,SDK中提供了{strong>没有实现的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);
请注意,即使第一个命名空间未在源文档中指定前缀(即它是default namespace),您仍必须将其与前缀相关联。然后,您的表达式应使用您选择的前缀引用该命名空间中的节点,如下所示:
/main:workbook/main:sheets/main:sheet[1]
您选择与每个命名空间关联的前缀名称是任意的;它们不需要匹配源XML中显示的内容。这种映射只是告诉XPath引擎表达式中给定前缀名与源文档中特定名称空间相关的一种方式。
答案 3 :(得分:3)
如果您使用的是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 :(得分:1)
确保您在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" >
答案 5 :(得分:1)
我编写了一个简单的NamespaceContext
实现(here),它以Map<String, String>
作为输入,其中key
是前缀,{ {1}}是名称空间。
遵循NamespaceContext规定,您可以在unit tests中看到它是如何运作的。
value
请注意,它依赖于Google Guava
答案 6 :(得分:0)
要添加到现有答案中的两件事:
当您问以下问题时,我不知道是否是这种情况:对于Java 10,如果您未在文档构建器工厂上使用setNamespaceAware(true)
,则XPath实际上可用于第二个文档(默认为false
。
如果您确实想使用setNamespaceAware(true)
,其他答案已经显示了如何使用名称空间上下文来执行此操作。但是,您不需要自己提供前缀到名称空间的映射,如以下答案所示:document元素中已经存在前缀,并且可以将其用于名称空间上下文:
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]
。)
P.S。:我刚刚发现here实际上有一个方法Node.lookupNamespaceURI(String prefix)
,因此您可以使用它代替属性查找:
public String getNamespaceURI(String prefix) {
return documentElement.lookupNamespaceURI(prefix.isEmpty() ? null : prefix);
}
另外,请注意,可以在document元素以外的元素上声明名称空间,而这些名称将无法被识别(任一版本)。
答案 7 :(得分:-1)
令人惊讶的是,如果我没有设置Future<void> checkIfUserHasSubscription() async {
await Future < dynamic > remainingS = CloudFunctions.instance
.call(functionName: 'isValid');
if (remaining == "true")
hasSubscription = true;
else {
hasSubscription = false;
remaining = int.parse(remaining);
}
}
,那么您提到的xpath可以在使用和不使用名称空间的情况下使用。您只是不能选择“指定了名称空间”的东西,而只能选择通用xpath。去搞清楚。因此,这可能是一个选择:
factory.setNamespaceAware(true);