JAXP,sax与dom4j解析xml文档(持续更新,菜鸟的野路子、参考和想当然,不断学习和专业化)
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方法中每次及时清空数据,但不适合于此例的情况。