使用 DOM 解析器在 Java 中解析具有 2 个默认命名空间的 XML

Posted

技术标签:

【中文标题】使用 DOM 解析器在 Java 中解析具有 2 个默认命名空间的 XML【英文标题】:Parsing an XML with 2 default namespace in Java using DOM parser 【发布时间】:2021-10-21 05:47:57 【问题描述】:

提前致谢。

我有一个 xml,在根级别有 2 个默认命名空间,然后作为元素级别。

<?xml version="1.0" encoding="UTF-8"?>
<Msg xmlns="http://www.geological.com">
    <header>
        <date>08-08-2021</date>
        <jur>US</jur>
    </header>
    <Demographic xmlns="urn:com.test:009">
        <geoData>
            <id>DL89716</id>
            <name>North</name>
        </geoData>
    </Demographic>
</Msg>

我正在使用 Java DOM 解析器来读取这个 xml 并获取“id”的值。 我仍然得到 null 的价值

DocumentBuilderFactory dbFactory = DocumentBuilderFactory.newInstance();
DocumentBuilder dBuilder = dbFactory.newDocumentBuilder();
dbFactory.setNamespaceAware(true);
document = dBuilder.parse(new InputSource(new StringReader(xmlPayLoad)));
document.normalize();

XPathEvaluator xPathObj = (XPathEvaluator)XPathFactory.newInstance(NamespaceConstant.OBJECT_MODEL_SAXON).newXPath();
xPathObj.setNamespaceContext(new MyNameSpace());
xPathObj.getStaticContext().setDefaultElementNamespace("http://www.geological.com");
XPathExpression expr = xPathObj.compile(xpath);
Object result = expr.evaluate(document, XPathConstants.NODESET);
NodeList nodeList = (NodeList) result;


 private static class MyNameSpace implements NamespaceContext 
       
        //The lookup for the namespace uris is delegated to the stored document.
        public String getNamespaceURI(String prefix) 
            if (prefix.equals(XMLConstants.DEFAULT_NS_PREFIX)) 
                return null;
             if("ns".equals(prefix))
                return "urn:com.test:009";
             
        

        public String getPrefix(String namespaceURI) 
            return sourceDocument.lookupPrefix(namespaceURI);
        

        @SuppressWarnings("rawtypes")
        public Iterator getPrefixes(String namespaceURI) 
            return null;
        
    

【问题讨论】:

【参考方案1】:

如果您知道您正在寻找一个 id 元素,或者至少没有来自不同命名空间的多个 id 元素并且您只想从一个命名空间。

如果您只想从一个命名空间(例如urn:com.test:009)中选择它们,则将其设置为默认命名空间,您可以简单地使用//id//Demographic/geoData/id

只有当你真的需要从不同的命名空间中选择元素时,你才需要绑定前缀。

使用 Java 8 内置的 JAXP XPathFactory 和不支持命名空间的 DocumentBuilderFactory,以下内容也适用于我选择 id 元素节点,因为在构建 DOM 树时会忽略默认命名空间声明:

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

    DocumentBuilder jaxpDocumentBuilder = documentBuilderFactory.newDocumentBuilder();

    Document domDoc = jaxpDocumentBuilder.parse(new File("sample1.xml"));

    XPathFactory jaxpXPathFactory = XPathFactory.newInstance();

    Node resultDomNode = (Node)jaxpXPathFactory.newXPath().evaluate("/Msg/Demographic/geoData/id", domDoc, XPathConstants.NODE);

我认为在您的原始代码中,一旦您使用 Saxon API 构建了一个命名空间感知 DOM,您就可以摆脱设置

xPathObj.getStaticContext().setUnprefixedElementMatchingPolicy(UnprefixedElementMatchingPolicy.ANY_NAMESPACE);

并且不要设置任何命名空间上下文或默认元素命名空间。

使用 Saxon 10 和 XPath 将任何命名空间中的元素与 id 等选择器匹配的另一种方法是深入其低级 API 以将其与 s9api 混合:

    Processor processor = new Processor(false);

    DocumentBuilder docBuilder = processor.newDocumentBuilder();

    XdmNode input = docBuilder.build(new File("sample1.xml"));

    NodeInfo contextItem = input.getUnderlyingNode();

    XPathEvaluator xpathEvaluator = new XPathEvaluator(processor.getUnderlyingConfiguration());

    IndependentContext independentContext = new IndependentContext();
    independentContext.setUnprefixedElementMatchingPolicy(UnprefixedElementMatchingPolicy.ANY_NAMESPACE);

    xpathEvaluator.setStaticContext(independentContext);

    XPathExpression expression = xpathEvaluator.createExpression("/Msg/Demographic/geoData/id");

    NodeInfo resultInfo = (NodeInfo) expression.evaluateSingle(expression.createDynamicContext(contextItem));

    XdmNode resultNode = new XdmNode(resultInfo);

    System.out.println(resultNode);

【讨论】:

