一、概況
項目中要解析一個XML格式的目錄,經過搜索了解到,解析方式主要有DOM,Pull,SAX三種方式,各自特點如下:
SAX
sax是一個用于處理xml事件驅動的“推”模型;
優點:解析速度快,占用內存少,它需要哪些數據再加載和解析哪些內容。
缺點:它不會記錄標簽的關系,而是需要應用程序自己處理,這樣就會增加程序的負擔。
DOM
dom是一種文檔對象模型;
優點:dom可以以一種獨立于平臺和語言的方式訪問和修改一個文檔的內容和結構,dom技術使得用戶頁面可以動態的變化,如動態顯示隱藏一個元素,改變它的屬性,增加一個元素等,dom可以使頁面的交互性大大增強。
缺點:dom解析xml文件時會將xml文件的所有內容以文檔樹方式存放在內存中。
PULL
pull和sax很相似,區別在于:pull讀取xml文件后觸發相應的事件調用方法返回的是數字,且pull可以在程序中控制,想解析到哪里就可以停止解析。 (SAX解析器的工作方式是自動將事件推入事件處理器進行處理,因此你不能控制事件的處理主動結束;而Pull解析器的工作方式為允許你的應用程序代碼主動從解析器中獲取事件,正因為是主動獲取事件,因此可以在滿足了需要的條件后不再獲取事件,結束解析。pull是一個while循環,隨時可以跳出,而sax不是,sax是只要解析了,就必須解析完成。)
所給的目錄文件是這樣的:
<?xml version="1.0" encoding="utf-8" ?>
<contents body-start-page="5">
<item name="封面" start-page="1" end-page="1"></item>
<item name="目錄" start-page="3" end-page="3"></item>
<item name="一、復習與提高" start-page="5" end-page="7">
<item name="符號表示數" start-page="6" end-page="6"></item>
<item name="小數" start-page="7" end-page="7"></item>
</item>
...
<item name="六、整理與提高" start-page="79" end-page="91">
<item name="小數的四則混合運算" start-page="80" end-page="80"></item>
...
<item name="數學廣場——編碼" start-page="91" end-page="91"></item>
</item>
<item name="說明" start-page="93" end-page="93"></item>
<item name="封底" start-page="95" end-page="95"></item>
</contents>
二、解析
1、先用DOM方式搞一搞:
fun getCatalogDOM(context: Context): ArrayList<Catalog> {
val catalogs = ArrayList<Catalog>()
try {
val factory = DocumentBuilderFactory.newInstance()
val builder = factory.newDocumentBuilder()
val inputStream = context.resources.assets.open("contents.xml")
val document = builder.parse(inputStream)
val root = document.documentElement
//以上獲取到了根節點
catalogs.addAll(parse(0, root))
} catch (e: Exception) {
e.printStackTrace()
}
return catalogs
}
private fun parse(level: Int, element: Element): ArrayList<Catalog> {
val catalogs = ArrayList<Catalog>()
val nodes = element.childNodes
for (i in 0 until nodes.length) {
val element2 = nodes.item(i)
if (element2 is Element) {
val catalog = Catalog()
catalog.name = element2.getAttribute("name")
catalog.startPage = element2.getAttribute("start-page")
catalog.endPage = element2.getAttribute("end-page")
catalog.level = level
catalogs.add(catalog)
}
if ("item".equals(element2.nodeName) && element2.nodeType == Document.ELEMENT_NODE) {
//還有子節點
val newLevel = level + 1
catalogs.addAll(parse(newLevel, element2 as Element))
}
}
return catalogs
}
把目錄定義了不同的層級,用level表示。
public class Catalog {
private String name;
private String startPage;
private String endPage;
private int level;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getStartPage() {
return startPage;
}
public void setStartPage(String startPage) {
this.startPage = startPage;
}
public String getEndPage() {
return endPage;
}
public void setEndPage(String endPage) {
this.endPage = endPage;
}
public int getLevel() {
return level;
}
public void setLevel(int level) {
this.level = level;
}
@Override
public String toString() {
return "Catalog{" +
"name='" + name + '\'' +
", startPage='" + startPage + '\'' +
", endPage='" + endPage + '\'' +
", level=" + level +
'}';
}
}
上面已經說了,DOM解析會占用較多的內存,作為一個追求完美的人,這肯定是不能接受的,下面說說用SAX方式解析。
2、SAX解析
fun getChapterSAX(context: Context): ArrayList<Chapter> {
val chapters = ArrayList<Chapter>()
try {
val factory: SAXParserFactory = SAXParserFactory.newInstance()
val parser: SAXParser = factory.newSAXParser()
val xmlReader: XMLReader = parser.xmlReader
val chapterHandler = ChapterHandler()
xmlReader.contentHandler = chapterHandler
xmlReader.parse(InputSource(context.resources.assets.open("contents.xml")))
chapters.addAll(chapterHandler.chapters)
} catch (e: Exception) {
e.printStackTrace()
}
return chapters
}
public class ChapterHandler extends DefaultHandler {
private Stack<Catalog> catalogStack = new Stack<>();
private List<Chapter> chapters = new ArrayList<>();//有很多章
private List<Catalog> catalogs = new ArrayList<>();//存放一章中的節,集合
public List<Chapter> getChapters() {
return chapters;
}
@Override
public void startDocument() throws SAXException {
super.startDocument();
Log.e("www", "------startDocument------");
}
@Override
public void endDocument() throws SAXException {
super.endDocument();
Log.e("www", "------endDocument------");
}
@Override
public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException {
super.startElement(uri, localName, qName, attributes);
Log.e("www", "startElement uri=" + uri + ",localName=" + localName + ",qName=" + qName + ",attributes=" + attributes);
if (TextUtils.equals("contents", qName))
return;
if (TextUtils.equals("item", qName)) {
Catalog catalog = new Catalog();
for (int i = 0; i < attributes.getLength(); i++) {
if (TextUtils.equals("name", attributes.getQName(i))) {
catalog.setName(attributes.getValue(i));
} else if (TextUtils.equals("start-page", attributes.getQName(i))) {
catalog.setStartPage(attributes.getValue(i));
} else if (TextUtils.equals("end-page", attributes.getQName(i))) {
catalog.setEndPage(attributes.getValue(i));
}
}
if (catalog != null) {
catalogStack.push(catalog);
}
}
}
@Override
public void endElement(String uri, String localName, String qName) throws SAXException {
super.endElement(uri, localName, qName);
Log.e("www", "endElement uri=" + uri + ",localName=" + localName + ",qName=" + qName);
if (TextUtils.equals("item", qName)) {
if (!catalogStack.empty()) {
Catalog catalog = catalogStack.pop();
if (catalogStack.empty()) {
//隊列空了,這一章結束.
Chapter curChapter = new Chapter();//當前章
curChapter.setName(catalog.getName());
curChapter.setStartPage(catalog.getStartPage());
curChapter.setEndPage(catalog.getEndPage());
curChapter.setCatalogs(catalogs);
catalogs.clear();
chapters.add(curChapter);
} else {
catalogs.add(catalog);
}
}
}
}
}
SAX是按標簽驅動的,不會記錄標簽之間的關系,開始解析的時候先回調startDocument() ,然后一個標簽開始的時候會回調startElement(),標簽結束的時候會回調endElement(),整個文件結束的時候回調endDocument()。這樣各自對應關系很難找,所以我又定義了一個目錄的類,目錄里邊有章節。
public class Chapter {
private String name;
private String startPage;
private String endPage;
private List<Catalog> catalogs=new ArrayList<>();
public String getStartPage() {
return startPage;
}
public void setStartPage(String startPage) {
this.startPage = startPage;
}
public String getEndPage() {
return endPage;
}
public void setEndPage(String endPage) {
this.endPage = endPage;
}
public List<Catalog> getCatalogs() {
return catalogs;
}
public void setCatalogs(List<Catalog> catalogs) {
if (catalogs != null)
this.catalogs.addAll(catalogs);
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "Chapter{" +
"name='" + name + '\'' +
", startPage='" + startPage + '\'' +
", endPage='" + endPage + '\'' +
", catalogs=" + catalogs +
'}';
}
這樣就可以解析出這個目錄了。但是,如果某一天目錄增加了層級,那我個方法還要改,也就是這個SAX解析不如DOM那種方法智能,這同樣是不能接受的。經過查找資料,原來有Dom4j這個神器,這樣解析就非常方便了。還是直接上代碼:
fun getChapterSAX2(context: Context): List<Catalog> {
val catalogs = ArrayList<Catalog>()
try {
val saxReader = SAXReader()
val inputStream = context.resources.assets.open("contents.xml")
val doc = saxReader.read(inputStream)
val rootElement = doc.rootElement
parseChild(1, rootElement, catalogs)
} catch (e: java.lang.Exception) {
e.printStackTrace()
}
return catalogs
}
private fun parseChild(level: Int, root: org.dom4j.Element, catalogs: ArrayList<Catalog>) {
if (root == null) return
val elements = root.elements()
for (i in 0 until elements.size) {
val element = elements.get(i)
val childElement = element.elements()
val catalog = Catalog()
catalog.name = element.attributeValue("name")
catalog.startPage = element.attributeValue("start-page")
catalog.endPage = element.attributeValue("end-page")
catalog.level = level
catalogs.add(catalog)
if (childElement.size > 0) {
parseChild(level + 1, element, catalogs)
}
}
}