如何使用XPath使用Java中的命名空间查询XML?

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可能匹配错误的元素并选择错误的内容:

  • 我不明白为什么我需要在我的XPath中关联名称空间URI和名称空间前缀?在XML文档中,已经存在这样的关联,例如原始问题中的xmlns:r =“ http://schemas.openxmlformats.org/officeDocument/2006/relationships”。在那里,前缀r绑定到名称空间URI。以我的阅读方式,我不得不(或以编程方式)在XPath中重新建立此连接。 (2认同)
  • 我会反对这种做法。如果可能的话,请不要与本地名称和名称空间匹配,这会使您的代码混乱,并且无法快速进行哈希速度查找。@nokul:这是因为XPath可以对任何文档进行操作,并且名称空间前缀可以不同,但​​是名称空间不能。如果将xmlns:xx绑定到名称空间aaa,并且文档在同一名称空间中具有&lt;yy:foo&gt;,则xpath表达式xx:foo将选择该节点。 (2认同)

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.否则,此代码将无法正常工作!

  • 该关键警告的+1; 它给我带来了很多悲伤. (5认同)
  • +1 for setNamespaceAware(true) ..xpath 在我发现问题不在于注册 NS 或 xpath 语句本身而是更早之前让我发疯! (2认同)

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引擎表达式中给定前缀名与源文档中的特定名称空间相关的一种方法.


kas*_*asi 5

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