XPath,XML命名空间和Java
我花了一天的时间尝试从以下文档中提取一个XML节点,并且无法掌握XML名称空间的细微差别以使其工作。XPath,XML命名空间和Java
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”标签(从底部三个)中提取“数字”属性。
,我使用做到这一点的代码如下所示:
/***
* 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]";
不幸的是,它不起作用,我不断得到一个空回报。
我已经完成了相当数量的阅读here,here和here,但没有任何证据足以说明我的工作。
当我明白这一点时,我几乎肯定我要面子,但我真的很机智地结束了我失踪的事情。
感谢您阅读所有这些内容,并提前感谢您的帮助。
-Andy
啊哈,我试着调试你的表达式+让它工作。你错过了一些事情。这个XPath表达式应该这样做:
/XFDL/globalpage/global/xmlmodel/instances/instance/form_metadata/title/documentnbr/@number
- 您需要包括根元素(XFDL在这种情况下)
- 我没有最终需要在因某种原因表达式中使用的任何命名空间。不知道为什么。如果是这种情况,那么NamespaceContext.getNamespaceURI()永远不会被调用。如果我将
instance
替换为xforms:instance
,那么getNamespaceURI()将以xforms
作为输入参数被调用一次,但程序会引发异常。 - 属性值的语法是
@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);
}
}
}
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);
如果我没有在应用程序的其他地方使用DOM/XPath,我会走这条路,但是现在我已经朝着另一个方向前进了。 在应用程序内部混合是否存在传统观点? – MrWizard54 2011-03-29 11:15:06
您可以使用带有第二个代码的DOM/XPath(xpath.evalute也需要Document对象)。在我看来,最好使用NamespaceContextMap类(imho应该在JDK中)。 – 2011-04-06 23:33:10
看看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);
(声明:我是图书馆的作者)
完美解决方案,我还是不太明白的命名空间,但在现在最少的代码工作。 非常感谢。 – MrWizard54 2011-03-29 11:16:36
@Jason:“由于某种原因,我最终没有在表达式中使用任何名称空间。”在标准的Java实现中,默认情况下,DocumentBuilderFactory会生成namespace-_unaware_分析器。在生成DocumentBuilder之前添加'dbfac.SetNamespaceAware(true)'可能会改变结果。 – 2013-03-08 15:18:09