JAXP,sax与dom4j解析xml文档及XPath在文档内容定位中的应用(持续更新,初学者的野路子、参考和想当然,不断学习和专业化)

JAXP案例

一、需求:一个基本的学生成绩管理系统,用xml文档模拟数据库,要求能够向数据库实时增删改查学生成绩数据


二、需求分析:


1.需要分层定义和实现,在用户层由用户输入查询信息,在底层做好数据接收,异常抛出、处理和程序测试,保证在UI层不会出现未捕获和处理的异常,并能正确处理非法输入和操作信息不存在的情况,给用户一个良好的用户体验。


2.定义学生类封装id,name,scores等数据,定义学生工具类根据查询关键字进行各种学生信息处理,适当利用容器存储多个学生对象,层层抽取简化,在用户层抽取成只是简单接收和调用。


三、文档,各层接口和所用技术


(开发时逐步更新)


四、具体流程实现


1.xml文档和简单学生类:

<?xml version="1.0" encoding="UTF-8" standalone="no"?><exam>
	<student examid="222" idcard="111">
		<name>张三</name>
		<location>沈阳</location>
		<grade>89</grade>
	</student>
	<student examid="444" idcard="333">
		<name>李四</name>
		<location>大连</location>
		<grade>89</grade>
	</student>
</exam>

根据文档设计学生类属性:

package cn.itcast.domain;

public class Student {
	
	private String examid;
	private String idcard;
	private String name;
	private String location;
	private double grade;
	/**
	 * @return the examid
	 */
	String getExamid() {
		return examid;
	}
	/**
	 * @param examid the examid to set
	 */
	void setExamid(String examid) {
		this.examid = examid;
	}
	/**
	 * @return the idcard
	 */
	String getIdcard() {
		return idcard;
	}
	/**
	 * @param idcard the idcard to set
	 */
	void setIdcard(String idcard) {
		this.idcard = idcard;
	}
	/**
	 * @return the name
	 */
	String getName() {
		return name;
	}
	/**
	 * @param name the name to set
	 */
	void setName(String name) {
		this.name = name;
	}
	/**
	 * @return the location
	 */
	String getLocation() {
		return location;
	}
	/**
	 * @param location the location to set
	 */
	void setLocation(String location) {
		this.location = location;
	}
	/**
	 * @return the grade
	 */
	double getGrade() {
		return grade;
	}
	/**
	 * @param grade the grade to set
	 */
	void setGrade(double grade) {
		this.grade = grade;
	}
}

2.学生工具类分析:利用JAXP根据输入的关键字信息实现对xml文档的增删改查,学生数据是一个整体,无论是接收处理还是返回数据都要封装成Student对象,方便于对数据的调用和封装存放(可能删除会稍有不同,直接判断节点属性值删除对应节点即可)


从学生工具类中剥离、抽取出的主要功能:各种处理需求中JAXP对xml的一些统一操作封装成工具类抽取出来,一个特殊的用于信息返回目的的自定义异常类(后面会发觉到)


抽取出操作xml文档的工具类:

public class XmlUtils {
	
	//用xml文档创建Document对象
	public static Document getDocument(String filename) throws Exception{

		DocumentBuilderFactory factory=DocumentBuilderFactory.newInstance();
		
		DocumentBuilder builder=factory.newDocumentBuilder();
		
		return builder.parse(filename);
	}
	
	//将Document对象写回xml文档
	public static void write2XML(Document document,String filename) throws Exception{
		TransformerFactory factory=TransformerFactory.newInstance();
	
		Transformer tf=factory.newTransformer();
		
		tf.transform(new DOMSource(document), new StreamResult(new FileOutputStream(filename)));
	}
	
}

一个特殊的用于信息返回目的的自定义异常类(后面用到):

package cn.itcast.exception;

public class StudentNotExistsException extends Exception {

	public StudentNotExistsException() {
		super();
		// TODO Auto-generated constructor stub
	}

	public StudentNotExistsException(String message, Throwable cause) {
		super(message, cause);
		// TODO Auto-generated constructor stub
	}

	public StudentNotExistsException(String message) {
		super(message);
		// TODO Auto-generated constructor stub
	}

