【嘿!Java】XPath在Java中的使用和小问题

博主最近看W3School的教程http://www.w3school.com.cn/xpath/index.asp,学了一些XPath的皮毛,这里面主要讲XPath的语法,具体如何在Java代码中使用XPath则参考这篇文章Java 语言的 XPath API

博主参考第二篇文章,首先在这里给出示例Java代码,之后会说说使用过程中的小问题。下面的代码只做到查询出NodeList这一步,查询出的结果做什么样的处理(如:读取文本等)这里暂时不讨论。

import java.io.IOException;
import java.lang.Double;

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 org.w3c.dom.Document;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;

public class MyXPath
{
	private XPath xpath;
	private Document doc;
	private Object result;
	
	/**
	 * 构造函数
	 * @param fileName 待解析的xml文件
	 */
	public MyXPath(String fileName)
	{
		this.xpath = generateXPath();
		//将filename路径下的文档加载到Document对象中
		this.doc = generateDocument(fileName);
	}	

	public XPath generateXPath()
	{
		XPathFactory factory = XPathFactory.newInstance();
		XPath xpath = factory.newXPath();
		return xpath;
	}
	
	public Document generateDocument(String fileName)
	{
		DocumentBuilder builder = generateDocumentBuilder();
		Document doc = null;
		try
		{
			doc = builder.parse(fileName);
		} catch (SAXException e)
		{
			e.printStackTrace();
		} catch (IOException e)
		{
			e.printStackTrace();
		}
		return doc;
	}
	
	public DocumentBuilder generateDocumentBuilder()
	{
		DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
		factory.setNamespaceAware(true);// never forget this!
		DocumentBuilder builder = null;
		try 
		{
			builder = factory.newDocumentBuilder();
		} catch (ParserConfigurationException e)
		{
			e.printStackTrace();
		}
		return builder;
	}

	/**
	 * 查询节点集
	 * @param exprString XPath查询语句
	 * @return 查询到的节点集
	 */
	public NodeList queryNodelist(String exprString)
	{
		NodeList nodes = null;		
		try
		{
			//用XPath对象编译 XPath表达式
			XPathExpression expr = xpath.compile(exprString);
			/*计算XPath表达式得到结果
			*表达式是针对特定的上下文节点计算的,在这个例子中是整个文档
			*还必须指定返回类型。这里要求返回一个节点集
			*/
			Object result = expr.evaluate(doc, XPathConstants.NODESET);
			//将结果强制转化成NodeList
			nodes = (NodeList) result;
		} catch (XPathExpressionException e)
		{
			e.printStackTrace();
		}
		return nodes;
	}
	
	/**
	 * 查询数字
	 * @param exprString XPath查询语句
	 * @return 查询到的数字
	 */
	public double queryNumber(String exprString)
	{
		Double number=new Double(0);
		try
		{
			//用XPath对象编译 XPath表达式
			XPathExpression expr = xpath.compile(exprString);
			/*计算XPath表达式得到结果
			*表达式是针对特定的上下文节点计算的,在这个例子中是整个文档
			*还必须指定返回类型。这里要求返回一个数值
			*/
			Object result = expr.evaluate(doc, XPathConstants.NUMBER);
			//将结果强制转化成NodeList
			number = (Double) result;
		} catch (XPathExpressionException e)
		{
			e.printStackTrace();
		}
		//将Double类转变为基本数据类型double并返回
		return number.doubleValue();
	}
}

后来,博主使用这段代码时产生了新需求。上面这段代码在new MyXPath(String fileName)时,必须提供一个包括其路径文件,比如在Java工程下有一个xml文件夹,里面存放了一个author.xml文件,就要写new MyXPath("xml/author.xml");。但有时候并 没有一个已经存在的现成的文件,比如:通过发送网络请求,得到xml数据,因为不想将其以文件形式存储下来,所以只有存储它的流InputStream。

博主在网上搜了一些文章,了解到上面代码中generateDocument()方法里有句
doc = builder.parse(fileName);
这里fileName是形如"xml/author.xml"的字符串,其实这句parse()方法的参数也可以是InputStream类的 得意这下好办了,博主就将上面代码修改成下面这样。
import java.io.IOException;
import java.io.InputStream;
import java.lang.Double;

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 org.w3c.dom.Document;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;

