XPath,XML命名空间和Java

我花了一天时间尝试从以下文档中提取一个XML节点,并且无法掌握XML命名空间的细微差别以使其工作。

XML文件总数很大,所以这是我关注的部分:

       
HAND RECEIPT/ANNEX NUMBER

该文件继续存在并且形成良好。 我试图从“documentnbr”标签中提取“数字”属性(从底部三个)。

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

 /*** * 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]"; 

不幸的是它不起作用,我不断获得null返回。

我已经在这里 , 这里和这里做了相当多的阅读,但没有任何证据足以帮助我实现这一目标。

当我想到这一点时,我几乎是肯定的,我会面对手掌,但我真的很想知道我缺少什么。

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

-Andy

啊哈,我试着调试你的表情+让它发挥作用。 你错过了一些事情。 这个XPath表达式应该这样做:

 /XFDL/globalpage/global/xmlmodel/instances/instance/form_metadata/title/documentnbr/@number 
  1. 您需要包含根元素(在这种情况下为XFDL)
  2. 出于某种原因,我最终不需要在表达式中使用任何名称空间。 不知道为什么。 如果是这种情况,则永远不会调用NamespaceContext.getNamespaceURI()。 如果我用xforms:instance替换instance ,则以xforms作为输入参数调用getNamespaceURI()一次,但程序会抛出exception。
  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 prefixMap; MyNamespaceContext(Map prefixMap) { if (prefixMap != null) { this.prefixMap = Collections.unmodifiableMap(new HashMap(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); } } } 

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

你可以从这里获得NamespaceContextMap类(不是我的)(GPL许可证)。 还有6376058错误。

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

获取number属性的代码是:

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

命名空间自动从根节点中提取(在本例中为doc )。 如果您需要显式定义其他命名空间,可以使用以下命令:

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

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