	public StudentNotExistsException(Throwable cause) {
		super(cause);
		// TODO Auto-generated constructor stub
	}

}

学生工具类具体实现:所用技术:JAXP和w3c.dom

package cn.itcast.dao;

import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NodeList;

import cn.itcast.domain.Student;
import cn.itcast.exception.StudentNotExistsException;
import cn.itcast.xmlutils.XmlUtils;

public class StudentDao {

	private String filename = "src/exam.xml";

	public StudentDao(String filename) {
		this.filename = filename;
	}

	/**
	 * @param filename
	 *            the filename to set
	 */
	void setFilename(String filename) {
		this.filename = filename;
	}

	// 增删改查
	public void add(Student s) {
		try {
			// 得到文档对象
			Document document = XmlUtils.getDocument(filename);

			// 从文档对象创建元素节点
			Element student_tag = document.createElement("student");

			// 为节点添加属性
			student_tag.setAttribute("idcard", s.getIdcard());
			student_tag.setAttribute("examid", s.getExamid());

			// 创建学生姓名,所在地,成绩元素
			Element name = document.createElement("name");
			Element location = document.createElement("location");
			Element grade = document.createElement("grade");

			// 设置文本内容
			name.setTextContent(s.getName());
			location.setTextContent(s.getLocation());
			grade.setTextContent(s.getGrade() + "");// 转成字符串

			// 挂载到学生元素
			student_tag.appendChild(name);
			student_tag.appendChild(location);
			student_tag.appendChild(grade);

			// 学生元素挂载到根节点
			document.getElementsByTagName("exam").item(0).appendChild(
					student_tag);

			// 更新内存
			XmlUtils.write2XML(document, filename);
		} catch (Exception e) {
			throw new RuntimeException(e);
		}

	}

	public Student find(String examid) {
		try {
			Document document = XmlUtils.getDocument(filename);

			// 获取学生节点集合
			NodeList list = document.getElementsByTagName("student");

			for (int i = 0; i < list.getLength(); i++) {
				// item方法返回Node类型,是Element的父类,需要强转
				Element student_tag = (Element) list.item(i);

				if (student_tag.getAttribute("examid").equals(examid)) {
					Student s = new Student();
					s.setExamid(examid);
					s.setIdcard(student_tag.getAttribute("idcard"));
					s.setName(student_tag.getElementsByTagName("name").item(0)
							.getTextContent());
					s.setLocation(student_tag.getElementsByTagName("location")
							.item(0).getTextContent());
					s.setGrade(Double.parseDouble(student_tag
							.getElementsByTagName("grade").item(0)
							.getTextContent()));
					return s;
				}
			}
			return null;
		} catch (Exception e) {
			throw new RuntimeException("信息未找到");
		}
	}

	// 注意上面两个都是内部抓取和处理异常,这里抛一个编译时异常,用于返回信息!
	public void delete(String name) throws StudentNotExistsException {
		try {
			Document document = XmlUtils.getDocument(filename);
			NodeList list = document.getElementsByTagName("name");// 直接找这个标签

			for (int i = 0; i < list.getLength(); i++) {
				if (list.item(i).getTextContent().equals(name)) {
					list.item(i).getParentNode().getParentNode().removeChild(
							list.item(i).getParentNode());
					// 找到删除了即更新,返回
					XmlUtils.write2XML(document, filename);
					return;
				}
			}
			throw new StudentNotExistsException(name + "该生不存在");
		} catch (StudentNotExistsException e) {
			// 再次抛出,这个异常不能在这里捕获,一定要以返回信息的目的抛给用户
			throw e;
		} catch (Exception e) {
			throw new RuntimeException(e);// 这里一定用e,持续传递信息,保证异常链不会断!
		}
	}
}

3.JUnit测试单元

结合设置断点Debug模式和直接运行测试,通过,其中删除功能,当删除项不存在时,抛出StudentNotExistsException,属于正常可期情况:

package cn.itcast.test;

import org.junit.Test;

import cn.itcast.dao.StudentDao;
import cn.itcast.domain.Student;
import cn.itcast.exception.StudentNotExistsException;

