1. 程式人生 > >自定義xml解析框架

自定義xml解析框架

標籤:

我們在工作中,經常會從伺服器獲取資料並進行解析,伺服器返回的資料有兩種:json和xml。json我們可以用gson或者fastjson等優秀的開源框架去進行解析,省去不少麻煩,通常我們只需要把bean設計出來,然後呼叫一句話就可以解析成功了。而解析xml資料,我們得去一個標籤一個標籤的去比較,修改起來也非常麻煩,比如我們現在不需要某一個標籤上的資料了,我們還得去把程式碼中那一行給刪除,如果我們的需求又變了,現在又需要加上某個標籤的資料,那隻能再到程式碼中把相應程式碼加上,這樣非常麻煩,這篇文章我們將介紹如何設計我們自己的xml解析框架,做到可以和gson一樣,只需要關心bean的結構,呼叫一句程式碼就可以解析成功。

首先我們先來分析一下伺服器返回回來的xml資料的格式,然後進行設計。我的資料是從聚合資料上面得到的。

<?xml version="1.0" encoding="utf-8"?>
<root>
    <reason>查詢成功</reason>
    <result>
        <title>NBA2014-2015賽季_火箭隊賽程賽果</title>
        <list>
            <item>
                <c1></c1
> <c2>10-25</c2> <c3>08:00</c3> <c4R>87-96</c4R> <c4T1>馬刺</c4T1> <c4T1URL>http://nba.sports.sina.com.cn/team.php?id=24</c4T1URL> <c4T2>火箭</c4T2>
<c4T2URL>http://nba.sports.sina.com.cn/team.php?id=10</c4T2URL> <c51></c51> <c52>視訊集錦</c52> <c52Link>http://video.sina.com.cn/z/sports/k/nba/141025sashou/</c52Link> <c53>資料統計</c53> <c53Link>http://nba.sports.sina.com.cn/look_scores.php?id=2014102410</c53Link> <c54>新聞報道</c54> <c54Link>http://sports.sina.com.cn/nba/2014-10-25/10267382993.shtml</c54Link> </item> <item> <c1></c1> <c2>10-29</c2> <c3>10:30</c3> <c4R>108-90</c4R> <c4T1>火箭</c4T1> <c4T1URL>http://nba.sports.sina.com.cn/team.php?id=10</c4T1URL> <c4T2>湖人</c4T2> <c4T2URL>http://nba.sports.sina.com.cn/team.php?id=13</c4T2URL> <c51></c51> <c52>視訊集錦</c52> <c52Link>http://video.sina.com.cn/z/sports/k/nba/141029houlal/</c52Link> <c53>資料統計</c53> <c53Link>http://nba.sports.sina.com.cn/look_scores.php?id=2014102813</c53Link> <c54>新聞報道</c54> <c54Link>http://sports.sina.com.cn/nba/2014-10-29/13367388047.shtml</c54Link> </item> <item> <c1></c1> <c2>10-30</c2> <c3>09:00</c3> <c4R>104-93</c4R> <c4T1>火箭</c4T1> <c4T1URL>http://nba.sports.sina.com.cn/team.php?id=10</c4T1URL> <c4T2>爵士</c4T2> <c4T2URL>http://nba.sports.sina.com.cn/team.php?id=26</c4T2URL> <c51></c51> <c52>視訊集錦</c52> <c52Link>http://video.sina.com.cn/z/sports/k/nba/141030houuta/</c52Link> <c53>資料統計</c53> <c53Link>http://nba.sports.sina.com.cn/look_scores.php?id=2014102926</c53Link> <c54>新聞報道</c54> <c54Link>http://sports.sina.com.cn/nba/2014-10-30/11267389310.shtml</c54Link> </item> <item> <c1></c1> <c2>11-02</c2> <c3>08:00</c3> <c4R>VS</c4R> <c4T1>凱爾特人</c4T1> <c4T1URL>http://nba.sports.sina.com.cn/team.php?id=2</c4T1URL> <c4T2>火箭</c4T2> <c4T2URL>http://nba.sports.sina.com.cn/team.php?id=10</c4T2URL> <c51></c51> <c52>文字直播</c52> <c52Link>http://sports.sina.com.cn/nba/live.html?id=2014110110</c52Link> <c53></c53> <c53Link></c53Link> <c54></c54> <c54Link></c54Link> </item> <item> <c1></c1> <c2>11-04</c2> <c3>08:00</c3> <c4R>VS</c4R> <c4T1>火箭</c4T1> <c4T1URL>http://nba.sports.sina.com.cn/team.php?id=10</c4T1URL> <c4T2>76人</c4T2> <c4T2URL>http://nba.sports.sina.com.cn/team.php?id=20</c4T2URL> <c51></c51> <c52>文字直播</c52> <c52Link>http://sports.sina.com.cn/nba/live.html?id=2014110320</c52Link> <c53></c53> <c53Link></c53Link> <c54></c54> <c54Link></c54Link> </item> <item> <c1></c1> <c2>11-05</c2> <c3>08:30</c3> <c4R>VS</c4R> <c4T1>火箭</c4T1> <c4T1URL>http://nba.sports.sina.com.cn/team.php?id=10</c4T1URL> <c4T2>熱火</c4T2> <c4T2URL>http://nba.sports.sina.com.cn/team.php?id=14</c4T2URL> <c51></c51> <c52>文字直播</c52> <c52Link>http://sports.sina.com.cn/nba/live.html?id=2014110414</c52Link> <c53></c53> <c53Link></c53Link> <c54></c54> <c54Link></c54Link> </item> </list> <more1> <link>http://nba.sports.sina.com.cn/team_match.php?id=10</link> <txt>火箭隊完整賽程</txt> </more1> <more2> <link>http://nba.sports.sina.com.cn/showtv.php</link> <txt>電視轉播表</txt> </more2> </result> <error_code>0</error_code> </root>
1.我們使用xml的pull解析方法,解析的過程是從第一個開始標籤順序往下直到最後一個結束標籤。先確定我們需要哪些標籤上的資料。reason和title上面有資料,我們想要得到。然後就是一堆item,每個item裡面都有一些資料,我們想要得到。 2.分析結構:item一看就知道是要儲存在一個list裡面的,每個item裡面的標籤也都是一樣的,只是資料不一樣而已,所以我們要設計一個ListBean,用來儲存item裡面的資料,然後放到一個ArrayList裡面。這樣可以將所有的item都儲存起來。
public class ListBean {
    public String c1;
    public String c2;
    public String c3;
    public String c4R;
    public String c4T1;
    public String c4T1URL;
    public String c4T2;
    public String c4T2URL;
    public String c51;
    public String c52;
    public String c52Link;
    public String c53;
    public String c53Link;
    public String c54;
    public String c54Link;
    @Override
    public String toString() {
        return "ListBean [c1=" + c1 + ", c2=" + c2 + ", c3=" + c3 + ", c4R="
                + c4R + ", c4T1=" + c4T1 + ", c4T1URL=" + c4T1URL + ", c4T2="
                + c4T2 + ", c4T2URL=" + c4T2URL + ", c51=" + c51 + ", c52="
                + c52 + ", c52Link=" + c52Link + ", c53=" + c53 + ", c53Link="
                + c53Link + ", c54=" + c54 + ", c54Link=" + c54Link + "]";
    }
    
}

