系統程式員成長計劃-文本處理(XML解析器)

來源:互聯網
上載者:User
系統程式員成長計劃-文本處理(一) Sunday, June 07th, 2009 | Author: admin | » Edit «

 

轉載時請註明出處和作者連絡方式
文章出處:http://www.limodev.cn/blog
作者連絡方式:李先靜 <xianjimli at hotmail dot com>

系統程式員成長計劃-文本處理(一)

狀態機器(4)

XML解析器

XML(Extensible Markup Language)即可延伸標記語言 (XML),也是一種常用的資料檔案格式。相對於INI來說,它要複雜得多,INI只能儲存線性結構的資料,而XML可以儲存樹形結構的資料。先看下面的例子:

<?xml version="1.0" encoding="utf-8"?>
<mime-type xmlns="http://www.freedesktop.org/standards/shared-mime-info" type="all/all">
<!--Created automatically by update-mime-database. DO NOT EDIT!-->
<comment>all files and folders</comment>
</mime-type>

第一行稱為處理指示(PI),是給解析器用的。這裡告訴解析器,當前的XML檔案遵循XML 1.0規範,檔案內容用UTF-8編碼。

第二行是一個起始TAG,TAG的名稱為mime-type。它有兩個屬性,第一個屬性的名稱為xmlns,值為 http://www.freedesktop.org/standards/shared-mime-info。第二個屬性的名稱為type,值為 all/all。

第三行是一個注釋。

第四行包括一個起始TAG,一段文本和結束TAG。

第五行是一個結束TAG。

XML本身的格式不是本文的重點,我們不詳細討論了。這裡的重點是如何用狀態機器解析格式複雜的資料。

按照前面的方法,先把資料讀入到一個緩衝區中,讓一個指標指向緩衝區的頭部,然後移動指標,直到指向緩衝區的尾部。在這個過程中,指標可能指向:起始TAG,結束TAG,注釋,處理指示和文本。由此我們定義出狀態機器的主要狀態:

1. 起始TAG狀態
2. 結束TAG狀態
3. 注釋狀態
4. 處理指示狀態
5. 文本狀態

由於起始TAG、結束TAG、注釋和處理指示都在字元‘<’和‘>’之間,所以當讀入字元‘<’時,我們還無法知道當前的狀態, 為了便於處理,我們引入一個中間狀態,稱為“小於符號之後”的狀態。在讀入字元‘<’和‘!’之後,還要讀入兩個‘-’,才能確定進入注釋狀態,為了 便於處理,再引入兩個中間狀態“注釋前一”和“注釋前二”。再引入一個“空”狀態,表示不在上述任何狀態中。

狀態轉換函數:
1. 在“空”狀態下,讀入字元‘<’,進入“小於符號之後”狀態。
2. 在“空”狀態下,讀入非‘<’非空白的字元,進入“文本”狀態。
3. 在“小於符號之後”狀態下,讀入字元‘。’,進入“注釋前一” 狀態。
4. 在“小於符號之後”狀態下,讀入字元‘?’,進入“處理指示”狀態。
5. 在“小於符號之後”狀態下,讀入字元‘/’,進入“結束TAG”狀態。
6. 在“小於符號之後”狀態下,讀入有效ID字元,進入“起始TAG”狀態。
7. 在“注釋前一” 狀態下,讀入字元‘-’, 進入“注釋前二” 狀態。
8. 在“注釋前二” 狀態下,讀入字元‘-’, 進入“注釋” 狀態。
9. 在 “起始TAG” 狀態、“結束TAG” 狀態 、“文本” 狀態、“注釋”狀態 和“處理指示”狀態結束後,重新回到“空”狀態下。

這個狀態機器的圖形表示如下:

下面我們來看看代碼實現:

