一、前言
SAX操作xml是基於事件來完成的,自己只負責調用解析的方法,然後具體解析操作都是交給DefaultHandler處理者來完成的,總的來說使用SAX解析和生成xml文檔還是比較方便的 。
二、準備條件
因爲SAX是jdk自帶的解析方式,所以不用添加jar包引用。
三、使用SAX實戰
1、解析xml文檔
實現思路:
<1>先由SAXParserFactory這個工廠的實例生產一個SAXParser解析器;
<2>然後根據讀取的xml路徑,傳遞給SAXParser這個解析器,再調用parse()方法;
<3>在parse()方法中需要傳遞DefaultHandler這個類的擴展類的實例,因爲它纔會真正去一步步去解析xml文檔的;
<4>在DefaultHandler擴展類中需要重寫startDocument(),endDocument()等等方法,因爲他們方法內部有返回的具體文檔的結果。
具體代碼如下:
- import java.io.BufferedOutputStream;
- import java.io.File;
- import java.io.FileInputStream;
- import java.io.FileNotFoundException;
- import java.io.FileOutputStream;
- import java.io.IOException;
- import java.io.InputStream;
- import java.util.ArrayList;
- import java.util.List;
- import javax.xml.parsers.ParserConfigurationException;
- import javax.xml.parsers.SAXParser;
- import javax.xml.parsers.SAXParserFactory;
- import javax.xml.transform.OutputKeys;
- import javax.xml.transform.Result;
- import javax.xml.transform.Transformer;
- import javax.xml.transform.TransformerConfigurationException;
- import javax.xml.transform.sax.SAXTransformerFactory;
- import javax.xml.transform.sax.TransformerHandler;
- import javax.xml.transform.stream.StreamResult;
- import org.xml.sax.Attributes;
- import org.xml.sax.InputSource;
- import org.xml.sax.SAXException;
- import org.xml.sax.helpers.AttributesImpl;
- import org.xml.sax.helpers.DefaultHandler;
- /**
- * 使用SAX操作xml的簡單例子
- * @author Administrator
- *
- */
- public class SAXOperateXmlDemo {
- public void parseXml01(){
- String xmlPath = "D:\\project\\dynamicWeb\\src\\resource\\user01.xml";
- String xmlName = xmlPath.substring(xmlPath.lastIndexOf("\\"));
- try {
- //獲取SAX分析器的工廠實例,專門負責創建SAXParser分析器
- SAXParserFactory saxParserFactory = SAXParserFactory.newInstance();
- //獲取SAXParser分析器的實例
- SAXParser saxParser = saxParserFactory.newSAXParser();
- //和其他解析方式一樣,也要間接通過InputStream輸入流對象獲取xml信息
- //1、直接指定絕對路徑獲取文件輸入流對象
- //InputStream inputStream = new FileInputStream(xmlPath);
- //2、使用類的相對路徑查找xml路徑
- //InputStream inputStream = this.getClass().getResourceAsStream(xmlName);
- //3、也可以指定路徑完成InputStream輸入流的實例化操作
- InputStream inputStream = new FileInputStream(new File(xmlPath));
- //4、使用InputSource輸入源作爲參數也可以轉換xml
- //InputSource inputSource = new InputSource(inputStream);
- //解析xml文檔
- saxParser.parse(inputStream, new XmlSAXHandler01());//這裏傳遞了自定義的XmlSAXHandler()管理者參數來解析xml,不像以前都是直接調用返回的Document對象
- } catch (ParserConfigurationException e) {
- e.printStackTrace();
- } catch (SAXException e) {
- e.printStackTrace();
- } catch (FileNotFoundException e) {
- e.printStackTrace();
- } catch (IOException e) {
- e.printStackTrace();
- }
- }
- public static void main(String[] args) {
- SAXOperateXmlDemo demo = new SAXOperateXmlDemo();
- demo.parseXml01();
- }
- }
- /**
- * 解析SAX的處理者01
- * @author Administrator
- *
- */
- class XmlSAXHandler01 extends DefaultHandler {
- @Override
- public void startDocument() throws SAXException {
- System.out.println("---->startDocument() is invoked...");
- super.startDocument();
- }
- @Override
- public void endDocument() throws SAXException {
- System.out.println("---->endDocument() is invoked...");
- super.endDocument();
- }
- @Override
- public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException {
- System.out.println("-------->startElement() is invoked...");
- super.startElement(uri, localName, qName, attributes);
- }
- @Override
- public void endElement(String uri, String localName, String qName) throws SAXException {
- System.out.println("-------->endElement() is invoked...");
- super.endElement(uri, localName, qName);
- }
- @Override
- public void characters(char[] ch, int start, int length) throws SAXException {
- System.out.println("------------>characters() is invoked...");
- super.characters(ch, start, length);
- }
- }
上面代碼簡單解析了一個xml,user01.xml文件的內容如下:
- <?xml version="1.0" encoding="utf-8" ?>
- <Root>Content</Root>
根據控制檯的顯示可知:
<1>解析類必須繼承DefaultHandler這個類,重寫自己需要獲取節點信息的方法,不重寫的情況下會調用父類的對應方法,所以不影響程序;
<2>XmlSAXHandler01這個處理者來完成xml的解析工作,並且調用方式是按照xml層級關係來處理的,比如最開始調用startDocument()獲取Document對象,然後再遞歸調用startElement()獲取根節點以及子節點的信息,其中的characters()用於獲取節點文本內容信息,待節點解析完畢之後會調用endElement(),同樣整個xml解析完畢之後會調用endDocument()結束。
上面只是簡單的獲取了xml的根目錄的元素,接下來使用DefaultHandler這個處理者怎麼獲取節點內的信息。
具體代碼如下:
- public void parseXml02(){
- String xmlPath = "D:\\project\\dynamicWeb\\src\\resource\\user02.xml";
- try {
- //獲取SAX分析器的工廠實例,專門負責創建SAXParser分析器
- SAXParserFactory saxParserFactory = SAXParserFactory.newInstance();
- //獲取SAXParser分析器的實例
- SAXParser saxParser = saxParserFactory.newSAXParser();
- InputStream inputStream = new FileInputStream(new File(xmlPath));
- //解析xml文檔
- saxParser.parse(inputStream, new XmlSAXHandler02());
- } catch (ParserConfigurationException e) {
- e.printStackTrace();
- } catch (SAXException e) {
- e.printStackTrace();
- } catch (FileNotFoundException e) {
- e.printStackTrace();
- } catch (IOException e) {
- e.printStackTrace();
- }
- }
- /**
- * 解析SAX的處理者02
- * @author Administrator
- *
- */
- class XmlSAXHandler02 extends DefaultHandler {
- @Override
- public void startDocument() throws SAXException {
- System.out.println("---->startDocument() is invoked...");
- }
- @Override
- public void endDocument() throws SAXException {
- System.out.println("---->endDocument() is invoked...");
- }
- @Override
- public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException {
- System.out.println("-------->startElement() is invoked...");
- System.out.println("uri的屬性值:" + uri);
- System.out.println("localName的屬性值:" + localName);
- System.out.println("qName的屬性值:" + qName);
- if(attributes.getLength()>0){
- System.out.println("element屬性值-->" + attributes.getQName(0) + ":" + attributes.getValue(0)); //根據下標獲取屬性name和value,也可以直接傳遞屬性name獲取屬性值:attributes.getValue("id")
- }
- }
- @Override
- public void endElement(String uri, String localName, String qName) throws SAXException {
- System.out.println("-------->endElement() is invoked...");
- System.out.println("uri的屬性值:" + uri);
- System.out.println("localName的屬性值:" + localName);
- System.out.println("qName的屬性值:" + qName);
- }
- @Override
- public void characters(char[] ch, int start, int length) throws SAXException {
- System.out.println("------------>characters() is invoked...");
- System.out.println("節點元素文本內容:" + new String(ch, start, length));
- }
- }
- <?xml version="1.0" encoding="utf-8" ?>
- <Root>
- 這個是根節點的內容
- <user id="001">用戶信息</user>
- </Root>
根據控制檯的顯示可知:
<1>XMLSAXHandler02在解析的時候執行方法是從最外層往內、從上往下依次解析的,並且每一次解析節點都是startElement()和endElement()成對出現的;
<2>圖中顯示了每一個節點解析的信息,並且startElement()和endElement()的區別在於前面方法有攜帶屬性,而後面方法沒有;
<3>圖中之所以出現三個“節點元素文本內容”是XMLSAXHandler02也是把非標籤的文本當前一個節點了,所以在解析的時候要排除這種情況,以免影響最終想要的結果。
另外發現:
<1>查看父類的方法發現它們的方法體什麼都沒做;
<2>SAX不支持重新修改XML的結構;
<3>如果正常業務需求,解析xml之後不可能只是簡單輸出下內容,而是要返回成一個集合或者其他形式返回,目前情況可以使用全局的ArrayList集合來完成解析之後節點內容的封裝。
接下來需要實現如何封裝SAX解析完畢的XML文檔,都知道java是面向對象編程的,那麼這個時候可以把文檔中的每一個節點都看成一個對象,包括節點裏面的屬性也是一樣,那麼在解析XML的時候直接使用javabean封裝一下,思路就非常清晰了,但是現在還有還一個問題: 如何在SAXParser調用parse()方法之後返回最終的結果集呢?就目前肯定不行的,其一方法沒有返回值,其二解析操作完全交給DefaultHandler去做了,所以這種情況下肯定不能使用普通變量或者全局變量,因爲使用了之後會隨着當前操作類的實例化生命週期而存在,並且DefaultHandler在調用的時候又需要產生一個新的實例,這樣前後就沒有關聯性了。 所以只能使用靜態ArrayList來完成了。
具體操作如下:
1、前面說了構建節點對象和屬性對象,具體代碼如下:
- import java.util.List;
- /**
- * Xml節點對象
- * @author Administrator
- *
- */
- public class Node {
- private Long id;
- private String name;
- private String text;
- private List<Attribute> attributeList;
- private List<Node> nodeList;
- public Long getId() {
- return id;
- }
- public void setId(Long id) {
- this.id = id;
- }
- public String getName() {
- return name;
- }
- public void setName(String name) {
- this.name = name;
- }
- public String getText() {
- return text;
- }
- public void setText(String text) {
- this.text = text;
- }
- public List<Attribute> getAttributeList() {
- return attributeList;
- }
- public void setAttributeList(List<Attribute> attributeList) {
- this.attributeList = attributeList;
- }
- public List<Node> getNodeList() {
- return nodeList;
- }
- public void setNodeList(List<Node> nodeList) {
- this.nodeList = nodeList;
- }
- }
- /**
- * Xml屬性對象
- * @author Administrator
- *
- */
- public class Attribute {
- private String name;
- private String value;
- public String getName() {
- return name;
- }
- public void setName(String name) {
- this.name = name;
- }
- public String getValue() {
- return value;
- }
- public void setValue(String value) {
- this.value = value;
- }
- }
- public static List<Node> nodeList = null;
- public static Node node = null;
3、定義xml文檔解析方法,具體如下:
- public void parseXml03(){
- String xmlPath = "D:\\project\\dynamicWeb\\src\\resource\\user03.xml";
- try {
- //獲取SAX分析器的工廠實例,專門負責創建SAXParser分析器
- SAXParserFactory saxParserFactory = SAXParserFactory.newInstance();
- //獲取SAXParser分析器的實例
- SAXParser saxParser = saxParserFactory.newSAXParser();
- InputStream inputStream = new FileInputStream(new File(xmlPath));
- //解析xml文檔
- saxParser.parse(inputStream, new XmlSAXHandler03());
- //迭代list
- if(SAXOperateXmlDemo.nodeList.size() > 0){
- for (Node node : SAXOperateXmlDemo.nodeList) {
- System.out.println("-----------------------------------------");
- System.out.println("【節點】" + node.getName() + ":" + node.getText());
- List<Attribute> attributeList = node.getAttributeList();
- if (attributeList != null) {
- for (Attribute attribute : attributeList) {
- System.out.println("【屬性】" + attribute.getName() + ":" + attribute.getValue());
- }
- }
- }
- }
- } catch (ParserConfigurationException e) {
- e.printStackTrace();
- } catch (SAXException e) {
- e.printStackTrace();
- } catch (FileNotFoundException e) {
- e.printStackTrace();
- } catch (IOException e) {
- e.printStackTrace();
- }
- }
4、對應的解析處理者代碼如下:
- /**
- * 解析SAX的處理者03
- * @author Administrator
- *
- */
- class XmlSAXHandler03 extends DefaultHandler {
- @Override
- public void startDocument() throws SAXException {
- SAXOperateXmlDemo.nodeList = new ArrayList<Node>();
- }
- @Override
- public void endDocument() throws SAXException {
- }
- @Override
- public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException {
- SAXOperateXmlDemo.node = new Node();
- SAXOperateXmlDemo.node.setId(null);
- SAXOperateXmlDemo.node.setName(qName);
- //封裝當前節點的屬性
- List<Attribute> attributeList = new ArrayList<Attribute>();
- if(attributes.getLength()>0){
- for (int i = 0; i < attributes.getLength(); i++) {
- Attribute attribute = new Attribute();
- attribute.setName(attributes.getQName(i));
- attribute.setValue(attributes.getValue(i));
- attributeList.add(attribute);
- }
- }
- SAXOperateXmlDemo.node.setAttributeList(attributeList);
- }
- @Override
- public void endElement(String uri, String localName, String qName) throws SAXException {
- }
- @Override
- public void characters(char[] ch, int start, int length) throws SAXException {
- if(SAXOperateXmlDemo.node != null){
- SAXOperateXmlDemo.node.setText(new String(ch, start, length));
- //因爲startElement()在characters()的前面調用,所以必須放在後面才能把文本添加進去
- SAXOperateXmlDemo.nodeList.add(SAXOperateXmlDemo.node);
- SAXOperateXmlDemo.node = null;
- //在這裏之所以重新置爲null是在解析標籤的時候節點與節點之間的回車符、Tab符或者空格以及普通文本等等這些字符串也算一個節點
- //如果不這樣,那麼解析的時候會把之前添加成功的節點內的文本給替換掉。
- }
- }
- }
5、代碼中解析的user03.xml的結構如下:
- <?xml version="1.0" encoding="utf-8" ?>
- <Root>
- <user id="001">UserInfo_1</user>
- <user id="002">UserInfo_2</user>
- </Root>
接下來執行該解析xml的方法,控制檯顯示效果如下:
根據控制檯的顯示可知:
<1>使用全局靜態變量完成完成了對Xml解析之後的封裝工作,並且在輸出的時候沒有問題,但是需要注意的是去掉空文本節點這種特殊情況,否則會出現獲取的節點內的內容爲"\n\t" 等等結果;<2>雖然功能是完成了,但是如果Xml文檔中錄入的不是文本,而是添加的詳細的子節點呢?這樣每一個節點就是一個Node對象,在查詢和使用的時候非常的不方便。
所以爲了避免這種情況,作出如下改動:
因爲需求只需要獲取User信息,那麼不用每一個解析的節點都封裝成一個對象,並且屬性對象和節點對象可以合併,不用分太開這樣不易於後期維護。
具體操作如下:
假設現在需要解析的xml文檔如下:
- <?xml version="1.0" encoding="utf-8" ?>
- <Users>
- <user id="9527">
- <name>admin</name>
- <age>40</age>
- <hobby>manage someone!</hobby>
- </user>
- <user id="9632">
- <name>chenghui</name>
- <age>110</age>
- <hobby>code something!</hobby>
- </user>
- </Users>
- /**
- * xml節點對象
- * @author Administrator
- *
- */
- public class User {
- private Long id;
- private String name;
- private Long age;
- private String hobby;
- public Long getId() {
- return id;
- }
- public void setId(Long id) {
- this.id = id;
- }
- public String getName() {
- return name;
- }
- public void setName(String name) {
- this.name = name;
- }
- public Long getAge() {
- return age;
- }
- public void setAge(Long age) {
- this.age = age;
- }
- public String getHobby() {
- return hobby;
- }
- public void setHobby(String hobby) {
- this.hobby = hobby;
- }
- }
- public void parseXml04(){
- String xmlPath = "D:\\project\\dynamicWeb\\src\\resource\\user04.xml";
- try {
- //獲取SAX分析器的工廠實例,專門負責創建SAXParser分析器
- SAXParserFactory saxParserFactory = SAXParserFactory.newInstance();
- //獲取SAXParser分析器的實例
- SAXParser saxParser = saxParserFactory.newSAXParser();
- InputStream inputStream = new FileInputStream(new File(xmlPath));
- //解析xml文檔
- saxParser.parse(inputStream, new XmlSAXHandler04());
- //迭代list
- if(SAXOperateXmlDemo.userList.size() > 0){
- for (User user : SAXOperateXmlDemo.userList) {
- System.out.println("-----------------------------------------");
- System.out.println("【Id】" + user.getId());
- System.out.println("【姓名】" + user.getName());
- System.out.println("【年齡】" + user.getAge());
- System.out.println("【愛好】" + user.getHobby());
- }
- }
- } catch (ParserConfigurationException e) {
- e.printStackTrace();
- } catch (SAXException e) {
- e.printStackTrace();
- } catch (FileNotFoundException e) {
- e.printStackTrace();
- } catch (IOException e) {
- e.printStackTrace();
- }
- }
並且當前解析類需要添加兩個全局靜態變量,具體如下:
- public static List<User> userList = null;
- public static User user = null;
對應的解析xml處理者代碼如下:
- /**
- * 解析SAX的處理者04
- * @author Administrator
- *
- */
- class XmlSAXHandler04 extends DefaultHandler {
- private String currentQName; //因爲startElement()才能獲取到標籤名稱,但是標籤的內容在characters()獲取,而且他們的執行順序一直是順序的,所以可以使用currentQName來過渡一下獲取上一次的標籤名
- @Override
- public void startDocument() throws SAXException {
- SAXOperateXmlDemo.userList = new ArrayList<User>();
- }
- @Override
- public void endDocument() throws SAXException {
- }
- @Override
- public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException {
- if(qName.equals("user")){
- SAXOperateXmlDemo.user = new User(); //每次解析到user標籤了纔會創建user對象的實例
- //添加user標籤中的id屬性
- if(attributes.getLength() > 0){
- SAXOperateXmlDemo.user.setId(Long.valueOf(attributes.getValue("id")));
- }
- }
- this.currentQName = qName;
- }
- @Override
- public void endElement(String uri, String localName, String qName) throws SAXException {
- //需要說明的是,因爲每一個非空標籤都有characters(),那麼無法知道user子標籤循環完了
- //但是可以這樣,如果不考慮子標籤順序可以判斷是否解析到了最後一個子標籤來判斷
- //或者直接在user標籤的endElement()中添加即可。
- if(qName.equals("user")){
- SAXOperateXmlDemo.userList.add(SAXOperateXmlDemo.user);
- SAXOperateXmlDemo.user = null;
- }
- this.currentQName = null;
- }
- @Override
- public void characters(char[] ch, int start, int length) throws SAXException {
- String content = new String(ch, start, length);
- //System.out.println(currentQName + ":" + content);
- if(SAXOperateXmlDemo.user != null && currentQName != null){
- if(currentQName.equals("name")){
- SAXOperateXmlDemo.user.setName(content);
- }else if(currentQName.equals("age")){
- SAXOperateXmlDemo.user.setAge(Long.valueOf(content));
- }else if(currentQName.equals("hobby")){
- SAXOperateXmlDemo.user.setHobby(content);
- }
- }
- }
- }
好了,現在滿足需求了 解析自己需要的節點然後封裝成集合展示出來。
2、生成xml文檔
SAX能夠解析xml,同樣肯定能生成xml,而且使用起來也不是很複雜。
實現思路:
<1>創建保存xml的結果流對象StreamResult;
<2>然後利用SAXTransformerFactory這個工廠創建TransformerHandler這個操作者;
<3>操作這個TransformerHandler獲取Transformer,利用Transformer創建節點信息;
具體代碼如下:- public void buildXml01(){
- try {
- //創建保存xml的結果流對象
- Result reultXml = new StreamResult(new FileOutputStream(new File("c:\\user.xml")));
- //獲取sax生產工廠對象實例
- SAXTransformerFactory saxTransformerFactory = (SAXTransformerFactory) SAXTransformerFactory.newInstance();
- //獲取sax生產處理者對象實例
- TransformerHandler transformerHandle = saxTransformerFactory.newTransformerHandler();
- transformerHandle.setResult(reultXml);
- //獲取sax生產器
- Transformer transformer = transformerHandle.getTransformer();
- //transformer.setOutputProperty(OutputKeys.ENCODING,"UTF-8");//xml的編碼格式
- transformer.setOutputProperty(OutputKeys.INDENT,"yes");//換行
- //開始封裝document文檔對象,這裏和解析一樣都是成雙成對的構造標籤
- transformerHandle.startDocument();
- AttributesImpl attrImple = new AttributesImpl();
- transformerHandle.startElement("", "", "Users",attrImple);
- attrImple.addAttribute("", "", "id", "string", "123");
- transformerHandle.startElement("", "", "user", attrImple);
- transformerHandle.characters("這個是用戶的信息".toCharArray(), 0, "這個是用戶的信息".length());
- transformerHandle.endElement("", "", "user");
- transformerHandle.endElement("", "", "Users");
- //因爲沒有appendChild等等添加子元素的方法,sax提供的是構造在startElement()和endElement()區間內的標籤爲包含的節點的父節點
- transformerHandle.endDocument();
- System.out.println("xml文檔生成成功!");
- } catch (FileNotFoundException e) {
- e.printStackTrace();
- } catch (TransformerConfigurationException e) {
- e.printStackTrace();
- } catch (SAXException e) {
- e.printStackTrace();
- }
- }
然後看看生成的XML,結構如下:
結果顯示達到了期望,但是有一個問題:
如果使用transformer.setOutputProperty(OutputKeys.ENCODING,"UTF-8"); 重新指定了編碼,插入的中文會變成亂碼,現在沒有想到解決方案。。
但是如果不指定編碼 卻沒有問題,顯示結果是上圖中的默認的UTF-8。
原文轉載自:http://blog.csdn.net/chenghui0317/article/details/11990891