XML解析(下)

StAX解析

除了DOM和SAX,還有一種解析XML文檔的模式StAX。StAX的全稱為The Stream API for XML。與SAX類似,StAX也是一邊解析,一邊處理,一邊釋放內存資源。
之所以將StAX與DOM、SAX分開來介紹,最重要的原因是,DOM和SAX都是推(PUSH)模式的解析模式,而StAX是拉(PULL)模式的解析模式(因此一般稱StAX解析為XML Pull解析)。是推模式還是拉模式,取決于在解析的過程中誰處于主導地位,是服務器還是客戶端。在推模式中,解析器自動解析XML文檔而不受用戶干預,但在拉模式中,用戶可以主動控制解析的進行,也就是主動控制事件的處理,主動調用相應的方法。理解了什么是推模式和拉模式后,StAX解析的思想也不難理解:XML文檔傳遞給解析器,通過next()方法觸發(fā)文檔解析事件,用戶可以獲取當前事件 ,也可以調用相應的方法 :

XML Pull解析原理

與SAX不同的是,XML Pull中用int型數(shù)據表示不同的事件:

XML Pull中不同的事件

如果想要查找龍族這本書的價格,代碼為:

@Test
public void demo1() throws Exception {
    XmlPullParserFactory xppFactory = XmlPullParserFactory.newInstance();//構建解析器工廠
    XmlPullParser xpp = xppFactory.newPullParser();//構建解析器
    xpp.setInput(new FileInputStream("books.xml"), "utf-8");//將XML文檔和XML文檔的編碼方式傳遞給解析器
    int event;
    boolean isFound = false;//定義boolean變量用于標記
    while ((event = xpp.getEventType()) != XmlPullParser.END_DOCUMENT) {//判斷當前事件是否為文檔結束事件
        if (event == XmlPullParser.START_TAG && xpp.getName().equals("name")) {//找到name元素
        String bookName = xpp.nextText();//獲取name元素的文本內容
        if (bookName.equals("龍族")) {
            isFound = true;//若這本書為《龍族》,將標記變量isFound置為true
        }
        if (event == XmlPullParser.START_TAG && xpp.getName().equals("price") && isFound == true) {//利用標記變量檢驗price元素是否為所要找的
            System.out.println(xpp.nextText());//輸出《龍族》的價格
            break;//跳出循環(huán)
        }
        xpp.next();// 觸發(fā)下一事件
    }
}

用JUnit進行單體測試:

demo1()運行效果

結果是正確的。
當然還有一種更簡單的方法,先通過name元素找到龍族這本書,再調用next()方法找到price元素,輸出價格。代碼實現(xiàn)相對容易,這里不再演示。
XML Pull解析還可以生成XML文檔,這個過程又稱為序列化。和單純地解析XML文檔相比,序列化的代碼有所不同:

XmlPullParserFactory xppFactory = XmlPullParserFactory.newInstance();//構建解析器工廠
XmlSerializer xs = xppFactory.newSerializer();//構建序列化器
xs.setOutput(new FileOutputStream("books2.xml"), "utf-8");//將需要生成的XML文檔和XML文檔的編碼方式傳遞給序列化器

下面生成一個簡單的XML文檔:

@Test
public void demo2() throws Exception {
    XmlPullParserFactory xppFactory = XmlPullParserFactory.newInstance();
    XmlSerializer xs = xppFactory.newSerializer();
    xs.setOutput(new FileOutputStream("personal_info.xml"), "utf-8");
    xs.startDocument("utf-8", true);//設置XML文檔的編碼格式和standalone屬性
    xs.startTag(null, "personal_info");//為personal_info元素設置元素開始事件
    xs.startTag(null, "name");//為name元素設置元素開始事件
    xs.text("超哥");//為name元素設置文本元素事件
    xs.endTag(null, "name");//為name元素設置元素結束事件
    xs.endTag(null, "personal_info");//為personal_info元素設置元素結束事件
    xs.endDocument();//設置文檔結束事件
}

用JUnit進行單體測試,格式化personal_info.xml文檔后查看:

book2.xml

操作成功。
startTag()endTag()方法中,第一個參數(shù)為名稱空間,一般為0null,建議整個Java程序中統(tǒng)一為0null
在XML Pull解析中有一個非常重要的思想:這種解析模式決定了在解析的過程中是不能對數(shù)據進行修改的,但如果不立即釋放數(shù)據而是將其保存在內存中,那么就能對數(shù)據進行修改了。而對于這些數(shù)據,可以將它們封裝成List集合對象:

XML Pull修改數(shù)據原理

對于文檔books.xml,很顯然數(shù)據是根據書來存放的,那么就可以定一個book類,類中定義nameauthorprice三個屬性并生成GettersSetters方法 :

public class Book{
    private String name;
    private String author;
    private double price;
    public String getName(){
        return name;
    }
    public void setName(String name){
        this.name = name;
    }
    public String getAuthor(){
        return author;
    }
    public void setAuthor(String author){
        this.author = author;
    }
    public double getPrice(){
        return price;
    }
    public void setPrice(double price){
        this.price = price;
    }
}

再將數(shù)據序列化生成XML文檔:

@Test
public void demo3() throws Exception{
    Book book = new Book();
    book.setName("天龍八部");
    book.setAuthor("金庸");
    book.setPrice(108.0);
    XmlPullParserFactory xppFactory = XmlPullParserFactory.newInstance();
    XmlSerializer xs = xppFactory.newSerializer();
    xs.setOutput(new FileOutputStream("books3.xml"), "utf-8");
    xs.startDocument("utf-8", true);
    xs.startTag(null, "books");
    xs.startTag(null, "book");
    xs.startTag(null, "name");
    xs.text(book.getName());
    xs.endTag(null, "name");
    xs.startTag(null, "author");
    xs.text(book.getAuthor());
    xs.endTag(null, "author");
    xs.startTag(null, "price");
    xs.text(String.valueOf(book.getPrice()));
    xs.endTag(null, "price");
    xs.endTag(null, "book");
    xs.endTag(null, "books");
    xs.endDocument();
}

用JUnit進行單體測試,格式化books3.xml文檔后查看:

books3.xml

操作成功。
為了方便解析XML文檔和序列化生成XML文檔,可以定義一個工具類PullMethod,類中定義這兩個方法(當然這個工具類只對book對象生效,可以根據XML文檔的內容對類和方法進行修改):

public class PullMethod{
    public static List<Book> parseXMLtoList(String fileName) throws Exception{//接收XML文檔,將文檔中的數(shù)據封裝成List集合對象
    List<Book> books = new ArrayList<Book>();
    Book book = null;
    XmlPullParserFactory xppFactory = XmlPullParserFactory.newInstance();
    XmlPullParser xpp = xppFactory.newPullParser();
    xpp.setInput(new FileInputStream(fileName), "utf-8");
    int event;
    while ((event = xpp.getEventType()) != XmlPullParser.END_DOCUMENT){
        if (event == XmlPullParser.START_TAG && xpp.getName().equals("book")){
            book = new Book();定義Book類對象book
        }
        if (event == XmlPullParser.START_TAG && xpp.getName().equals("name")){
            book.setName(xpp.nextText());//為book設置name屬性
        }
        if (event == XmlPullParser.START_TAG && xpp.getName().equals("author")){
            book.setAuthor(xpp.nextText());//為book設置author屬性
        }
        if (event == XmlPullParser.START_TAG && xpp.getName().equals("price")){
            book.setPrice(Double.parseDouble(xpp.nextText()));//為book設置price屬性
        }
        if (event == XmlPullParser.END_TAG && xpp.getName().equals("book")){
            books.add(book);//將book添加至集合中
        }
        xpp.next();
     }
     return books;//返回List集合對象
     }
    public static void serializeListtoXML(List<Book> books, String fileName) throws Exception{//接收List集合對象,將集合對象序列化成XML文檔
        XmlPullParserFactory xppFactory = XmlPullParserFactory.newInstance();
        XmlSerializer xs = xppFactory.newSerializer();
        xs.setOutput(new FileOutputStream(fileName), "utf-8");
        xs.startDocument("utf-8", true);
        xs.startTag(null, "books");
        for (Book book : books){//foreach語句遍歷集合
            xs.startTag(null, "book");
            xs.startTag(null, "name");
            xs.text(book.getName());
            xs.endTag(null, "name");
            xs.startTag(null, "author");
            xs.text(book.getAuthor());
            xs.endTag(null, "author");
            xs.startTag(null, "price");
            xs.text(String.valueOf(book.getPrice()));
            xs.endTag(null, "price");
            xs.endTag(null, "book");
        }
        xs.endTag(null, "books");
        xs.endDocument();
    }   
}

可以這樣測試這兩個方法是否正確:

@Test
public void demo4() throws Exception{
    List<Book> books = PullMethod.parseXMLtoList("books.xml");
    PullMethod.serializeListtoXML(books, "books3.xml");
}

原理很簡單,就是將books.xml文檔先解析再生成,如果序列化后的文檔和原文檔完全一樣,則證明工具類中的兩個方法是正確的:

books3.xml

有了工具類的兩個方法,就能夠很方便地對XML文檔中的數(shù)據進行修改了:

@Test
public void demo5() throws Exception{//增加數(shù)據
    List<Book> books = PullMethod.parseXMLtoList("books.xml");
    Book book = new Book();
    book.setName("三國演義");
    book.setAuthor("羅貫中");
    book.setPrice(39.6);
    books.add(book);
    PullMethod.serializeListtoXML(books, "books3.xml");
}
book3.xml
@Test
public void demo6() throws Exception{//修改數(shù)據
    List<Book> books = PullMethod.parseXMLtoList("books.xml");
    for(Book book:books){
        if(book.getName().equals("三體")){
            book.setPrice(book.getPrice() * 0.5);
        }
    }
    PullMethod.serializeListtoXML(books, "books3.xml");
}
books3.xml
@Test
public void demo7() throws Exception{//刪除數(shù)據
    List<Book> books = PullMethod.parseXMLtoList("books.xml");
    for(Book book:books){
        if(book.getName().equals("三體")){
            books.remove(book);//從集合中刪除book
            break;
        }
    }
    PullMethod.serializeListtoXML(books, "books3.xml");
}
books3.xml

學習提示

這就是XML的三種解析模式,無論是DOM、SAX還是StAX,實際上都不難掌握,關鍵是要記住各自的思想和解析時調用的方法。在學習的過程中一定要善于查閱API文檔,這樣才能更好地理解與運用它們。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 227,797評論 6 531
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 98,179評論 3 414
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 175,628評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,642評論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,444評論 6 405
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 54,948評論 1 321
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,040評論 3 440
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,185評論 0 287
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經...
    沈念sama閱讀 48,717評論 1 333
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,602評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,794評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,316評論 5 358
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 44,045評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,418評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,671評論 1 281
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,414評論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,750評論 2 370

推薦閱讀更多精彩內容