void xml_parser_parse(XmlParser* thiz, const char* xml)
{
/*定義狀態的枚舉值*/
enum _State
{
STAT_NONE,
STAT_AFTER_LT,
STAT_START_TAG,
STAT_END_TAG,
STAT_TEXT,
STAT_PRE_COMMENT1,
STAT_PRE_COMMENT2,
STAT_COMMENT,
STAT_PROCESS_INSTRUCTION,
}state = STAT_NONE;

thiz->read_ptr = xml;
/*指標從頭移動到尾*/
for(; *thiz->read_ptr != '/0'; thiz->read_ptr++)
{
char c = thiz->read_ptr[0];

switch(state)
{
case STAT_NONE:
{
if(c == '<')
{
/*在“空”狀態下,讀入字元‘<’,進入“小於符號之後”狀態。*/
xml_parser_reset_buffer(thiz);
state = STAT_AFTER_LT;
}
else if(!isspace(c))
{
/*在“空”狀態下,讀入非‘<’非空白的字元,進入“文本”狀態。*/
state = STAT_TEXT;
}
break;
}
case STAT_AFTER_LT:
{
if(c == '?')
{
/*在“小於符號之後”狀態下,讀入字元‘?’,進入“處理指示”狀態。*/
state = STAT_PROCESS_INSTRUCTION;
}
else if(c == '/')
{
/*在“小於符號之後”狀態下,讀入字元‘/’,進入“結束TAG”狀態。*/
state = STAT_END_TAG;
}
else if(c == '!')
{
/*在“小於符號之後”狀態下,讀入字元‘。’,進入“注釋前一” 狀態*/
state = STAT_PRE_COMMENT1;
}
else if(isalpha(c) || c == '_')
{
/*在“小於符號之後”狀態下,讀入有效ID字元,進入“起始TAG”狀態。*/
state = STAT_START_TAG;
}
else
{
}
break;
}
case STAT_START_TAG:
{
/*進入子狀態*/
xml_parser_parse_start_tag(thiz);
state = STAT_NONE;
break;
}
case STAT_END_TAG:
{
/*進入子狀態*/
xml_parser_parse_end_tag(thiz);
state = STAT_NONE;
break;
}
case STAT_PROCESS_INSTRUCTION:
{
/*進入子狀態*/
xml_parser_parse_pi(thiz);
state = STAT_NONE;
break;
}
case STAT_TEXT:
{
/*進入子狀態*/
xml_parser_parse_text(thiz);
state = STAT_NONE;
break;
}
case STAT_PRE_COMMENT1:
{
if(c == '-')
{
/*在“注釋前一” 狀態下,讀入字元‘-’, 進入“注釋前二” 狀態。*/
state = STAT_PRE_COMMENT2;
}
else
{
}
break;
}
case STAT_PRE_COMMENT2:
{
if(c == '-')
{
/*在“注釋前二” 狀態下,讀入字元‘-’, 進入“注釋” 狀態。*/
state = STAT_COMMENT;
}
else
{
}
}
case STAT_COMMENT:
{
/*進入子狀態*/
xml_parser_parse_comment(thiz);
state = STAT_NONE;
break;
}
default:break;
}

if(*thiz->read_ptr == '/0')
{
break;
}
}

return;
}

解析並沒有在此結束,原因是像“起始TAG”狀態和“處理指示”狀態等,它們不是原子的,內部還包含一些子狀態,如TAG名稱,屬性名稱和屬性值等, 它們需要進一步分解。在考慮子狀態時,我們可以忘掉它所處的上下文,只考慮子狀態本身,這樣問題會得到簡化。下面看一下起始TAG的狀態機器。

假設我們要解析下面這樣一個起始TAG:
<mime-type xmlns=”http://www.freedesktop.org/standards/shared-mime-info” type=”all/all”>

我們應該怎樣去做呢。還是按前面的方法,讓一個指標指向緩衝區的頭部,然後移動指標,直到指向緩衝區的尾部。在這個過程中,指標可能指向,TAG名稱,屬性名稱和屬性值。由此我們可以定義出狀態機器的主要狀態:

1. “TAG名稱”狀態
2. “屬性名稱”狀態
3. “屬性值”狀態

為了方便處理,再引兩個中間狀態,“屬性名稱之前”狀態和“屬性值之前”狀態。

狀態轉換函數:

