【问题标题】:XPath, XML Namespaces and JavaXPath、XML 命名空间和 Java
【发布时间】:2011-07-24 20:43:12
【问题描述】:

过去一天我一直在尝试从以下文档中提取一个 XML 节点,但无法掌握 XML 命名空间的细微差别以使其工作。

XML 文件太大,无法全部发布,所以这里是我关心的部分:

<?xml version="1.0" encoding="ISO-8859-1" standalone="no"?>
<XFDL xmlns="http://www.PureEdge.com/XFDL/6.5" xmlns:custom="http://www.PureEdge.com/XFDL/Custom" xmlns:designer="http://www.PureEdge.com/Designer/6.1" xmlns:pecs="http://www.PureEdge.com/PECustomerService" xmlns:xfdl="http://www.PureEdge.com/XFDL/6.5">
   <globalpage sid="global">
      <global sid="global">
         <xmlmodel xmlns:xforms="http://www.w3.org/2003/xforms">
            <instances>
               <xforms:instance id="metadata">
                  <form_metadata>
                     <metadataver version="1.0"/>
                     <metadataverdate>
                        <date day="05" month="Jul" year="2005"/>
                     </metadataverdate>
                     <title>
                        <documentnbr number="2062" prefix.army="DA" scope="army" suffix=""/>
                        <longtitle>HAND RECEIPT/ANNEX NUMBER </longtitle>
                     </title>

文档继续,并且格式正确。我正在尝试从“documentnbr”标签(底部三个)中提取“number”属性。

我用来执行此操作的代码如下所示:

/***
     * Locates the Document Number information in the file and returns the form number.
     * @return File's self-declared number.
     * @throws InvalidFormException Thrown when XPath cannot find the "documentnbr" element in the file.
     */
    public String getFormNumber() throws InvalidFormException
    {
        try{
            XPath xPath = XPathFactory.newInstance().newXPath();
            xPath.setNamespaceContext(new XFDLNamespaceContext());

            Node result = (Node)xPath.evaluate(QUERY_FORM_NUMBER, doc, XPathConstants.NODE);
            if(result != null) {
                return result.getNodeValue();
            } else {
                throw new InvalidFormException("Unable to identify form.");
            }

        } catch (XPathExpressionException err) {
            throw new InvalidFormException("Unable to find form number in file.");
        }

    }

其中 QUERY_FORM_NUMBER 是我的 XPath 表达式,而 XFDLNamespaceContext 实现了 NamespaceContext,如下所示:

public class XFDLNamespaceContext implements NamespaceContext {

    @Override
    public String getNamespaceURI(String prefix) {
        if (prefix == null) throw new NullPointerException("Invalid Namespace Prefix");
        else if (prefix.equals(XMLConstants.DEFAULT_NS_PREFIX))
            return "http://www.PureEdge.com/XFDL/6.5";
        else if ("custom".equals(prefix))
            return "http://www.PureEdge.com/XFDL/Custom";
        else if ("designer".equals(prefix)) 
            return "http://www.PureEdge.com/Designer/6.1";
        else if ("pecs".equals(prefix)) 
            return "http://www.PureEdge.com/PECustomerService";
        else if ("xfdl".equals(prefix))
            return "http://www.PureEdge.com/XFDL/6.5";      
        else if ("xforms".equals(prefix)) 
            return "http://www.w3.org/2003/xforms";
        else    
            return XMLConstants.NULL_NS_URI;
    }

    @Override
    public String getPrefix(String arg0) {
        // TODO Auto-generated method stub
        return null;
    }

    @Override
    public Iterator getPrefixes(String arg0) {
        // TODO Auto-generated method stub
        return null;
    }

}

我尝试了许多不同的 XPath 查询,但我一直觉得这应该可行:

protected static final String QUERY_FORM_NUMBER = 
        "/globalpage/global/xmlmodel/xforms:instances/instance" + 
        "/form_metadata/title/documentnbr[number]";

不幸的是它不起作用,我不断得到一个空返回。

我已经阅读了大量的 hereherehere,但没有任何证据证明足以帮助我完成这项工作。