public class JUnitTest {
	@Test
	public void testAdd() {
		StudentDao dao = new StudentDao();
		Student s = new Student();

		s.setExamid("flsfl");
		s.setIdcard("3950");
		s.setName("Mrd");
		s.setLocation("beijing");
		s.setGrade(99);
		dao.add(s);
	}

	@Test
	public void testFind() {
		StudentDao dao = new StudentDao();
		Student s = dao.find("flsfl");

	}

	@Test
	public void testDelete() throws StudentNotExistsException {
		StudentDao dao = new StudentDao();
		dao.delete("Mrd");
	}
}

4.UI单元

package cn.itcast.ui;


import java.io.BufferedReader;
import java.io.InputStreamReader;

import cn.itcast.dao.StudentDao;
import cn.itcast.domain.Student;
import cn.itcast.exception.StudentNotExistsException;

public class Main {
	public static void main(String[] args) {
		try {
			StudentDao dao = new StudentDao();

			Student s;

			while (true) {
				System.out.print("请输入您的需求:a 添加   b 查询  c 删除");

				BufferedReader bf = new BufferedReader(new InputStreamReader(
						System.in));

				String ss = bf.readLine();

				if ("a".equals(ss)) {
					s = new Student();// 不能不new,那是空指针!

					System.out.print("请输入准考证号:");
					s.setExamid(bf.readLine());

					System.out.print("请输入id号:");
					s.setIdcard(bf.readLine());

					System.out.print("请输入姓名:");
					s.setName(bf.readLine());

					System.out.print("请输入地址:");
					s.setLocation(bf.readLine());

					System.out.print("请输入分数:");
					try {
						s.setGrade(Double.parseDouble(bf.readLine()));
						// 这两句应该放在这里,因为上面发生异常这里就不会再执行,而放在外面无论怎样都执行!!!
						dao.add(s);
						// add方法未发生异常就成功
						System.out.println("添加成功");
					} catch (Exception e) {
						// 这里不能抛,即使是运行时异常,也会被外层捕获,而这里要求输出分数非法信息,顺利结束程序
						System.out.println("分数必须为数值");
					}
				} else if ("b".equals(ss)) {
					System.out.print("请输入准考证号:");
					s = dao.find(bf.readLine());
					if (s == null) {
						System.out.println("未找到相关信息");
					} else {
						System.out.println("准考证号:" + s.getExamid());
						System.out.println("id号:" + s.getIdcard());
						System.out.println("姓名:" + s.getName());
						System.out.println("地址:" + s.getLocation());
						System.out.println("成绩:" + s.getGrade());
					}
				} else if ("c".equals(ss)) {
					try {
						System.out.print("请输入学生姓名:");
						dao.delete(bf.readLine());
						// 没异常就成功
						System.out.println("删除成功");// 这一句确实应该放在这里,因为发生异常就会终止在这一句之前,如果放在外面,则无论怎样都输出这句!
					} catch (StudentNotExistsException e) {
						System.out.println(e.getMessage());// 只要这里捕获处理了异常,这里的程序就顺利结束,不会再输出外层的异常!
					}
				}
			}
		} catch (Exception e) {
			throw new RuntimeException("系统正忙,请稍后登入");
		}
	}
}

5.几组简单测试结果(无限循环输入需求)

add:

请输入您的需求:a 添加   b 查询  c 删除a
请输入准考证号:3400534
请输入id号:64392
请输入姓名:xiaobai
请输入地址:beijing
请输入分数:100
添加成功
请输入您的需求:a 添加   b 查询  c 删除

find:

请输入您的需求:a 添加   b 查询  c 删除b
请输入准考证号:dsfsd
未找到相关信息
请输入您的需求:a 添加   b 查询  c 删除b
请输入准考证号:222
准考证号:222
id号:111
姓名:张三
地址:沈阳
成绩:89.0
请输入您的需求:a 添加   b 查询  c 删除

delete:

请输入您的需求:a 添加   b 查询  c 删除c
请输入学生姓名:sdfsdf
sdfsdf该生不存在
请输入您的需求:a 添加   b 查询  c 删除c
请输入学生姓名:李四
删除成功
请输入您的需求:a 添加   b 查询  c 删除


五、总结(其中更新信息暂时未做)


SAX解析案例

需求:把xml文档中的每本书封装成一个book对象,并把多个对象放到一个List集合中返回

