Xml解析 - XmlPullParser解析示例分析
XmlPullParser是一种事件驱动的XML文件解析方式。由于Android系统已经集成了该部分,且Android framework中有关XML文件的解析的部分,基本都是使用这种方式去处理的;所以,我们有必要学习、了解XmlPullParser的解析过程及使用方法,以便我们阅读Android源码或在工作中使用。
XmlPullParser以事件驱动解析流程,我们平时常用的事件有:
- XmlPullParser.START_DOCUMENT:XML解析器目前在文件的最开始处,此事还没有读到任何数据
- XmlPullParser.END_DOCUMENT:逻辑意义上的文件末尾;表明当前XML解析器处理的文件流已经到末端
- XmlPullParser.START_TAG:XML解析器解析到了一个开始标签
- XmlPullParser.END_TAG:XML解析器解析到了一个结束标签
- XmlPullParser.TEXT:XML解析器解析到了字符数据
另外还有其他的XmlPullParser.CDSECT、XmlPullParser.ENTITY_REF等事件,因这些事件较少涉及到,这里就不做分析了。
我们通过XmlPullParser解析器读取到每个事件后,都可以根据事件的类型来进行相应的处理。如,遇到XmlPullParser.START_TAG,我们就能获取到当前TAG的名字,然后根据我们自己设定的TAG的含义,去进行后续的解析、读取工作。
XmlPullParser使用中有时会涉及到当前解析的标签在整个XML结构中的所处的高度问题,我们通常调用getDepth()函数来获取当前TAG所处的高度值。下面我们借助一个示例,来看下XmlPullParser是如何定义TAG的高度的。
示例一
我们要解析的XML内容如下:
<?xml version="1.0" encoding="utf-8"?>
<root>
<A>
<B>
<C>
</C>
<D />
</B>
</A>
<E>
<F />
</E>
<G />
</root>
接着使用XmlPullParser写一段XML解析代码,通过打印来看每个节点的高度值是怎样的:
public class XmlPullMain {
public static void main(String[] args) {
try {
// InputStream xmlStream = new FileInputStream(new File("res/res.xml"));
// XmlPullMain parserTool = new XmlPullMain();
// parserTool.parserXmlByPull(xmlStream, "UTF-8");
InputStream xmlStream = new FileInputStream(new
File("res/test.xml"));
XmlPullMain parserTool = new XmlPullMain();
parserTool.testDepth(xmlStream, "UTF-8");
} catch (FileNotFoundException e) {
e.printStackTrace();
}
}
public void testDepth(InputStream xmlStream, String xmlEncode) {
XmlPullParserFactory factory = null;
XmlPullParser parser = null;
try {
factory = XmlPullParserFactory.newInstance();
factory.setNamespaceAware(true);
parser = factory.newPullParser();
parser.setInput(xmlStream, xmlEncode);
int event = parser.getEventType();
System.out.println("start current depth: " + parser.getDepth());
while (event != XmlPullParser.END_DOCUMENT) {
switch (event) {
case XmlPullParser.START_DOCUMENT:
System.out.println("START_DOCUMENT depth: " + parser.getDepth());
break;
case XmlPullParser.START_TAG:
String node = parser.getName();
if (node.equals("root")) {
System.out.println("root START_TAG depeth: " + parser.getDepth());
} else if (node.equals("A")) {
System.out.println("A START_TAG depeth: " + parser.getDepth());
} else if (node.equals("B")) {
System.out.println("B START_TAG depeth: " + parser.getDepth());
} else if (node.equals("C")) {
System.out.println("C START_TAG depeth: " + parser.getDepth());
} else if (node.equals("D")) {
System.out.println("D START_TAG depeth: " + parser.getDepth());
} else if (node.equals("E")) {
System.out.println("E START_TAG depeth: " + parser.getDepth());
} else if (node.equals("F")) {
System.out.println("F START_TAG depeth: " + parser.getDepth());
} else if (node.equals("G")) {
System.out.println("G START_TAG depeth: " + parser.getDepth());
}
break;
case XmlPullParser.TEXT:
System.out.println("--TEXT depth: " + parser.getDepth());
break;
case XmlPullParser.END_TAG:
String nodeEnd = parser.getName();
if (nodeEnd.equals("root")) {
System.out.println("root END_TAG depeth: " + parser.getDepth());
} else if (nodeEnd.equals("A")) {
System.out.println("A END_TAG depeth: " + parser.getDepth());
} else if (nodeEnd.equals("B")) {
System.out.println("B END_TAG depeth: " + parser.getDepth());
} else if (nodeEnd.equals("C")) {
System.out.println("C END_TAG depeth: " + parser.getDepth());
} else if (nodeEnd.equals("D")) {
System.out.println("D END_TAG depeth: " + parser.getDepth());
} else if (nodeEnd.equals("E")) {
System.out.println("E END_TAG depeth: " + parser.getDepth());
} else if (nodeEnd.equals("F")) {
System.out.println("F END_TAG depeth: " + parser.getDepth());
} else if (nodeEnd.equals("G")) {
System.out.println("G END_TAG depeth: " + parser.getDepth());
}
break;
case XmlPullParser.CDSECT:
break;
default:
break;
}
event = parser.next();
}
} catch (Exception e) {
}
}
}
代码的执行结果如下:
start current depth: 0
START_DOCUMENT depth: 0
root START_TAG depeth: 1
--TEXT depth: 1
A START_TAG depeth: 2
--TEXT depth: 2
B START_TAG depeth: 3
--TEXT depth: 3
C START_TAG depeth: 4
--TEXT depth: 4
C END_TAG depeth: 4
--TEXT depth: 3
D START_TAG depeth: 4
D END_TAG depeth: 4
--TEXT depth: 3
B END_TAG depeth: 3
--TEXT depth: 2
A END_TAG depeth: 2
--TEXT depth: 1
E START_TAG depeth: 2
--TEXT depth: 2
F START_TAG depeth: 3
F END_TAG depeth: 3
--TEXT depth: 2
E END_TAG depeth: 2
--TEXT depth: 1
G START_TAG depeth: 2
G END_TAG depeth: 2
--TEXT depth: 1
root END_TAG depeth: 1
根据输出结果,我们可以在Xml文件中标记处各个节点的高度值:
<?xml version="1.0" encoding="utf-8"?>
<!-- 0 -->
<root><!-- 1 -->
<A><!-- 2 -->
<B><!-- 3 -->
<C><!-- 4 -->
</C><!-- 4 -->
<D /><!-- 4 -->
</B><!-- 3 -->
</A><!-- 2 -->
<E><!-- 2 -->
<F /><!-- 3 -->
</E><!-- 2 -->
<G /><!-- 2 -->
</root><!-- 1 -->
注释标签中的整数值即为当前节点所处的高度,从结果可以看到:XmlPullParser对节点高度的处理是基于它所处的层次结构得出的。如果我们的XML文件写的层次清晰的话, 就很容易看出其中各个节点所处的高度情况。
另外,从输出结果,我们还看到:
- <a> ... </a>这种形式的标签,解析时会读取到两个TEXT事件:一个在<a> START_TAG之后产生,一个在</a> END_TAG之后产生
- <b />这种形式的标签只会在<b> END_TAG之后读取到一个TEXT事件,<b> START_TAG之后不会产生TEXT事件
这种TEXT事件有时候对我们是没有意义的,基于这种原因,我们在使用XmlPullParser解析Xml文件时,要根据自己的需要来处理得到的事件。
XmlPullParser的基本用法就跟上面示例所展示的那样:
- 首先获取到需要解析的XML文件对应的InputStream流
- 实例化XmlPullParserFactory工厂对象
- 再借助工厂对象获取XmlPullParser类型的解析器实例
然后再进行解析即可。
XmlPullParser的使用模式较为固定,这里不再做过多说明;其他的问题可以看它的源码来得到答案。
示例二
下面再给出一个模仿Androidmainfest.xml配置文件解析过程的示例。
我们要解析的Xml文件内容:
<?xml version="1.0" encoding="utf-8"?>
<!-- 0 -->
<manifest package="com.example.androidtest" ><!-- 1 -->
<uses-sdk
minSdkVersion="19"
targetSdkVersion="21">kitkat4.4</uses-sdk><!-- 2 -->
<application
allowBackup="true"
icon="ic_launcher" ><!-- 2 -->
<activity
name="MainActivity"
label="app_name" ><!-- 3 -->
<intent-filter><!-- 4 -->
<action name="android.intent.action.MAIN" /><!-- 5 -->
<category name="android.intent.category.LAUNCHER" /><!-- 5 -->
</intent-filter><!-- 4 -->
</activity><!-- 3 -->
<activity
name="DefaultActivity"
label="def_name" ><!-- 3 -->
<intent-filter><!-- 4 -->
<category name="android.intent.category.LAUNCHER" /><!-- 5 -->
</intent-filter><!-- 4 -->
<intent-filter><!-- 4 -->
<category name="android.intent.category.HOME" /><!-- 5 -->
</intent-filter><!-- 4 -->
</activity><!-- 3 -->
</application><!-- 2 -->
<application
allowBackup="false"
icon="ic_launcher"><!-- 2 -->
<activity
name="TestActivity"
label="test_name" ><!-- 3 -->
<intent-filter><!-- 4 -->
<category name="android.intent.category.LAUNCHER" /><!-- 5 -->
</intent-filter><!-- 4 -->
</activity><!-- 3 -->
</application><!-- 2 -->
</manifest><!-- 1 -->
<!-- 0 -->
这里只是模仿了Android配置文件中的形式。再看具体的解析代码:
public class XmlPullMain {
public static void main(String[] args) {
try {
InputStream xmlStream = new FileInputStream(new File("res/res.xml"));
XmlPullMain parserTool = new XmlPullMain();
parserTool.parserXmlByPull(xmlStream, "UTF-8");
// InputStream xmlStream = new FileInputStream(new
// File("res/test.xml"));
// XmlPullMain parserTool = new XmlPullMain();
// parserTool.testDepth(xmlStream, "UTF-8");
} catch (FileNotFoundException e) {
e.printStackTrace();
}
}
public void parserXmlByPull(InputStream xmlStream, String xmlEncode) {
XmlPullParserFactory factory = null;
XmlPullParser parser = null;
try {
factory = XmlPullParserFactory.newInstance();
factory.setNamespaceAware(true);
parser = factory.newPullParser();
parser.setInput(xmlStream, xmlEncode);
int event = parser.getEventType();
while (event != XmlPullParser.END_DOCUMENT) {
switch (event) {
case XmlPullParser.START_DOCUMENT:
System.out.println("First we get START_DOCUMENT event");
break;
case XmlPullParser.START_TAG:
String node = parser.getName();
if (node.equals("manifest")) {
System.out.println("now we parser manifest Tag");
System.out.println("\tmainfest attribute: " + parser.getAttributeValue(0));
} else if (node.equals("uses-sdk")) {
System.out.println("now we parser uses-sdk Tag");
int count = parser.getAttributeCount();
for (int i = 0; i < count; i++)
System.out.println("\tuses-sdk attribute: " + parser.getAttributeValue(i));
System.out.println("\tuses-sdk plateform: " + parser.nextText());
} else if (node.equals("application")) {
System.out.println("now we parser Application Tag");
parserApplicationInfo(parser);
}
break;
default:
break;
}
event = parser.next();
}
} catch (XmlPullParserException | IOException e) {
e.printStackTrace();
}
}
private void parserApplicationInfo(XmlPullParser parser) throws XmlPullParserException, IOException {
int count = parser.getAttributeCount();
for (int i = 0; i < count; i++)
System.out.println("\twe parser Application attribute: " + parser.getAttributeValue(i));
int event = 0;
int depth = parser.getDepth();
while ((event = parser.next()) != XmlPullParser.END_DOCUMENT
&& (parser.getDepth() > depth || event != XmlPullParser.END_TAG)) {//只解析当前的标签内部的内容
if (event == XmlPullParser.TEXT || event == XmlPullParser.END_TAG) {
continue;
}
switch (event) {
case XmlPullParser.START_TAG:
String node = parser.getName();
if (node.equals("activity")) {
parserActivityInfo(parser);
}
break;
default:
break;
}
}
}
private void parserActivityInfo(XmlPullParser parser) throws XmlPullParserException, IOException {
int count = parser.getAttributeCount();
for (int i = 0; i < count; i++)
System.out.println("\t\twe parser Activity -> attribute: " + parser.getAttributeValue(i));
int event = 0;
int depth = parser.getDepth();
while ((event = parser.next()) != XmlPullParser.END_DOCUMENT
&& (parser.getDepth() > depth || event != XmlPullParser.END_TAG)) {//只解析当前的标签内部的内容
if (event == XmlPullParser.TEXT || event == XmlPullParser.END_TAG) {
continue;
}
switch (event) {
case XmlPullParser.START_TAG:
String node = parser.getName();
if (node.equals("intent-filter")) {
parserIntentInfo(parser);
}
break;
default:
break;
}
}
}
private void parserIntentInfo(XmlPullParser parser) throws XmlPullParserException, IOException {
int event = 0;
int depth = parser.getDepth();
while ((event = parser.next()) != XmlPullParser.END_DOCUMENT
&& (parser.getDepth() > depth || event != XmlPullParser.END_TAG)) {//只解析当前的标签内部的内容
if (event == XmlPullParser.TEXT || event == XmlPullParser.END_TAG) {
continue;
}
switch (event) {
case XmlPullParser.START_TAG:
String node = parser.getName();
if (node.equals("action")) {
System.out.println("\t\t\twe parser intent-filter action-> " + parser.getAttributeValue(0));
} else if (node.equals("category")) {
System.out.println("\t\t\twe parser intent-filter category-> " + parser.getAttributeValue(0));
}
break;
default:
break;
}
}
}
}
输出的结果如下:
First we get START_DOCUMENT event
now we parser manifest Tag
mainfest attribute: com.example.androidtest
now we parser uses-sdk Tag
uses-sdk attribute: 19
uses-sdk attribute: 21
uses-sdk plateform: kitkat4.4
now we parser Application Tag
we parser Application attribute: true
we parser Application attribute: ic_launcher
we parser Activity -> attribute: MainActivity
we parser Activity -> attribute: app_name
we parser intent-filter action-> android.intent.action.MAIN
we parser intent-filter category-> android.intent.category.LAUNCHER
we parser Activity -> attribute: DefaultActivity
we parser Activity -> attribute: def_name
we parser intent-filter category-> android.intent.category.LAUNCHER
we parser intent-filter category-> android.intent.category.HOME
now we parser Application Tag
we parser Application attribute: false
we parser Application attribute: ic_launcher
we parser Activity -> attribute: TestActivity
we parser Activity -> attribute: test_name
we parser intent-filter category-> android.intent.category.LAUNCHER
Android源码中对XML文件的解析工作基本都是按照这种模式去解析的。这里介绍XmlPullParser也是为了让我们能更好地阅读Fucking Source Code。
结束。