expat 是使用C語言所寫的XML解析器,採用Stream-oriented的方式解析XML 文件,在我使用的Ubunto環境下,可以用一下方式安裝套件:
# sudo apt-get install expat
expat 大約有二十多個供使用的Library Call,在此只介紹要用的幾個基本函數。
程式編譯後,連結的程式館是 -lexpat ,程式一開始要加入以下的 include file:
#include "expat.h"
之後在調用之前,要做以下宣告與初始化動作:
XML_Parser XML_ParserCreate(const XML_Char *encoding)
XML_ParserCreate()會建立一個 XML_Parser 的資料結構然後回傳給一個變數,之後所有的操作都要帶上這個變數。他的參數 encoding 是一個字串說明這個XML要處理的編碼方式。目前已經知道的有以下四種方式:
- US-ASCII
- UTF-8
- UTF-16
- ISO-8859-1
但是因為XML通常會指定文件的編碼方式,所以就不用雞婆,直接用 NULL 即可。所以我們最常見的作法就是採用如下宣告:
XML_Parser xml = XML_ParserCreate(NULL);
由於XML處理程式使用 Stream 架構,所以要先掛上一個Callback,也就是說我們要處理的XML是哪一段。
XML_SetElementHandler(XML_Parser p,
XML_StartElementHandler start,
XML_EndElementHandler end);
typedef void (*XML_StartElementHandler)(void *userData,
const XML_Char *name,
const XML_Char **atts);
typedef void (*XML_EndElementHandler)(void *userData,
const XML_Char *name);
XML_SetElementHandler() 就是用來掛上兩個 Callback,一個程式叫做 start() 是開始找出XML要處理的字串,一個 end() 用來找出結束的字串。 srat() 與 end() 都是Function pointer,指到兩個獨立的處理程式(Function)。但是在呼叫 XML_SetElementHandler() 設定Callback之前,我們還要為這兩個 Callback程式中的 userData 進行設定。
還有一個變數叫做 Depth,這是指XML的深度。就是第一層、第二層的意思。
設定 userData 的程式是
XML_SetUserData(XML_Parser parser, void *p)
這個呼叫會自動將 *p 所指到的記憶體,自動變成 *userData,而成為 start() 與 end() 的 data stream input。
start() callback 的功能就是找出 XML 自串的起始,然後將字串當中的字放到 **atts 之中回傳。 end() 的功能就是找出結尾字,也放到 *name 之中回傳。
也許用例子來說明比較容易,看下面看這個例子:
<server>
<client ip="114.36.9.181" lat="25.0478" lon="121.5318" isp="Chunghwa Telecom" isprating="3.7" rating="0" ispdlavg="0" ispulavg="0" loggedin="0" country="TW"/>
</server>
我們想要讀出這串XML裏面的值。
先將我們要處理的資料結構餵到 XML expat 當中。
XML_SetUserData(xml, p_client);
這個 p_client 是如此宣告的
struct client_info *p_client ;
struct client_info
{
char ip[MAX_IPADDRESS_STRLEN];
double lat;
double lon;
char isp[MAX_ISP_NAME];
};
對應到上面的XML,可以知道我們要出 ip 的值,填到上面的資料結構中,程式就可以繼續處理了。這時候要先寫 start 與 end 這兩個Callback。
Start_Callback()因為是Call-back,所以要使用預定的帶入的三個參數:
- void *userData : 就是上面的的 XML_SetUserData 設定的資料結構(就是 p_client)。
- const char *tagname : tagname 是一個要處理XML的標頭字串,以上面的例子來說,就是"client"。
- const char **atts : 這個一個Array。Call-back程式把字串中的內容Parsing出來之後,將結果放到Array當中。
注意這邊 tagname 與 atts 都是 Const char,說明這是一個Global Variable,必須在外圍先做宣告。
static void XMLCALL
Start_Callback(void *userData, const char *tagname, const char **atts)
{
int i;
if (strcmp(tagname, "client") == 0) {
struct client_info *p_client = (struct client_info *)userData;
for (i = 0; atts[i]; i += 2) {
if (strcmp(atts[i], "ip") == 0)
strcpy(p_client->ip, atts[i + 1]);
if (strcmp(atts[i], "isp") == 0)
strcpy(p_client->isp, atts[i + 1]);
if (strcmp(atts[i], "lat") == 0)
p_client->lat = atof(atts[i + 1]);
if (strcmp(atts[i], "lon") == 0)
p_client->lon = atof(atts[i + 1]);
}
}
}
End_Callback() 與 Start_Callback() 一樣,用來處理XML的結尾,通常這個結尾都很簡單,所以幾乎沒有甚麼好寫的。
這Call Back 有兩個參數:
userData : 就是之前設定好的XML Stream ;
name : 只需要做的XML Tag,例如前面的例子,就是傳入 "client" 這個字串。
static void XMLCALL
end_callback(void *userData, const char *tagname)
{
depth--; / *將處理這張XML的位置還原 */
if (strcmp(tagname, "clinet") == 0) {
/* 下面就是完成Pasing XML之後,做一堆處理這些XML的工作 */
int i;
struct server_info *p_server = (struct server_info *)userData;
p_server->distance = get_distance(client.lat, client.lon, p_server->lat, p_server->lon);
for (i = 0; i < MAX_CLOSEST_SERVER_NUM; i++) {
if ( servers[i].url[0] == 0 ) {
break;
}
}
if ( i == MAX_CLOSEST_SERVER_NUM ) {
for (i = 0; i <MAX_CLOSEST_SERVER_NUM; i++) {
if (servers[i].distance > p_server->distance) {
break;
}
}
}
if (i != MAX_CLOSEST_SERVER_NUM)
memcpy(&servers[i], p_server, sizeof(struct server_info));
memset(p_server, 0, sizeof(struct server_info));
}
}