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

2022-08-31 14:39:05

当我的XML看起来像这样(不)时,我可以很容易地用XPath查询它,就像xmlns/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>

有什么想法吗?


答案 1

在第二个示例 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]

如您所见,它会产生一个非常长且冗长的 XPath 语句,该语句非常难以阅读(和维护)。

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

/*[local-name()='workbook']/*[local-name()='sheets']/*[local-name()='sheet'][1]

但是,您有匹配错误元素的风险。如果您的 XML 具有使用相同词汇的混合词汇表(对于此实例可能不是问题),则 XPath 可能会在错误的元素上进行匹配并选择错误的内容:local-name()


答案 2

您的问题是默认命名空间。查看本文,了解如何处理 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);

瞧...现在,您的元素已保存在变量中。result

注意:如果您使用标准的 JAXP 类将 XML 解析为 DOM,请务必调用 .否则,此代码将不起作用!setNamespaceAware(true)DocumentBuilderFactory


推荐