public class XPathInputStream
{
	public XPath generateXPath()
	{
		XPathFactory factory = XPathFactory.newInstance();
		XPath xpath = factory.newXPath();
		return xpath;
	}
	
	public Document generateDocument(InputStream in)
	{
		DocumentBuilder builder = generateDocumentBuilder();
		Document doc = null;
		try
		{
			doc = builder.parse(in);
		} catch (Exception e)
		{
			e.printStackTrace();
		}
		return doc;
	}
	
	public DocumentBuilder generateDocumentBuilder()
	{
		DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
		factory.setNamespaceAware(true);// never forget this!
		DocumentBuilder builder = null;
		try 
		{
			builder = factory.newDocumentBuilder();
		} catch (ParserConfigurationException e)
		{
			e.printStackTrace();
		}
		return builder;
	}

	/**
	 * 查询节点集
	 * @param in 待查询的输入流
	 * @param exprString XPath查询语句
	 * return 查询到的节点集
	 */
	public NodeList queryNodelist(InputStream in,String exprString)
	{
		NodeList nodes = null;		
		
		XPath xpath=generateXPath();
		Document doc=generateDocument(in);
		try
		{			
			XPathExpression expr = xpath.compile(exprString);//用XPath对象编译 XPath表达式
			/*计算XPath表达式得到结果
			*表达式是针对特定的上下文节点计算的,在这个例子中是整个文档
			*还必须指定返回类型。这里要求返回一个节点集
			*/
			result = expr.evaluate(doc, XPathConstants.NODESET);			
			nodes = (NodeList) result;//将结果强制转化成NodeList
		} catch (XPathExpressionException e)
		{
			e.printStackTrace();
		}
		return nodes;
	}
}

问题描述

但是这段代码在运用过程中出现了一个问题:当用类似下面的代码连续解析同一个流in,会报错。

XPathInputStream.queryNodelist(in, "//disambiguation[1]/organization");
XPathInputStream.queryNodelist(in, "//interest/keyword");


原因分析

博主在网上查了,在使用xpath解析xml时,出现IOException里找到了答案。这位博主evaluate方法用的是Object evaluate(InputSource source, QName returnType),他是这么说自己代码报错原因的:

在每次调用Object evaluate(InputSource source, QName returnType)这个方法时都会重新生成DocumentBuilder对象来parse InputSource,每次parse都会把整个文件都解析成dom树并load到内存中,然后关闭流,在第二次parse同一个Inputsource的时候便产生了IOException

博主用的是Object evaluate(Document doc, QName returnType),与上面不一样,但根据这个说法类推——博主的代码里每次调用XPathUtils.queryNodelist()都会执行一次generateDocument(in),里面有一句doc = builder.parse(in);,可以猜测这句话执行完会关闭流,所以对同一个流in再次调用XPathInputStream.queryNodelist();时,in已经被关闭,造成报错。

博主没有看XPath的源代码,这只是自己猜想,如果哪位大神知道真正的原因,告诉博主呀~~可怜


解决办法

博主猜测上面的原因后,想如果连续同一个流in,只调用一次doc = builder.parse(in);,是不是就不会报错了呢?为此,博主修改了代码,实验了之后发现真的没有再报错,说明博主猜想应该没有错。

import java.io.IOException;
import java.io.InputStream;
import java.lang.Double;

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 org.w3c.dom.Document;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;

public class MyXPathInputStream
{
	private XPath xpath;
	private Document doc;
	private Object result;
	
	/**
	 * 构造函数
	 * @param in 待解析的流
	 */
	public MyXPathInputStream(InputStream in)
	{
		this.xpath = generateXPath();	
		//将InputStream加载到Document对象中
		this.doc = generateDocument(in);
	}	

	public XPath generateXPath()
	{
		XPathFactory factory = XPathFactory.newInstance();
		XPath xpath = factory.newXPath();
		return xpath;
	}
	
	public Document generateDocument(InputStream in)
	{
		DocumentBuilder builder = generateDocumentBuilder();
		Document doc = null;
		try
		{
			doc = builder.parse(in);
		} catch (Exception e)
		{
			e.printStackTrace();
		}
		return doc;
	}
	