谢谢马丁。我确实需要绑定到不同的命名空间。 @Anurag,很好,但是请显示您的 XPath 表达式,否则我们无法判断 Java 代码中的命名空间绑定是否有意义。 我将从我的根节点开始在 excel(大约 500 多个)中转储 xpath。所以不可能改变我的 xpath 或编辑它 /Msg/Demographic/geoData/id /Msg/Demographic/geoData/name /Msg/Demographic/.. 别的东西 @Anurag,如果您希望像 /Msg/Demographic/geoData/id 这样的路径可以针对具有两个不同名称空间中的元素的输入文档进行处理,那么您在使用名称空间的任何 XPath 实现中都找不到解决方法了解 DOM 并实现标准 XPath。你唯一的赌注是不知道命名空间的 DOM,但我不确定 Saxon 是否支持。对于 XSLT,最新的 Saxon 版本有 saxonica.com/html/documentation10/javadoc/net/sf/saxon/s9api/…,但我不确定您是否可以为 XPath 设置它。【参考方案2】:

这是一个功能齐全的示例:

import org.w3c.dom.Document;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;

import javax.xml.XMLConstants;
import javax.xml.namespace.NamespaceContext;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.xpath.XPath;
import javax.xml.xpath.XPathConstants;
import javax.xml.xpath.XPathExpression;
import javax.xml.xpath.XPathExpressionException;
import javax.xml.xpath.XPathFactory;
import java.io.IOException;
import java.io.StringReader;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;

public class NamespacesExample 

  private static String xmlPayLoad =
    "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" +
    "<Msg xmlns=\"http://www.geological.com\">\n" +
    "    <header>\n" +
    "        <date>08-08-2021</date>\n" +
    "        <jur>US</jur>\n" +
    "    </header>\n" +
    "    <Demographic xmlns=\"urn:com.test:009\">\n" +
    "        <geoData>\n" +
    "            <id>DL89716</id>\n" +
    "            <name>North</name>\n" +
    "        </geoData>\n" +
    "    </Demographic>\n" +
    "</Msg>";

  public static void main(String[] args) 
    try 
      DocumentBuilderFactory dbFactory = DocumentBuilderFactory.newInstance();
      DocumentBuilder dBuilder = null;
      dbFactory.setNamespaceAware(true);
      dBuilder = dbFactory.newDocumentBuilder();
      Document document = dBuilder.parse(new InputSource(new StringReader(xmlPayLoad)));

      final Map<String, String> ns = new HashMap<>();
      ns.put("geo", "http://www.geological.com");
      ns.put("test", "urn:com.test:009");
      XPath xpath = XPathFactory.newInstance().newXPath();
      xpath.setNamespaceContext(new MyNameSpace(ns));

      XPathExpression expr = xpath.compile("/geo:Msg/test:Demographic/test:geoData/test:id/text()");
      String result = (String) expr.evaluate(document, XPathConstants.STRING);
      System.out.println("Result: " + result);
     catch (ParserConfigurationException | IOException | XPathExpressionException | SAXException e) 
      e.printStackTrace();
    
  


  private static class MyNameSpace implements NamespaceContext 

    private final Map<String, String> ns;

    MyNameSpace(Map<String, String> ns) 
      this.ns = new HashMap<>(ns);
      this.ns.put(XMLConstants.XML_NS_PREFIX, XMLConstants.XML_NS_URI);
      this.ns.put(XMLConstants.XMLNS_ATTRIBUTE, XMLConstants.XMLNS_ATTRIBUTE_NS_URI);
    

    @Override
    public String getNamespaceURI(String prefix) 
      if(prefix == null) 
        throw new IllegalArgumentException();
      

      final String uri = ns.get(prefix);
      return uri == null ? XMLConstants.NULL_NS_URI : uri;
    

    @Override
    public String getPrefix(String namespaceURI) 
      throw new UnsupportedOperationException();
    

    @Override
    public Iterator getPrefixes(String namespaceURI) 
      throw new UnsupportedOperationException();
    
  

一些备注:

这些语句的顺序很重要。否则没有命名空间感知:

dbFactory.setNamespaceAware(true);
dBuilder = dbFactory.newDocumentBuilder();

你为什么使用XPathEvaluator?没有必要这样做。使用接口XPath就够了。

NamespaceConstant.OBJECT_MODEL_SAXON 是什么?至少在这个例子中,我们不需要它。

通过我的通用NamespaceContext 实现,您可以使用简单的Map&lt;String, String&gt; 来定义XPATH 评估的命名空间前缀。 无论在 XML 文档中如何声明命名空间,都应为 XML 中使用的每个命名空间使用专用前缀。

然后你可以使用这个 XPATH 表达式来提取 id:/geo:Msg/test:Demographic/test:geoData/test:id/text()

输出是:

Result: DL89716

【讨论】:

以上是关于使用 DOM 解析器在 Java 中解析具有 2 个默认命名空间的 XML的主要内容,如果未能解决你的问题,请参考以下文章

PHP DOM 解析器在循环时按类获取特定文本

流式处理和基于树的 XML 解析器在 JAVA 开始时是不是消耗相似数量的内存

用于 XML 编辑、更新的 DOM 解析器

如何使用基于标签的 XML::Twig 解析器在 Unix 上清理 xml 文件 [关闭]

用于XML Edit,Update的DOM解析器

HyperDown.js 这个markdown解析器在浏览器中怎么使用