如何在带有XPath的Java中使用名称空间查询XML?

如何在带有XPath的Java中使用名称空间查询XML?,java,xml,xpath,xml-namespaces,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> </workbo

当我的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>


有什么想法吗?

您的问题是默认名称空间。查看本文,了解如何在XPath中处理名称空间:

他们得出的结论之一是:

因此,为了能够使用XPath 中定义的XML内容的表达式 (默认)名称空间,我们需要 指定命名空间前缀映射

请注意,这并不意味着您必须以任何方式更改源文档(尽管如果您愿意,您可以将名称空间前缀放在其中)。听起来很奇怪,对吧?您将要做的是在java代码中创建一个名称空间前缀映射,并在XPath表达式中使用该前缀。在这里,我们将创建一个从
电子表格
到默认名称空间的映射

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,请确保在
DocumentBuilderFactory
上调用
setNamespaceAware(true)
。否则,此代码将无法工作

确保在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"       >

要从源XML中选择的所有名称空间必须与宿主语言中的前缀相关联。在Java/JAXP中,这是通过使用
javax.xml.namespace.NamespaceContext
的实例为每个名称空间前缀指定URI来实现的。不幸的是,SDK中没有提供
namespacecoxt
实现

幸运的是,编写自己的代码非常容易:

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();
    }

}

您选择与每个命名空间关联的前缀名称是任意的;它们不需要匹配源XML中出现的内容。此映射只是告诉XPath引擎表达式中的给定前缀名称与源文档中的特定命名空间相关的一种方式。

在第二个示例XML文件中,元素绑定到命名空间。您的XPath试图寻址绑定到默认“无命名空间”命名空间的元素,因此它们不匹配

首选方法是使用名称空间前缀注册名称空间。它使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]
/*[local-name()='workbook']/*[local-name()='sheets']/*[local-name()='sheet'][1]
正如您所看到的,它生成了一个非常长且冗长的XPath语句,很难读取(和维护)

您也可以只匹配元素的
local-name()
,而忽略名称空间。例如:

/*[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]
/*[local-name()='workbook']/*[local-name()='sheets']/*[local-name()='sheet'][1]

但是,您可能会遇到匹配错误元素的风险。如果您的XML包含使用相同
local-name()
的混合词汇表(这对于本例可能不是问题),则,您的XPath可能匹配错误的元素并选择错误的内容:

我编写了一个简单的
NamespaceContext
实现(),它将
映射作为输入,其中
键是前缀,
值是名称空间

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);
它遵循规范化,您可以看到它在中是如何工作的

Map mappings=newhashmap();
mappings.put(“foo”http://foo");
mappings.put(“foo2”http://foo");
mappings.put(“bar”http://bar");
上下文=新的SimpleNamespaceContext(映射);
context.getNamespaceURI(“foo”);//"http://foo"
context.getPrefix(“http://foo");   // “foo”或“foo2”
context.getPrefixes(“http://foo"); // [“foo”,“foo2”]

注意,它依赖于

如果您使用的是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);

令人吃惊的是,如果我不设置
factory.setNamespaceAware(true)
那么您提到的xpath在使用和不使用名称空间的情况下都可以工作。您只是无法选择“指定了名称空间”的东西,而只能选择通用XPath。想想看。所以这可能是一个选项:

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

要在现有答案中添加两个内容:

  • 我不知道当你问这个问题时是否是这样:对于Java 10,如果你没有在document builder工厂中使用
    setNamespaceAware(true)
    (默认值为
    false
    ),那么XPath实际上可以用于第二个文档

  • 如果您确实想使用
    setNamespaceAware(true)
    ,其他答案已经说明了如何使用命名空间上下文来实现这一点。但是,您不需要自己提供前缀到名称空间的映射,正如这些答案所做的那样:它已经存在于document元素中,您可以将其用于您的名称空间上下文:

import java.util.Iterator;
导入javax.xml.namespace.NamespaceContext;
导入org.w3c.dom.Document;
导入org.w3c.dom.Element;
公共类DocumentNamespaceContext实现NamespaceContext{
元素文档元素;
公共文档名称空间上下文(文档){
documentElement=document.getDocumentElement();
}
公共字符串getNamespaceURI(字符串前缀){
返回documentElement.getAttribute(前缀.isEmpty()?“xmlns”:“xmlns:”+前缀);
}
公共字符串getPrefix(字符串名称空间URI){
抛出新的UnsupportedOperationException();
}
公共迭代器getPrefixes(字符串名称空间URI){
抛出新的UnsupportedOperationException();
}
}
代码的其余部分与其他答案相同。然后XPath
/:工作簿/:sheets/:sheet[1]
y