目标xml文档:

<?xml version="1.0" encoding="UTF-8" standalone="no"?><书架>

	<书>
		<书名 name="中科大">javaweb开发</书名>
		<作者>张孝祥</作者>
		<售价>99.00元</售价>
    </书>
	<书>
		<书名>JavaScript网页开发</书名>
		<作者>黎活明</作者>
		<售价>28.00元</售价>
	</书>
	<页面作者 个人爱好="上网" 网站职务="页面作者" 联系信息="aaaa"/><!--实际三个属性,还有一个固定属性和一个默认属性-->
</书架>

分析:1.创建Book类,将节点信息封装为类的属性,提供基本的set,get方法进行读取和写入

package cn.itcast.sax;

public class Book {
	
	private String name;
	private String author;
	private String price;
	/**
	 * @return the name
	 */
	String getName() {
		return name;
	}
	/**
	 * @param name the name to set
	 */
	void setName(String name) {
		this.name = name;
	}
	/**
	 * @return the author
	 */
	String getAuthor() {
		return author;
	}
	/**
	 * @param author the author to set
	 */
	void setAuthor(String author) {
		this.author = author;
	}
	/**
	 * @return the price
	 */
	String getPrice() {
		return price;
	}
	/**
	 * @param price the price to set
	 */
	void setPrice(String price) {
		this.price = price;
	}
	
	
}

2.创建内容处理器封装处理逻辑,并额外提供一个生成List的方法用于返回Book对象数据

class BeanListHandler extends DefaultHandler {
	private List list = new ArrayList();
	private Book book;
	private String currentTag;// 保存当前标签

	// 定义返回list的方法get
	// 不需要提供set
	public List getBooks() {
		return list;
	}

	// 复写几个方法
	public void startElement(String uri, String localName, String qName,
			Attributes attributes) throws SAXException {
		// 获取当前标签
		currentTag = qName;
		// 如果是"书",创建一个新的对象
		if ("书".equals(currentTag)) {// 注意"书"放前面,避免空指针异常
			book = new Book();

		}
	}

	public void characters(char[] ch, int start, int length)
			throws SAXException {
		// 处理内容,如果是"书名","作者","售价"
		if ("书名".equals(currentTag)) {
			book.setName(new String(ch, start, length));
		}
		if ("作者".equals(currentTag)) {
			book.setAuthor(new String(ch, start, length));
		}
		if ("售价".equals(currentTag)) {
			book.setPrice(new String(ch, start, length));
		}
	}

	public void endElement(String uri, String localName, String qName)
			throws SAXException {
		// "书"结束标签
		if ("书".equals(qName)) {
			list.add(book);
			book = null;// 置空
		}
		currentTag = null;// 置空
	}

}

3.创建SAX解析,测试

//Sax解析xml文档
public class SaxDemo {
	@Test
	public void saxcall() throws ParserConfigurationException, SAXException,
			IOException {

		// 工厂
		SAXParserFactory factory = SAXParserFactory.newInstance();
		// 解析器
		SAXParser parser = factory.newSAXParser();
		// 读取器
		XMLReader reader = parser.getXMLReader();

		BeanListHandler handler = new BeanListHandler();

		reader.setContentHandler(handler);// 传人内容处理器
		// 解析文档
		reader.parse("src/book.xml");
		// 返回handler中存储信息的list,使用泛型(Book元素类型)
		List<Book> list = handler.getBooks();
		System.out.println(list);
	}
}

获取指定标签内容--->里面的一个标签间空内容引起的问题----->必须亲自尝试,调试,反复仔细观察,不能自以为是,想当然

内容处理器:

// 获取指定标签内容
class TagValueHandler extends DefaultHandler {

	private String currentTag;
	private String result = "";
	// private List list = new ArrayList();// 这里必须分配空间!
	private int requiredIndex = 3;
	private int count = 0;

	public void startElement(String uri, String localName, String qName,
			Attributes attributes) throws SAXException {
		currentTag = qName;
		// System.out.println(currentTag);
		count++;
	}

	public void characters(char[] ch, int start, int length)
			throws SAXException {
		if (count == requiredIndex) {
			// System.out.println("hello");
			result = new String(ch, start, length);
			// list.add(result);
			// System.out.println(result);
		}
	}