初始狀態為“TAG名稱”狀態
1. 在“TAG名稱”狀態下,讀入空白字元,進入“屬性名稱之前”狀態。
2. 在“TAG名稱”狀態下,讀入字元‘/’或‘>’,進入“結束”狀態。
3. 在“屬性名稱之前”狀態下,讀入其它非空白字元,進入“屬性名稱”狀態。
4. 在“屬性名稱”狀態下,讀入字元‘=’,進入“屬性值之前”狀態。
5. 在“屬性值之前”狀態下,讀入字元‘“’,進入“屬性值”狀態。
6. 在“屬性值”狀態下,讀入字元‘”’,成功解析屬性名稱和屬性值,回到“屬性名稱之前”狀態。
7. 在“屬性名稱之前”狀態下,讀入字元‘/’或‘>’,進入“結束”狀態。

由於處理指示(PI)裡也包含了屬性狀態,為了重用屬性解析的功能,我們把屬性的狀態再提取為一個子狀態。這樣,“起始TAG”狀態的圖形表示如下:

下面我們看代碼實現:

static void xml_parser_parse_attrs(XmlParser* thiz, char end_char)
{
int i = 0;
enum _State
{
STAT_PRE_KEY,
STAT_KEY,
STAT_PRE_VALUE,
STAT_VALUE,
STAT_END,
}state = STAT_PRE_KEY;

char value_end = '/"';
const char* start = thiz->read_ptr;

thiz->attrs_nr = 0;
for(; *thiz->read_ptr != '/0' && thiz->attrs_nr < MAX_ATTR_NR; thiz->read_ptr++)
{
char c = *thiz->read_ptr;

switch(state)
{
case STAT_PRE_KEY:
{
if(c == end_char || c == '>')
{
/*在“屬性名稱之前”狀態下,讀入字元‘/’或‘>’,進入“結束”狀態。*/
state = STAT_END;
}
else if(!isspace(c))
{
/*在“屬性名稱之前”狀態下,讀入其它非空白字元,進入“屬性名稱”狀態。*/
state = STAT_KEY;
start = thiz->read_ptr;
}
}
case STAT_KEY:
{
if(c == '=')
{
/*在“屬性名稱”狀態下,讀入字元‘=’,進入“屬性值之前”狀態。*/
thiz->attrs[thiz->attrs_nr++] = (char*)xml_parser_strdup(thiz, start, thiz->read_ptr - start);
state = STAT_PRE_VALUE;
}

break;
}
case STAT_PRE_VALUE:
{
/*在“屬性值之前”狀態下,讀入字元‘“’,進入“屬性值”狀態。*/
if(c == '/"' || c == '/'')
{
state = STAT_VALUE;
value_end = c;
start = thiz->read_ptr + 1;
}
break;
}
case STAT_VALUE:
{
/*在“屬性值”狀態下,讀入字元‘”’,成功解析屬性名稱和屬性值,回到“屬性名稱之前”狀態。*/
if(c == value_end)
{
thiz->attrs[thiz->attrs_nr++] = (char*)xml_parser_strdup(thiz, start, thiz->read_ptr - start);
state = STAT_PRE_KEY;
}
}
default:break;
}

if(state == STAT_END)
{
break;
}
}

for(i = 0; i < thiz->attrs_nr; i++)
{
thiz->attrs[i] = thiz->buffer + (size_t)(thiz->attrs[i]);
}
thiz->attrs[thiz->attrs_nr] = NULL;

return;
}

記得在XML裡,單引號和雙引號都可以用來界定屬性值,所以上面對此做了特殊處理。

static void xml_parser_parse_start_tag(XmlParser* thiz)
{
enum _State
{
STAT_NAME,
STAT_ATTR,
STAT_END,
}state = STAT_NAME;

char* tag_name = NULL;
const char* start = thiz->read_ptr - 1;

for(; *thiz->read_ptr != '/0'; thiz->read_ptr++)
{
char c = *thiz->read_ptr;

switch(state)
{
case STAT_NAME:
{
/*在“TAG名稱”狀態下,讀入空白字元,屬性子狀態。*/
/*在“TAG名稱”狀態下,讀入字元‘/’或‘>’,進入“結束”狀態。*/
if(isspace(c) || c == '>' || c == '/')
{
state = (c != '>' && c != '/') ? STAT_ATTR : STAT_END;
}
break;
}
case STAT_ATTR:
{
/*進入“屬性”子狀態*/
xml_parser_parse_attrs(thiz, '/');
state = STAT_END;

break;
}
default:break;
}

if(state == STAT_END)
{
break;
}
}

for(; *thiz->read_ptr != '>' && *thiz->read_ptr != '/0'; thiz->read_ptr++);

return;
}

處理指示的解析和起始TAG的解析基本上是一樣的,這裡只是看一下代碼:

static void xml_parser_parse_pi(XmlParser* thiz)
{
enum _State
{
STAT_NAME,
STAT_ATTR,
STAT_END
}state = STAT_NAME;

char* tag_name = NULL;
const char* start = thiz->read_ptr;

for(; *thiz->read_ptr != '/0'; thiz->read_ptr++)
{
char c = *thiz->read_ptr;

switch(state)
{
case STAT_NAME:
{
/*在“TAG名稱”狀態下,讀入空白字元,屬性子狀態。*/
/*在“TAG名稱”狀態下,‘>’,進入“結束”狀態。*/
if(isspace(c) || c == '>')
{
state = c != '>' ? STAT_ATTR : STAT_END;
}

break;
}
case STAT_ATTR:
{
/*進入“屬性”子狀態*/
xml_parser_parse_attrs(thiz, '?');
state = STAT_END;
break;
}
default:break;
}

if(state == STAT_END)
{
break;
}
}

tag_name = thiz->buffer + (size_t)tag_name;

for(; *thiz->read_ptr != '>' && *thiz->read_ptr != '/0'; thiz->read_ptr++);

return;
}

注釋,結束TAG和文本的解析非常簡單,這裡結合代碼看看就行了:

“注釋”子狀態的處理:

static void xml_parser_parse_comment(XmlParser* thiz)
{
enum _State
{
STAT_COMMENT,
STAT_MINUS1,
STAT_MINUS2,
}state = STAT_COMMENT;

const char* start = ++thiz->read_ptr;
for(; *thiz->read_ptr != '/0'; thiz->read_ptr++)
{
char c = *thiz->read_ptr;

switch(state)
{
case STAT_COMMENT:
{
/*在“注釋”狀態下,讀入‘-’,進入“減號一”狀態。*/
if(c == '-')
{
state = STAT_MINUS1;
}
break;
}
case STAT_MINUS1:
{
if(c == '-')
{
/*在“減號一”狀態下,讀入‘-’,進入“減號二”狀態。*/
state = STAT_MINUS2;
}
else
{
state = STAT_COMMENT;
}
break;
}
case STAT_MINUS2:
{
if(c == '>')
{
/*在“減號二”狀態下,讀入‘>’,結束解析。*/
return;
}
else
{
state = STAT_COMMENT;
}
}
default:break;
}
}

return;
}

“結束TAG”子狀態的處理:

static void xml_parser_parse_end_tag(XmlParser* thiz)
{
char* tag_name = NULL;
const char* start = thiz->read_ptr;
for(; *thiz->read_ptr != '/0'; thiz->read_ptr++)
{
/*讀入‘>’,結束解析。*/
if(*thiz->read_ptr == '>')
{
break;
}
}

return;
}

“文本”子狀態的處理:

static void xml_parser_parse_text(XmlParser* thiz)
{
const char* start = thiz->read_ptr - 1;
for(; *thiz->read_ptr != '/0'; thiz->read_ptr++)
{
char c = *thiz->read_ptr;
/*讀入‘>’,結束解析。*/
if(c == '<')
{
if(thiz->read_ptr > start)
{
}
thiz->read_ptr--;
return;
}
else if(c == '&')
{
/*讀入‘&’,進入實體(entity)解析子狀態。*/
xml_parser_parse_entity(thiz);
}
}

return;
}

實體(entity)子狀態比較簡單,這裡不做進一步分析了,留給讀者做練習吧。

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.