目录
在做4G项目实现拨号上网的过程中,我们需要获取中国三大运营商的APN,全球APN的原始参数都是存放在XML中的,我们需要使用,那么就需要将存放APN的XML文档进行解析。
一、XML基础知识
1、概念
(1)XML指可扩展标记语言(eXtensible Markup Language)。XML 本身不会做任何事情,是被设计用来传输和存储数据的,不用于表现和展示数据。
(2)XML 和 HTML 之间的差异:
- XML 不是 HTML 的替代;
- XML 和 HTML 为不同的目的而设计:
XML 被设计用来传输和存储数据,其焦点是数据的内容。
HTML 被设计用来显示数据,其焦点是数据的外观。 - HTML 旨在显示信息,而 XML 旨在传输信息。
2、结构
XML 文档中的元素形成了一棵文档树。这棵树从根部开始,并扩展到树的最底端。也就是从"根部"开始,然后扩展到"枝叶"。XML 文档必须包含根元素。该元素是所有其他元素的父元素。
以一个实例来进行说明:
<?xml version="1.0" encoding="UTF-8"?>
<letter>
<to>Seven</to>
<from>Tom</from>
<heading>Reminder</heading>
<body>Don't forget me this weekend!</body>
</letter>
说明:
第一行是 XML 声明。它定义 XML 的版本(1.0)和所使用的编码(UTF-8 : 万国码, 可显示各种语言)。
第二行描述文档的根元素(像在说:“本文档是一封信”)。
接下来 4 行描述根的 4 个子元素(to, from, heading 以及 body)。
最后一行定义根元素的结尾。
在这个实例中,XML 文档里面包含了一张 Tom 写给 Seven 的信。
二、APN的xml文档
1、 APN包含哪些参数
一个典型的APN包含的参数有名称、MCC、MNC、接入点、类型。
下面以CMCC APN为例,它包含下面一些参数:
<apn carrier=“连接互联网” //名称
mcc=“460” //MCC
mnc=“07” //MNC
apn=“cmnet” //接入点
type=“default,supl,net” //类型
/>
APN还可以包含其他更多的参数,详情见 android APN解析
2、APN的存储位置
- APN以XML的格式存储。
- 文件名:Apns-conf.xml
- 源码文件路径:
MTK平台(通常):alps\mediatek\frameworks\base\telephony\etc
高通平台(通常):android\vendor\qcom\proprietary\telephony-apps\etc
三、解析XML文档
本文使用库libxml2 解析
1、安装库libxml2
(1)官网地址:http://xmlsoft.org/sources/old/
(2)下载
在官网地址上复制下载链接,然后去linux下载
wget "https://gitlab.gnome.org/GNOME/libxml2/-/archive/v2.9.14/libxml2-v2.9.14.tar.gz"
(3)解压缩
tar -zxvf libxml2-v2.9.14.tar.gz
(4)安装
一般来说,在网上下载的库文件要安装要先使用命令 ./configure 生成 makefile ,但是,这个库文件,比较特殊,首先就使用 ./configure 命令会失败:
所以我们首先要做的应该是生成 configure 文件,打开解压缩过后的文件,会发现里面没有 makefile ,也没有 configure,但是此时我们可以发现有文件 autogen.sh ,这个脚本文件是我们生成 configure 的重要核心:
使用命令:
./autogen.sh
(这个过程可能时间会有些长)
然后就可以发现文件中已经生成了 configure 文件:
然后使用命令:
./configure
生成 makefile 文件:
之后,编译运行,但是此时又出现了问题,在编译时报错了:
经过查询后,我发现是因为我没有安装python-devel,这个软件包是 libxml2 的依赖包,如果不安装,libxml2 安装就会报错。这个软件包只是一个底层依赖包,所以安装 RPM 包即可。命令如下:
sudo apt-get -y --force-yes install python-dev
之后再进行make,就可以了
make
sudo make install
查看后发现库libxml2已经安装好了,此时的库是放在系统默认路径 下的,但是我想将其放在我自己的账号路径下,于是可以在 ./configure 的时候指定一下其路径:
./configure --prefix=/... ... (... ...是要安装的目标路径)
重新编译安装,会发现库已经安装到我的指定路径下了:
(5)链接
因为一般系统默认找库的路径是 /lib 和 /usr/lib ,在自己修改了路径之后,使用的时候要指明库的路径:
gcc -c *.c -I ../lib/include/libxml2 -L ../lib/lib/ -lxml2
-I :后面是编译时所需要的库的头文件的路径
-L:后面是链接时所需要的库文件
具体链接库的步骤可移步:动态库、静态库
2、代码封装
解析文件的具体使用方法和函数在代码中都有详述:
(1)apn_xml.h
#ifndef _APNS_XML_H_
#define _APNS_XML_H_
#include <stdio.h>
#include <errno.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <libxml2/libxml/parser.h>
#include <libxml2/libxml/xmlmemory.h>
#include <libxml/tree.h>
/******************************************************************
* 名称: xml_parser
* 功能: 解析保存APN的XML文件
*
* 入口参数: filename: xml文件名
*
* 出口参数: 正确返回为0,错误返回为-1
*******************************************************************/
extern int xml_parser(char *filename);
#endif
(2)apn_xml.c
#include "apns_xml.h"
#define XML_PRINT
#define FILENAME "./apns-full-conf.xml"
#ifdef XML_PRINT
#define xml_print(format,args...) printf(format,##args)
#else
#define xml_print(format,args...) do{} whlie(1)
#endif
/*
* 1、读入文件,返回文档指针doc
* 2、得到根节点
* 3、轮询所有节点,找到所需的节点(根元素为apn),取出其内容
* 4、取出带有mcc和mnc属性的节点
* 5、将mcc和mnc属性取出来
*
*
*/
int xml_parser(char *filename)
{
int rv = 0;
/* xmlDoc xmlDocPtr:文档对象的结构体及其指针 */
xmlDocPtr doc; //定义解析文件指针
/* xmlNode xmlNodePtr:节点对象的结构体及其指针 */
xmlNodePtr curNode; //定义节点指针
xmlChar *temp; //临时字符串变量
xmlChar *mccptr;
xmlChar *mncptr;
/*
* 函数:xmlDocPtr xmlReadFile (const char * filename,const char * encoding, int options);
* 函数说明: Filename: 目标文件的路径或URL
* encoding: 目标文件的编码方式,可以为NULL
* options: 枚举变量中的值,可以为0
* XML_PARSE_RECOVER = 1<<0, //尝试修复错误语法
* XML_PARSE_NOERROR = 1<<5, //不输出错误日志
* XML_PARSE_NOWARNING = 1<<6, //不输出警告日志
* XML_PARSE_PEDANTIC = 1<<7, //输出详细的错误日志
*
* 返回值:成功:XML文档的树结构 失败:NULL
*
*/
doc = xmlReadFile(FILENAME,"UTF-8",XML_PARSE_RECOVER);
if(NULL == doc)
{
xml_print("Read the xmlFile failed:%s\n",strerror(errno));
return -1;
}
/*
* 函数: xmlNodePtr xmlDocGetRootElement(xmlDocPtr doc)
* 函数说明:获得根节点
* 参数说明:doc:XML文档句柄。
* 返回值:成功:XML文档的根节点 失败:NULL
*
*/
curNode = xmlDocGetRootElement(doc);
if(NULL == curNode)
{
xml_print("Get the root failed:%s\n",strerror(errno));
//释放内存中的XML文档
xmlFreeDoc(doc);
return -1;
}
/*
* 函数:int xmlStrcmp(const xmlChar *str1,const xmlChar *str2);
* 函数说明:判断两个字符串是否相同
* 参数说明:str1:待判断的字符串 str2:正确的字符串
* 返回值:相同:0 不同:不为0
*
*/
rv = xmlStrcmp(curNode->name, (const xmlChar* ) "apns");
if(rv != 0)
{
xml_print("The root is not apns.\n");
xmlFreeDoc(doc);
return -1;
}
//找到根节点的首个儿子节点
curNode = curNode->xmlChildrenNode;
while(curNode != NULL)
{
//获得节点内容后取出
if( ! xmlStrcmp(curNode->name,(const xmlChar *) "apn") )
{
/*
* 函数:xmlChar* xmlNodeGetContent(xmlNodePtr cur);
* 函数说明:获得节点的内容
* 参数说明:cur:节点的指针
* 返回值:成功:节点的文本内容(需要用xmlFree()函数释放发挥的资源 失败:NULL
*
*/
temp = xmlNodeGetContent(curNode);
xml_print("APN: %s\n",temp);
xmlFree(temp);
}
//找到带有mcc和mnc属性的节点
/*
* 函数:
* 函数说明:判断节点cur是否具有属性
* 参数说明:
* 返回值:
*
*/
if( (xmlHasProp( curNode,BAD_CAST "mcc")) && (xmlHasProp( curNode,BAD_CAST "mnc") ) )
{
xmlNodePtr propNodePtr = curNode;
mccptr = xmlGetProp(propNodePtr,BAD_CAST "mcc");
mncptr = xmlGetProp(propNodePtr,BAD_CAST "MNC");
xml_print("The apn is:%s,mcc is:%s,mnc is:%s\n",temp,mccptr,mncptr);
xmlFree(mccptr);
xmlFree(mncptr);
}
curNode = curNode ->next;
}
xmlFreeDoc(doc);
return 0;
}
小tips:
可以看到我的.c文件中有这一段代码:
#define XML_PRINT
#ifdef XML_PRINT
#define xml_print(format,args...) printf(format,##args)
#else
#define xml_print(format,args...) do{} whlie(1)
#endif
说明:args… 表示可变参数列表,args在预处理过程中,会被实际的参数集所替换。“##”的作用是对token进行连接,上面中format,args都可以看作是token,如果token为空,“##”则不进行连接,所以允许省略可变参数。
其实这是一个很好用的小技巧,在做项目的时候,代码中调试阶段需要打印出来的语句,往往到最后阶段是没有必要的打印的,这时候要一个一个地修改删除 printf 是很麻烦的一件事,那我们再最开始写代码的时候就可以利用宏定义一段上面的代码,如果不想要打印,那么将 #define XML_PRINT 这一句注释掉就好。
还有在写解析代码的过程中,还有很多没有使用到的库函数,具体的函数说明可以移步下面两篇文章进行进一步学习:
libxml2库函数详解
Libxml2常用概述及常用函数