	public void endElement(String uri, String localName, String qName)
			throws SAXException {

	}

	public String getResult() {
		// System.out.println(result);
		return result;
		// return list;
	}
}

测试方法:

@Test
	public void saxcall() throws ParserConfigurationException, SAXException,
			IOException {

		// 工厂
		SAXParserFactory factory = SAXParserFactory.newInstance();
		// 解析器
		SAXParser parser = factory.newSAXParser();
		// 读取器
		XMLReader reader = parser.getXMLReader();

		TagValueHandler had = new TagValueHandler();
		reader.setContentHandler(had);
		reader.parse("src/book.xml");


		System.out.println(had.getResult());

	}

测试结果为空。


用容器保存,附上xml文档,仔细看结果:

xml:

<?xml version="1.0" encoding="UTF-8" standalone="no"?><书架>

	<书>
		<书名 name="中科大">javaweb开发</书名>
		<作者>张孝祥</作者>
		<售价>99.00元</售价>
    </书>
	<书>
		<书名>JavaScript网页开发</书名>
		<作者>黎活明</作者>
		<售价>28.00元</售价>
	</书>
	<页面作者 个人爱好="上网" 网站职务="页面作者" 联系信息="aaaa"/><!--实际三个属性,还有一个固定属性和一个默认属性-->
</书架>


//Sax解析xml文档
public class SaxDemo {
	@Test
	public void saxcall() throws ParserConfigurationException, SAXException,
			IOException {

		// 工厂
		SAXParserFactory factory = SAXParserFactory.newInstance();
		// 解析器
		SAXParser parser = factory.newSAXParser();
		// 读取器
		XMLReader reader = parser.getXMLReader();

		TagValueHandler had = new TagValueHandler();
		reader.setContentHandler(had);
		reader.parse("src/book.xml");


		System.out.println(had.getResult());

	}
}

// 获取指定标签内容
class TagValueHandler extends DefaultHandler {

	private String currentTag;
	private String result = "";
	private List list = new ArrayList();// 这里必须分配空间!
	private int requiredIndex = 3;
	private int count = 0;

	public void startElement(String uri, String localName, String qName,
			Attributes attributes) throws SAXException {
		currentTag = qName;
		// System.out.println(currentTag);
		count++;
	}

	public void characters(char[] ch, int start, int length)
			throws SAXException {
		if (count == requiredIndex) {
			// System.out.println("hello");
			result = new String(ch, start, length);
			list.add(result);
			System.out.println(result);
		}
	}

	public void endElement(String uri, String localName, String qName)
			throws SAXException {

	}

	public List getResult() {
		// System.out.println(result);
		// return result;
		return list;
	}
}

结果:

javaweb开发

        
[javaweb开发,
        ]

注意"javaweb开发"后面还有两个空行被存储,这是因为在解析完"javaweb开发"所在标签后,解析到了标签间的空行,直接执行character方法,而count仍然是上次的值,count==requiredIndex依然成立,所以空行也被赋给result,也是第一次结果为空(串)的原因。


所以在character方法中加入判断:

public void characters(char[] ch, int start, int length)
			throws SAXException {
		if (count == requiredIndex) {
			// System.out.println("hello");
			String s = new String(ch, start, length);
			s = s.trim();
			if (!"".equals(s)) {
				result = new String(ch, start, length);
			}
			// list.add(result);
			// System.out.println(result);
		}
	}

去掉测试用容器,改回返回数据的方法,正确输出结果:javaweb开发------------------->一定要注意标签间空行,尤其是其直接执行character方法,容易利用上一个标签未清空数据的情况!!!一般情况下可以在endElement方法中每次及时清空数据,但不适合于此例的情况。


dom4j解析xml文档:

示例:

public static void test(){
		
        try {
        	SAXReader reader = new SAXReader();
			Document document = reader.read(new File("src/book.xml"));//尽量用File,保证路径非法时的安全
			
			Element root=document.getRootElement();//根节点
			
			//achieve the second book
			//标签内容
			Element book=(Element)root.elements("书").get(1);//强转,标签名
			String value=book.elementText("书名");
			System.out.println(value);
			//标签属性值
			Element bookname=book.element("书名");
			String value1=bookname.attributeValue("name");//标签属性值
			System.out.println(value1);
			
			//add an element to the first book
			Element book0=root.element("书");
			//创建并添加
			book0.addElement("售价").setText("8888元");

			//内存中的Document重写入文本
			//编码问题:在这里默认按gb2312写入utf-8格式的xml文件(默认存储编码和xml声明编码一致)必然乱码
			//所以要用转换流改码表
			//测试发现:即使是gb2312的xml这里用默认的gb2312写也会乱码,用utf-8写则不会乱码,但编码变成utf-8
			//这是因为不管xml文件是什么编码,(其字节码)解析到内存中的Document默认都是utf-8!
			XMLWriter writer = new XMLWriter(new OutputStreamWriter(
		            new FileOutputStream( "src/book.xml" ),"utf-8")
		        );
			//所以上面的处理可以,但一般用另一种处理编码的方式保证修改前后码表一致并且不乱码:
			//格式化输出器(见本博关于中文编码问题的文章)
		    writer.write( document );
		    writer.close();
		    
		    
		} catch (Exception e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}

	}

指定位置增删改查(码表指定按格式化输出器方式):

	public static void test2(){
		
		try {
			SAXReader reader = new SAXReader();
			Document document = reader.read(new File("src/book.xml"));//尽量用File,保证路径非法时的安全
			//achieve the second book
			Element root=document.getRootElement();//根节点
			//第一本书
			Element book=root.element("书");//强转,标签名
			//在指定位置添加
			//得到子标签集合
			List list=book.elements();
			//创建标签
			Element price=DocumentHelper.createElement("售价");
			price.setText("309元");
			//加到集合中去,添加到index=2位置(index从0计)
			list.add(2,price);
			
			//指定格式和编码,重新写入
			OutputFormat format=OutputFormat.createPrettyPrint();
			format.setEncoding("UTF-8");
			
			XMLWriter writer=new XMLWriter(new FileOutputStream("src/book.xml"),format);
			writer.write(document);
			writer.close();
		} catch (Exception e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		
	}


//指定位置删除,修改
	public static void test3(){
		try{
			SAXReader reader = new SAXReader();
			Document document = reader.read(new File("src/book.xml"));//尽量用File,保证路径非法时的安全
			//第一个售价节点
			Element price=document.getRootElement().element("书").element("售价");
			//调用父节点删除子节点
			price.getParent().remove(price);
			
			//更新第二本书的作者
			Element book=(Element)document.getRootElement().elements("书").get(1);
			book.element("作者").setText("或黎明");
			
			OutputFormat format=OutputFormat.createPrettyPrint();
			format.setEncoding("UTF-8");
			
			XMLWriter writer=new XMLWriter(new FileOutputStream("src/book.xml"),format);
			writer.write(document);
			writer.close();
		} catch (Exception e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}

XPath结合dom4j文档定位:

需求:查找文档中指定用户名密码用于用户登录

例子:

(注:和XPath相关用Node,//代表所有,这里SingleNode就是第一个)

用于查找的xml文件:

<?xml version="1.0" encoding="UTF-8"?>
<users>
	<user id="1" username="aaa" password="123" email="[email protected]"></user>
	<user id="2" username="bbb" password="136" email="[email protected]"></user>

</users>

查找代码(dom4j,xpath):

//XPath查找用户名密码用于登录
	public static void test5() throws Exception{
		String username="aaa";
		String password="123";
		
		SAXReader reader = new SAXReader();
		Document document = reader.read(new File("src/cn/itcast/dom4j/NewFile.xml"));
		
		//获取满足XPath条件的单个节点
		//注意表达式中引用变量要加单引号!!!
		//书写表达式技巧:先在双引号中写出所有内容,包括',再用"和+来断开!!!
		Node node=document.selectSingleNode("//user[@username='"+username+"' and @password='"+password+"']");
		if(node==null){
			System.out.println("用户名或密码错误");
		}else{
			System.out.println("登录成功");
		}
	}

结果:

登录成功


你可能感兴趣的:(JAXP,sax与dom4j解析xml文档及XPath在文档内容定位中的应用(持续更新,初学者的野路子、参考和想当然,不断学习和专业化))