	public DocumentBuilder generateDocumentBuilder()
	{
		DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
		factory.setNamespaceAware(true);// never forget this!
		DocumentBuilder builder = null;
		try 
		{
			builder = factory.newDocumentBuilder();
		} catch (ParserConfigurationException e)
		{
			e.printStackTrace();
		}
		return builder;
	}

	public NodeList queryNodelist(String exprString)
	{
		NodeList nodes = null;		
		try
		{			
			XPathExpression expr = xpath.compile(exprString);//用XPath对象编译 XPath表达式
			/*计算XPath表达式得到结果
			*表达式是针对特定的上下文节点计算的,在这个例子中是整个文档
			*还必须指定返回类型。这里要求返回一个节点集
			*/
			result = expr.evaluate(doc, XPathConstants.NODESET);			
			nodes = (NodeList) result;//将结果强制转化成NodeList
		} catch (XPathExpressionException e)
		{
			e.printStackTrace();
		}
		return nodes;
	}
	
	public double queryNumber(String exprString)
	{
		Double number=new Double(0);
		try
		{
			XPathExpression expr = xpath.compile(exprString);
			/*计算XPath表达式得到结果
			表达式是针对特定的上下文节点计算的,在这个例子中是整个文档
			还必须指定返回类型。这里要求返回一个数值
			*/
			result = expr.evaluate(doc, XPathConstants.NUMBER);
			number = (Double) result;
		} catch (XPathExpressionException e)
		{
			e.printStackTrace();
		}
		//将Double类转变为基本数据类型double并返回
		return number.doubleValue();
	}
}
连续调用代码如下,不会报错

MyXPathInputStream.queryNodelist("//disambiguation[1]/organization");
MyXPathInputStream.queryNodelist("//interest/keyword");





JsoupXpath 是一款纯Java开发的使用xpath解析html的解析器,xpath语法分析与执行完全独立,html的DOM树生成借助Jsoup,故命名为JsoupXpath.为了在java里也享受xpath的强大与方便但又苦于找不到一款足够强大的xpath解析器,故开发了JsoupXpath。JsoupXpath的实现逻辑清晰,扩展方便,支持几乎全部常用的xpath语法.http://www.cnblogs.com/ 为例 "//a/@href"; "//div[@id='paging_block']/div/a[text()='Next >']/@href"; "//div[@id='paging_block']/div/a[text()*='Next']/@href"; "//h1/text()"; "//h1/allText()"; "//h1//text()"; "//div/a"; "//div[@id='post_list']/div[position()1000]/div/h3/allText()"; //轴支持 "//div[@id='post_list']/div[self::div/div/div/span[@class='article_view']/a/num()>1000]/div/h3/allText()"; "//div[@id='post_list']/div[2]/div/p/preceding-sibling::h3/allText()"; "//div[@id='post_list']/div[2]/div/p/preceding-sibling::h3/allText()|//div[@id='post_list']/div[1]/div/h3/allText()"; 在这里暂不列出框架间的对比了,但我相信,你们用了会发现JsoupXpath就是目前市面上最强大的的Xpath解析器。 快速开始 如果不方便使用maven,可以直接使用lib下的依赖包跑起来试试,如方便可直接使用如下dependency(已经上传至央maven库,最新版本0.1.1):    cn.wanghaomiao    JsoupXpath    0.1.1 依赖配置好后,就可以使用如下例子进行体验了!String xpath="//div[@id='post_list']/div[./div/div/span[@class='article_view']/a/num()>1000]/div/h3/allText()";String doc = "..."; JXDocument jxDocument = new JXDocument(doc); List<Object> rs = jxDocument.sel(xpath); for (Object o:rs){     if (o instanceof Element){             int index = ((Element) o).siblingIndex();             System.out.println(index);     }     System.out.println(o.toString()); } 其他可以参考 cn.wanghaomiao.example包下的例子 语法 支持标准xpath语法(支持谓语嵌套),支持全部常用函数,支持全部常用轴,去掉了一些标准里面华而不实的函数和轴,下面会具体介绍。语法可以参考http://www.w3school.com.cn/xpath/index.asp 关于使用Xpath的一些注意事项 非常不建议直接粘贴Firefox或chrome里生成的Xpa
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值