JAXP和Dom4j通过XPath获取带命名空间的节点
⼀、JAXP对XPath的⽀持
XPath是从JAXP1.3开始被⽀持的,在这些API中,其核⼼接⼝有XPath和XPathExpression,它们都在l.xpath包中,分别表⽰XPath对象以及被预编译后的表达式对象。
例如,对于如下的⽰例⽂件(schema_l)
<?xml version="1.0" encoding="UTF-8"?>
<templet>
<bean name="user" class="com.st.domain.User"></bean>
<bean name="company" class="com.st.domain.Company"></bean>
<description date="2013-06-07">This is a XML Schema example.</description>
</templet>
我们可以利⽤上述两个和XPath相关的接⼝进⾏⼀系列的节点访问操作。
public static void main(String[] args) throws XPathExpressionException, IOException {
// 返回默认的DOM对象模型的XPath⼯⼚
XPathFactory factory = wInstance();
XPath xPath = wXPath();
// 获取templet下name属性值为user的bean元素的class属性
String expression = "/templet/bean[@name='user']/@class";
InputSource source = new InputSource();
source.setByteStream(new FileInputStream(new File("resource/xml/schema/schema_l")));
// 预编译表达式,这种⽅式对于对同⼀个表达式多次求值来说,可提⾼运⾏效率,并且是线程安全的
XPathExpression compiledExpression = xPathpile(expression);
System.out.println(compiledExpression.evaluate(source));
}
上述代码执⾏后将会正确的得到schema_l⽂件中第⼀个bean元素的class的属性值。
现在,把xml⽂档的内容做如下修改(l):
<templet xmlns="www.appdemo.daniele/schema/templet"
xmlns:xsi="/2001/XMLSchema-instance"
xsi:schemaLocation="www.appdemo.daniele/schema/templet ../../../schema/schame_test.xsd">
<bean name="user" class="com.st.domain.User"></bean>
<bean name="company" class="com.st.domain.Company"></bean>
<description date="2013-06-07">This is a XML Schema example.</description>
</templet>
注意对⽐⽂件中根节点的变化,它在schema_l的基础上增加了两个与命名空间相关的属性xmlns和xmlns:xsi,其中xmlns是我们⾃定义⽂档结构规范的命名空间,是使⽤XPath时需要考虑的重点。
此时我们再次运⾏上⾯的main()⽅法后会发现将得不到任何输出结果。这主要是由于XPath不⽀持默认命名空间造成的,因此我们需要将前缀和相关的URI关联起来。
为达到这个⽬的,JAVA为我们提供了⼀个l.namespace.NamespaceContext接⼝,它的⽬的在于让我们可以在XPath接⼝对象中设置这个context,当XPath进⾏编译时,会根据前缀和URI的映射关系来匹配实际的节点。
NamespaceContext接⼝定义有如下三个⽅法:
1)public String getNamespaceURI(String prefix):获取绑定到当前范围中前缀的命名空间 URI;
2)public String getPrefix(String namespaceURI):获取绑定到当前范围命名空间 URI的前缀;
3)public Iterato getPrefixes(String namespaceURI):获取绑定到当前范围命名空间 URI的所有前缀。      下⾯来⾃定义实现⼀个NamespaceContext:
package com.l.jaxp.xpath;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
l.XMLConstants;
l.namespace.NamespaceContext;
/**
* <p>默认的命名空间处理器</p>
* @author  <a href="mailto:code727@gmail">Daniele</a>
* @version 1.0.0, 2013-7-11
* @see
* @since  AppDemo1.0.0
*/
public class DefaultNamespaceContext implements NamespaceContext {
/** 维持前缀与命名空间URI的映射关系(K:前缀,V:命名空间URI) */
private Map<String,String> namespaceMap = new HashMap<String,String>();
public DefaultNamespaceContext() {
addNamespace("xsi", XMLConstants.W3C_XML_SCHEMA_INSTANCE_NS_URI);
}
public void addNamespace(String prefix, String namespaceURI) {
namespaceMap.put(prefix, namespaceURI);
}
/**
* <p>获取绑定到当前范围中前缀的命名空间 URI。</p>
* @author <a href="mailto:code727@gmail">Daniele</a>
* @param prefix
* @return
* @since AppDemo1.0.0
*/
public String getNamespaceURI(String prefix) {
String namespaceURI = (prefix);
return namespaceURI != null ? namespaceURI : XMLConstants.NULL_NS_URI;
}
/**
* <p>获取绑定到当前范围命名空间 URI的前缀。</p>
* @author <a href="mailto:code727@gmail">Daniele</a>
* @param namespaceURI
* @return
* @since AppDemo1.0.0
*/
public String getPrefix(String namespaceURI) {
Set<String> keySet = namespaceMap.keySet();
for (String key : keySet) {
if ((key).equals(namespaceURI))
return key;
}
return XMLConstants.DEFAULT_NS_PREFIX;
}
}
/**
* <p>获取绑定到当前范围命名空间 URI的所有前缀。</p>
* @author <a href="mailto:code727@gmail">Daniele</a>
* @param namespaceURI
* @return
* @since AppDemo1.0.0
*/
public Iterator<?> getPrefixes(String namespaceURI) {
List<String> prefixes = new ArrayList<String>();
Set<String> keySet = namespaceMap.keySet();
for (String key : keySet) {
if ((key).equals(namespaceURI))
prefixes.add(key);
}
return prefixes.iterator();
}
}
完成后利⽤这个实现类,将前⾯的main()⽅法做如下修改(注意第5,11,12,15⾏的代码):
public static void main(String[] args) throws IOException, XPathExpressionException {
XPathFactory factory = wInstance();
XPath xPath = wXPath();
DefaultNamespaceContext defaultNamespaceContext = new DefaultNamespaceContext();
/*
*  设置前缀与命名空间URI的映射关系
*  XPath表达式中使⽤的前缀与⽂档中实际的前缀并没有关联,这⾥只是为了在XPath中增加⼀个前缀标识所有。
*  但前缀对应的命名空间URI必须与⽂档中实际的命名空间URI保持⼀致
*/
defaultNamespaceContext.addNamespace("t", "www.appdemo.daniele/schema/templet");
xPath.setNamespaceContext(defaultNamespaceContext);
InputSource source = new InputSource(new FileInputStream(new File("resource/xml/schema/l")));
String expression = "/t:templet/t:bean[@name='user']/@class";
XPathExpression compiledExpression = xPathpile(expression);
System.out.println(compiledExpression.evaluate(source));
}
再次运⾏main()⽅法将重新得到前⾯相同的运⾏结果。
⼆、Dom4j对XPath的⽀持
除了JAXP外,JDOM和Dom4j也提供了对XPath的⽀持。其中在Dom4j中,与JAXP的XPathExpression作⽤类似的接⼝为org.dom4j.XPath,这是在jar中,因此在实际的使⽤过程中需将此包加⼊到classpath中。
例如,利⽤XPath获取上述schema_l中⽆命名空间的节点:
package com.l.dom4j;
import java.io.File;
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.DocumentFactory;
import org.dom4j.DocumentHelper;
import org.dom4j.Element;
import org.dom4j.Node;
import org.dom4j.XPath;
import org.dom4j.io.SAXReader;
/**
* <p>Dom4j对XPath⽀持的测试类</p>
* @author  <a href="mailto:code727@gmail">Daniele</a>
* @version 1.0.0, 2013-7-18
* @see
* @since  AppDemo1.0.0
*/
public class Dom4jXPathTester {
public static void main(String[] args) throws DocumentException {
DocumentFactory factory = Instance();
SAXReader reader = new SAXReader(factory);
// 解析⽆命名空间的XML⽂档
Document document = ad(new File("./resource/xml/schema/schema_l"));
Element root = RootElement();
String subNodeName = "bean";
/*
* 从⼯⼚(DocumentFactory)对象或DocumentHelper中创建XPath对象,
* 与JAXP的XPathExpression类似,XPath的作⽤在于避免对同⼀个XPath求值时进⾏重复编译的问题
*/
XPath rootSubNodePath = Path() + "/" + subNodeName);
System.out.println("根元素" + Name() + "内共有"
+ rootSubNodePath.selectNodes(document).size() + "个"
+ subNodeName + "⼦节点");
subNodeName = "description";
rootSubNodePath = Path() + "/" + subNodeName);
Node subNode = rootSubNodePath.selectSingleNode(document);
System.out.println("根元素" + Name() + "⼦节点"
+ Name() + "的⽂本值为:" + Text());
}
}
运⾏后将得到如下结果:
使用dom4j解析xml文件根元素templet内共有2个bean⼦节点
根元素templet⼦节点description的⽂本值为:This is a XML Schema example.
与JAXP的情况⼀样,如果上述代码⽤来获取l⽂件中带命名空间的节点,则同样不会得到预期结果。为此,Dom4j 也提供了与JAXP类似的解决⽅案,实际使⽤情况甚⾄⽐JAXP的还要⽅便和灵活。
Dom4j也提供了⼀个名为NamespaceContext的接⼝(与l.namespace.NamespaceContext没有任何关系),它可以根据某⼀个前缀来得到对应的URI,它在Dom4j的依赖包jar中,因此在使⽤时需要将此包导⼊到classpath中。
jaxen提供了⼀个NamespaceContext的默认实现类SimpleNamespaceContext。在这个实现类中有⼀个叫namespaces的Map成员属性,它是⽤来维护命名空间前缀和URI的映射关系的。当进⾏XPath求值时,⾸先会将表达式中的前缀标识提取出来,再根据这个标识,到它所关联的NamespaceContext的映射中获取对应的URI,最终再调⽤NamespaceContext接⼝⽅法translateNamespacePrefixToUri(String prefix)返回URI。
对于SimpleNamespaceContext来说,它有如下两种⽅式来添加命名空间关系:
1)利⽤构造函数来添加——public SimpleNamespaceContext(Map namespaces), 这种⽅式传⼊⼀个已经在外部设置好了的映射关系即可;
2)调⽤SimpleNamespaceContext实例的addNamespace(String prefix, String URI)⽅法来逐条添加。
完成后,再调⽤XPath实例的setNamespaceContext(namespaceContext)⽅法,将Context实例传⼊即可。例如:

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。