再看reason和title,這兩個標籤也應該儲存在一個bean中,我們在設計一個Bean

public class Bean {
    public String reason;
    public String title;
    @Override
    public String toString() {
        return "Bean [reason=" + reason + ", title=" + title + "]";
    }
    
}
整個xml資料的結構為一個bean和一個list的結構:bean:reason和title    list:所有的item 3.我們再設計一個最終結果的實體類,一個bean和一個list
public class ResultBeanAndList<T> {
    public Object bean;
    
    public ArrayList<T> list;
    
    
    public Object getBean() {
        return bean;
    }
    public void setBean(Object bean) {
        this.bean = bean;
    }
    public ArrayList<T> getList() {
        return list;
    }
    public void setList(ArrayList<T> list) {
        this.list = list;
    }
    @Override
    public String toString() {
        return "ResultBeanAndList [bean=" + bean + ", list=" + list + "]";
    }
    
    
}
其中的T是一個泛型,裡面傳入的是item的實體類,即這個例子中的ListBean 4.在解析的過程中是從第一個標籤順序向後解析的,我們必須得讓parser知道:什麼時候是外層的Bean,什麼時候是內層的ListBean,什麼時候將ListBean加入到list中。其實很簡單,當parser解析到<root>的時候,我們就讓Bean例項化出來,當解析到</root>的時候,就將Bean儲存到ResultBeanAndList中。當parser解析到<item>的時候,我們將ListBean例項化出來,當解析到</list>的時候,將ListBean加入到ResultBeanAndList中。
public class XmlUtils {
    /**
     * 解析一個bean和一個list的xml檔案結構的方法
     * @param parser 解析者
     * @param listRoot 內層ListBean需要例項化物件的一個標識
     * @param listClazz ListBean.class
     * @param beanRoot 外層Bean需要例項化物件的一個標識
     * @param beanClazz Bean.class
     * @return 一個bean和一個list的結果
     * @throws Exception
     */
    public static <T,T1> ResultBeanAndList<T> getBeanByParseXml(XmlPullParser parser , String listRoot , Class<T> listClazz ,String beanRoot , Class<T1> beanClazz) throws Exception{
        //最後結果
        ResultBeanAndList<T> result = null;
        //list  存放一堆item
        ArrayList<T> list = null;
        //內層ListBean
        T t = null;
        //外層Bean
        T1 bean = null;
        //一個計數器
        int count = 0 ;
        try {
            //獲得當前標籤型別
            int eventType = parser.getEventType();
            //如果不是xml檔案結束標籤,則一個一個向下解析
            while(eventType != XmlPullParser.END_DOCUMENT){
                switch (eventType) {
                //如果是xml檔案開始標籤,則初始化一些資料
                case XmlPullParser.START_DOCUMENT:
                    //最後的結果
                    result = new ResultBeanAndList<T>();
                    //list
                    list = new ArrayList<T>();
                    //將list加入到result中,當前list是空的,等後面加入了資料後,就不是空了
                    result.setList(list);
                    break;
                //開始標籤
                case XmlPullParser.START_TAG:
                    //獲得標籤的名字
                    String tagName = parser.getName();
                    //如果內層的ListBean已經例項化出來的話
                    if (t != null) {
                        try {
                            //判斷當前標籤在沒在ListBean的屬性中
                            Field field = listClazz.getField(tagName);
                            //如果ListBean中有當前標籤
                            if (field != null) {
                                //計數器+1
                                count++;
                                //將取出來的值賦給ListBean中對應的屬性
                                field.set(t, parser.nextText());
                            }
                        } catch (Exception e) {
                            //如果ListBean中沒有當前標籤,則會直接跳到這裡,什麼都不執行,然後再繼續往下走
                            
                        }
                    //如果外層的Bean已經例項化出來的話
                    }else if (bean != null) {
                        try {
                            //判斷當前標籤在沒在Bean的屬性中
                            Field field = beanClazz.getField(tagName);
                            //如果Bean中有當前標籤
                            if (field != null) {
                                //計數器+1
                                count++;
                                //將取出來的值賦給Bean中對應的屬性
                                field.set(bean, parser.nextText());
                            }
                        } catch (Exception e) {
                            //如果Bean中沒有當前標籤,則會直接跳到這裡,什麼都不執行,然後再繼續往下走
                        }
                    }
                    //如果當前標籤為我們傳入的內層根標籤,說明ListBean需要例項化出來了
                    if (tagName.equals(listRoot)) {
                        //將ListBean例項化出來
                        t = listClazz.newInstance();
                    }
                    //如果當前標籤為我們傳入的內層根標籤,說明Bean需要例項化出來了
                    if (tagName.equals(beanRoot)) {
                        //將Bean例項化出來
                        bean = beanClazz.newInstance();
                    }
                    break;
                //結束標籤
                case XmlPullParser.END_TAG:
                    //如果當前標籤為</item>
                    if (listRoot.equalsIgnoreCase(parser.getName())) {
                        //如果ListBean不為空
                        if (t != null) {
                            //儲存到list中,同時也儲存到了result中,因為list已經是儲存在result中了,
                            //只不過剛才沒有值,現在有值了
                            list.add(t);
                            //並且把ListBean置空,因為後續還有好多個item
                            t = null;
                        }
                    //如果當前標籤為</root>
                    }else if (beanRoot.equalsIgnoreCase(parser.getName())) {
                        //將Bean儲存到result中
                        result.setBean(bean);
                    }
                    break;
                }
                //移動到下一個標籤
                eventType = parser.next();
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        //如果計數器為0說明沒有解析到任何資料
        if (count == 0) {
            //將result置空就可以了
            result = null;
        }
        //將result返回
        return result;
        
    }
}
public class MainActivity extends Activity {


    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        String url = "http://op.juhe.cn/onebox/basketball/team";
        //我們使用基於Volley自定義的XMLRequest獲取xml資料,post方法
        XMLRequest xmlRequest = new XMLRequest(Method.POST, url, new Listener<XmlPullParser>() {


            @Override
            public void onResponse(XmlPullParser response) {
                try {
                    //我們看到第二個引數 “listRoot”我們傳入的是item。第三個引數是ListBean.class
                    //第四個引數“beanRoot”我們傳入的是root。第四個引數是Bean.class
                    ResultBeanAndList<ListBean> result = XmlUtils.getBeanByParseXml(response, "item", ListBean.class, "root", Bean.class);
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
            
        }, new Response.ErrorListener(){


            @Override
            public void onErrorResponse(VolleyError error) {
                
            }
            
        }){
            //聚合資料文件要求需要傳遞以下引數,大家可以到聚合資料去申請一下,就會得到一個key
            @Override
            protected Map<String, String> getParams() throws AuthFailureError {
                Map<String, String> params = new HashMap<String, String>();
                params.put("key","14acc11b5dd32beb57b90181c80c76b2 ");
                params.put("team", "火箭");
                params.put("dtype", "xml");
                return params;
            }
        };
        //將request加入到requestQueue中
        AppController.getInstance().addToRequestQueue(xmlRequest);
    }
}

6.從xml獲取的資料如下:

技術分享 最後結果result中的資料如下 技術分享 總結: 1.程式碼中的註釋為了避免大家混淆,我都用的這個例子中的實體類的名字,其實我們定義了泛型,實體類可以傳入任何一個類,這個根據我們實際的需要,設計我們需要的實體類就可以了。比如我們不需要title這個資料了,那我們直接將Bean中的title屬性刪除就可以了,如果我們在Bean中加入了一個不相關的屬性,例如Public String abc; 我們也不需要擔心出錯,通過Field field = listClazz.getField(tagName);如果xml檔案中沒有<abc>這個標籤的話,就會直接跳到catch裡,接著會向後執行,不會出任何錯誤。 2.這個例子中的xml檔案是一個bean和一個list的結構,如果是一個bean和兩個list的結構,我們就需要再按照此思路設計一個解析方法, 如果是隻有一個bean的結構那更簡單了,我們在工作中,遇到一種結構,就將其設計出來加入到XmlUtil檔案中。這樣就形成了一款我們自己的xml解析框架,以後再有相同結構的xml檔案,直接設計實體類即可。 3.自定義bean時要注意屬性修飾符不要為private否則反射機制獲取不到結果. 直接複用xmlUtil 和 ResultListAndBean兩個類 然後自定義需要的bean 傳入對應的節點即可