我几乎可以肯定,当我弄清楚这一点时,我会面对手掌,但我真的不知所措。

感谢您阅读所有这些内容,并提前感谢您的帮助。

-安迪

【问题讨论】:

    标签: java xpath xml-namespaces xfdl


    【解决方案1】:

    啊哈,我试着调试你的表达+让它工作。你错过了一些东西。这个 XPath 表达式应该这样做:

    /XFDL/globalpage/global/xmlmodel/instances/instance/form_metadata/title/documentnbr/@number
    
    1. 您需要包含根元素(本例中为 XFDL)
    2. 出于某种原因,我最终不需要在表达式中使用任何命名空间。不知道为什么。如果是这种情况,则永远不会调用 NamespaceContext.getNamespaceURI()。如果我将instance 替换为xforms:instance,那么getNamespaceURI() 会以xforms 作为输入参数调用一次,但程序会引发异常。
    3. 属性值的语法是@attr,而不是[attr]

    我的完整示例代码:

    import java.io.File;
    import java.io.IOException;
    import java.util.Collections;
    import java.util.HashMap;
    import java.util.Iterator;
    import java.util.Map;
    
    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.XPathExpressionException;
    import javax.xml.xpath.XPathFactory;
    
    import org.w3c.dom.Document;
    import org.w3c.dom.Node;
    import org.xml.sax.SAXException;
    
    public class XPathNamespaceExample {
        static public class MyNamespaceContext implements NamespaceContext {
            final private Map<String, String> prefixMap;
            MyNamespaceContext(Map<String, String> prefixMap)
            {
                if (prefixMap != null)
                {
                    this.prefixMap = Collections.unmodifiableMap(new HashMap<String, String>(prefixMap));
                }
                else
                {
                    this.prefixMap = Collections.emptyMap();
                }
            }
            public String getPrefix(String namespaceURI) {
                // TODO Auto-generated method stub
                return null;
            }
            public Iterator getPrefixes(String namespaceURI) {
                // TODO Auto-generated method stub
                return null;
            }
            public String getNamespaceURI(String prefix) {
                    if (prefix == null) throw new NullPointerException("Invalid Namespace Prefix");
                    else if (prefix.equals(XMLConstants.DEFAULT_NS_PREFIX))
                        return "http://www.PureEdge.com/XFDL/6.5";
                    else if ("custom".equals(prefix))
                        return "http://www.PureEdge.com/XFDL/Custom";
                    else if ("designer".equals(prefix)) 
                        return "http://www.PureEdge.com/Designer/6.1";
                    else if ("pecs".equals(prefix)) 
                        return "http://www.PureEdge.com/PECustomerService";
                    else if ("xfdl".equals(prefix))
                        return "http://www.PureEdge.com/XFDL/6.5";      
                    else if ("xforms".equals(prefix)) 
                        return "http://www.w3.org/2003/xforms";
                    else    
                        return XMLConstants.NULL_NS_URI;
            }
    
    
        }
    
        protected static final String QUERY_FORM_NUMBER = 
            "/XFDL/globalpage/global/xmlmodel/xforms:instances/instance" + 
            "/form_metadata/title/documentnbr[number]";
    
        public static void main(String[] args) {
            try
            {
                DocumentBuilderFactory dbfac = DocumentBuilderFactory.newInstance();
                DocumentBuilder docBuilder = dbfac.newDocumentBuilder();
                Document doc = docBuilder.parse(new File(args[0]));
                System.out.println(extractNodeValue(doc, "/XFDL/globalpage/@sid"));
                System.out.println(extractNodeValue(doc, "/XFDL/globalpage/global/xmlmodel/instances/instance/@id" ));
                System.out.println(extractNodeValue(doc, "/XFDL/globalpage/global/xmlmodel/instances/instance/form_metadata/title/documentnbr/@number" ));
            } catch (SAXException e) {
                e.printStackTrace();
            } catch (IOException e) {
                e.printStackTrace();
            } catch (ParserConfigurationException e) {
                e.printStackTrace();
            }
        }
    
        private static String extractNodeValue(Document doc, String expression) {
            try{
    
                XPath xPath = XPathFactory.newInstance().newXPath();
                xPath.setNamespaceContext(new MyNamespaceContext(null));
    
                Node result = (Node)xPath.evaluate(expression, doc, XPathConstants.NODE);
                if(result != null) {
                    return result.getNodeValue();
                } else {
                    throw new RuntimeException("can't find expression");
                }
    
            } catch (XPathExpressionException err) {
                throw new RuntimeException(err);
            }
        }
    }
    

    【讨论】:

    • 完美的解决方案,我仍然不太了解命名空间,但至少代码现在可以工作。非常感谢。
    • @Jason:“出于某种原因,我最终不需要在表达式中使用任何命名空间。”在标准 Java 实现中,DocumentBuilderFactory 默认生成命名空间不知道的解析器。在生成 DocumentBuilder 之前添加dbfac.SetNamespaceAware(true) 可能会改变结果。
    【解决方案2】:

    SAX(XPath 的替代品)版本:

    SAXParser saxParser = SAXParserFactory.newInstance().newSAXParser();
    final String[] number = new String[1];
    DefaultHandler handler = new DefaultHandler()
    {           
        @Override
        public void startElement(String uri, String localName, String qName,
        Attributes attributes) throws SAXException
        {
            if (qName.equals("documentnbr"))
                number[0] = attributes.getValue("number");
        }
    };
    saxParser.parse("input.xml", handler);
    System.out.println(number[0]);
    

    我发现将 XPath 与命名空间一起使用会更复杂,因为它应该(我的意见)。这是我的(简单)代码:

    XPath xpath = XPathFactory.newInstance().newXPath();
    
    NamespaceContextMap contextMap = new NamespaceContextMap();
    contextMap.put("custom", "http://www.PureEdge.com/XFDL/Custom");
    contextMap.put("designer", "http://www.PureEdge.com/Designer/6.1");
    contextMap.put("pecs", "http://www.PureEdge.com/PECustomerService");
    contextMap.put("xfdl", "http://www.PureEdge.com/XFDL/6.5");
    contextMap.put("xforms", "http://www.w3.org/2003/xforms");
    contextMap.put("", "http://www.PureEdge.com/XFDL/6.5");
    
    xpath.setNamespaceContext(contextMap);
    String expression = "//:documentnbr/@number";
    InputSource inputSource = new InputSource("input.xml");
    String number;
    number = (String) xpath.evaluate(expression, inputSource, XPathConstants.STRING);
    System.out.println(number);
    

    您可以从here(GPL 许可)获得 NamespaceContextMap 类(不是我的)。还有6376058的bug。

    【讨论】:

    • 如果我没有在应用程序的其他任何地方使用 DOM/XPath,我会走这条路,但目前我正朝着另一个方向前进。是否有关于在应用程序内部进行混合的传统智慧?
    • 您可以将 DOM/XPath 与第二个代码一起使用(xpath.evalute 也采用 Document 对象)。在我看来,最好使用 NamespaceContextMap 类(恕我直言应该在 JDK 中)。
    【解决方案3】:

    看看XPathAPI 库。这是一种使用 XPath 的更简单的方法,而不会弄乱低级 Java API,尤其是在处理命名空间时。

    获取number 属性的代码是:

    String num = XPathAPI.selectSingleNodeAsString(doc, '//documentnbr/@number');
    

    命名空间会自动从根节点(在本例中为doc)提取。如果您需要明确定义额外的命名空间,您可以使用:

    Map<String, String> nsMap = new HashMap<String, String>();
    nsMap.put("xforms", "http://www.w3.org/2003/xforms");
    
    String num =
        XPathAPI.selectSingleNodeAsString(doc, '//documentnbr/@number', nsMap);
    

    (免责声明:我是图书馆的作者。)

    【讨论】:

      猜你喜欢
      • 2011-08-07
      • 1970-01-01
      • 2014-04-19
      • 1970-01-01
      • 2013-06-23
      • 2010-10-07
      • 1970-01-01
      • 1970-01-01
      • 2011-02-18
      相关资源
      最近更新 更多