zoukankan      html  css  js  c++  java
  • FSM有限状态机运用分析系列一 —— XML解析器

    XML(Extensible Markup Language)即可扩展标记语言,也是一种常用的数据文件格式。相对于INI来说,它要复杂得多,INI只能保存线性结构的数据,而XML可以保存树形结构的数据。先看下面的例子:

    1 <?xml version="1.0" encoding="utf-8"?>
    2 <mime-type xmlns="http://www.freedesktop.org/standards/shared-mime-info" type="all/all">
    3 <!--Created automatically by update-mime-database. DO NOT EDIT!-->
    4 <comment>all files and folders</comment>
    5 </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” 状态 、“文本” 状态、“注释”状态 和“处理指令”状态结束后,重新回到“空”状态下。

    这个状态机的图形表示如下:

     
    下面我们来看看代码实现:

      1 void xml_parser_parse(XmlParser* thiz, const char* xml)
      2 {
      3     /*定义状态的枚举值*/
      4     enum _State
      5     {
      6         STAT_NONE,
      7         STAT_AFTER_LT,
      8         STAT_START_TAG,
      9         STAT_END_TAG,
     10         STAT_TEXT,
     11         STAT_PRE_COMMENT1,
     12         STAT_PRE_COMMENT2,
     13         STAT_COMMENT,
     14         STAT_PROCESS_INSTRUCTION,
     15     }state = STAT_NONE;
     16 
     17     thiz->read_ptr = xml;
     18     /*指针从头移动到尾*/
     19     for(; *thiz->read_ptr != '/0'; thiz->read_ptr++)
     20     {
     21         char c = thiz->read_ptr[0];
     22 
     23         switch(state)
     24         {
     25             case STAT_NONE:
     26             {
     27                 if(c == '<')
     28                 {
     29                     /*在“空”状态下,读入字符‘<’,进入“小于号之后”状态。*/
     30                     xml_parser_reset_buffer(thiz);
     31                     state = STAT_AFTER_LT;
     32                 }
     33                 else if(!isspace(c))
     34                 {
     35                     /*在“空”状态下,读入非‘<’非空白的字符,进入“文本”状态。*/
     36                     state = STAT_TEXT;
     37                 }
     38                 break;
     39             }
     40             case STAT_AFTER_LT:
     41             {
     42                 if(c == '?')
     43                 {
     44                     /*在“小于号之后”状态下,读入字符‘?’,进入“处理指令”状态。*/
     45                     state = STAT_PROCESS_INSTRUCTION;
     46                 }
     47                 else if(c == '/')
     48                 {
     49                     /*在“小于号之后”状态下,读入字符‘/’,进入“结束TAG”状态。*/
     50                     state = STAT_END_TAG;
     51                 }
     52                 else if(c == '!')
     53                 {
     54                     /*在“小于号之后”状态下,读入字符‘!’,进入“注释前一” 状态*/
     55                     state = STAT_PRE_COMMENT1;
     56                 }
     57                 else if(isalpha(c) || c == '_')
     58                 {
     59                     /*在“小于号之后”状态下,读入有效的ID字符,进入“起始TAG”状态。*/
     60                     state = STAT_START_TAG;
     61                 }
     62                 else
     63                 {
     64                 }
     65                 break;
     66             }
     67             case STAT_START_TAG:
     68             {
     69                 /*进入子状态*/
     70                 xml_parser_parse_start_tag(thiz);
     71                 state = STAT_NONE;
     72                 break;
     73             }
     74             case STAT_END_TAG:
     75             {
     76                 /*进入子状态*/
     77                 xml_parser_parse_end_tag(thiz);
     78                 state = STAT_NONE;
     79                 break;
     80             }
     81             case STAT_PROCESS_INSTRUCTION:
     82             {
     83                 /*进入子状态*/
     84                 xml_parser_parse_pi(thiz);
     85                 state = STAT_NONE;
     86                 break;
     87             }
     88             case STAT_TEXT:
     89             {
     90                 /*进入子状态*/
     91                 xml_parser_parse_text(thiz);
     92                 state = STAT_NONE;
     93                 break;
     94             }
     95             case STAT_PRE_COMMENT1:
     96             {
     97                 if(c == '-')
     98                 {
     99                     /*在“注释前一” 状态下,读入字符‘-’, 进入“注释前二” 状态。*/
    100                     state = STAT_PRE_COMMENT2;
    101                 }
    102                 else
    103                 {
    104                 }
    105                 break;
    106             }
    107             case STAT_PRE_COMMENT2:
    108             {
    109                 if(c == '-')
    110                 {
    111                       /*在“注释前二” 状态下,读入字符‘-’, 进入“注释” 状态。*/
    112                     state = STAT_COMMENT;
    113                 }
    114                 else
    115                 {
    116                 }
    117             }
    118             case STAT_COMMENT:
    119             {
    120                 /*进入子状态*/
    121                 xml_parser_parse_comment(thiz);
    122                 state = STAT_NONE;
    123                 break;
    124             }
    125             default:break;
    126         }
    127 
    128         if(*thiz->read_ptr == '/0')
    129         {
    130             break;
    131         }
    132     }
    133     return;
    134 }

    解析并没有在此结束,原因是像“起始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”状态的图形表示如下:


    下面我们看代码实现:

      1 static void xml_parser_parse_attrs(XmlParser* thiz, char end_char)
      2 {
      3      int i = 0;
      4     enum _State
      5     {
      6         STAT_PRE_KEY,
      7         STAT_KEY,
      8         STAT_PRE_VALUE,
      9         STAT_VALUE,
     10         STAT_END,
     11     }state = STAT_PRE_KEY;
     12 
     13     char value_end = '/"';
     14     const char* start = thiz->read_ptr;
     15 
     16     thiz->attrs_nr = 0;
     17     for(; *thiz->read_ptr != '/0' && thiz->attrs_nr < MAX_ATTR_NR; thiz->read_ptr++)
     18     {
     19         char c = *thiz->read_ptr;
     20 
     21         switch(state)
     22         {
     23             case STAT_PRE_KEY:
     24             {
     25                 if(c == end_char || c == '>')
     26                 {
     27                     /*在“属性名之前”状态下,读入字符‘/’或‘>’,进入“结束”状态。*/
     28                     state = STAT_END;
     29                 }
     30                 else if(!isspace(c))
     31                 {
     32                     /*在“属性名之前”状态下,读入其它非空白字符,进入“属性名”状态。*/
     33                     state = STAT_KEY;
     34                     start = thiz->read_ptr;
     35                 }
     36             }
     37             case STAT_KEY:
     38             {
     39                 if(c == '=')
     40                 {
     41                     /*在“属性名”状态下,读入字符‘=’,进入“属性值之前”状态。*/
     42                     thiz->attrs[thiz->attrs_nr++] = (char*)xml_parser_strdup(thiz, start, thiz->read_ptr - start);
     43                     state = STAT_PRE_VALUE;
     44                 }
     45 
     46                 break;
     47             }
     48             case STAT_PRE_VALUE:
     49             {
     50                 /*在“属性值之前”状态下,读入字符‘“’,进入“属性值”状态。*/
     51                 if(c == '/"' || c == '/'')
     52                 {
     53                     state = STAT_VALUE;
     54                     value_end = c;
     55                     start = thiz->read_ptr + 1;
     56                 }
     57                 break;
     58             }
     59             case STAT_VALUE:
     60             {
     61                 /*在“属性值”状态下,读入字符‘”’,成功解析属性名和属性值,回到“属性名之前”状态。*/
     62                 if(c == value_end)
     63                 {
     64                     thiz->attrs[thiz->attrs_nr++] = (char*)xml_parser_strdup(thiz, start, thiz->read_ptr - start);
     65                     state = STAT_PRE_KEY;
     66                 }
     67             }
     68             default:break;
     69         }
     70 
     71         if(state == STAT_END)
     72         {
     73             break;
     74         }
     75     }
     76 
     77     for(i = 0; i < thiz->attrs_nr; i++)
     78     {
     79           thiz->attrs[i] = thiz->buffer + (size_t)(thiz->attrs[i]);
     80     }
     81     thiz->attrs[thiz->attrs_nr] = NULL;
     82 
     83     return;
     84 }
     85 
     86 记得在XML里,单引号和双引号都可以用来界定属性值,所以上面对此做了特殊处理。
     87 
     88 static void xml_parser_parse_start_tag(XmlParser* thiz)
     89 {
     90       enum _State
     91       {
     92           STAT_NAME,
     93           STAT_ATTR,
     94           STAT_END,
     95       }state = STAT_NAME;
     96 
     97       char* tag_name = NULL;
     98       const char* start = thiz->read_ptr - 1;
     99 
    100       for(; *thiz->read_ptr != '/0'; thiz->read_ptr++)
    101       {
    102           char c = *thiz->read_ptr;
    103 
    104           switch(state)
    105           {
    106               case STAT_NAME:
    107               {
    108                   /*在“TAG名称”状态下,读入空白字符,属性子状态。*/
    109                   /*在“TAG名称”状态下,读入字符‘/’或‘>’,进入“结束”状态。*/
    110                   if(isspace(c) || c == '>' || c == '/')
    111                   {
    112                       state = (c != '>' && c != '/') ? STAT_ATTR : STAT_END;
    113                   }
    114                   break;
    115               }
    116               case STAT_ATTR:
    117               {
    118                     /*进入“属性”子状态*/
    119                   xml_parser_parse_attrs(thiz, '/');
    120                   state = STAT_END;
    121 
    122                   break;
    123               }
    124               default:break;
    125           }
    126 
    127           if(state == STAT_END)
    128           {
    129               break;
    130           }
    131       }
    132 
    133       for(; *thiz->read_ptr != '>' && *thiz->read_ptr != '/0'; thiz->read_ptr++);
    134 
    135       return;
    136 }

    处理指令的解析和起始TAG的解析基本上是一样的,这里只是看一下代码:

     1 static void xml_parser_parse_pi(XmlParser* thiz)
     2 {
     3     enum _State
     4     {
     5       STAT_NAME,
     6       STAT_ATTR,
     7       STAT_END
     8     }state = STAT_NAME;
     9 
    10     char* tag_name = NULL;
    11     const char* start = thiz->read_ptr;
    12 
    13     for(; *thiz->read_ptr != '/0'; thiz->read_ptr++)
    14     {
    15         char c = *thiz->read_ptr;
    16 
    17         switch(state)
    18         {
    19             case STAT_NAME:
    20             {
    21                 /*在“TAG名称”状态下,读入空白字符,属性子状态。*/
    22                 /*在“TAG名称”状态下,‘>’,进入“结束”状态。*/
    23                 if(isspace(c) || c == '>')
    24                 {
    25                     state = c != '>' ? STAT_ATTR : STAT_END;
    26                 }
    27 
    28                 break;
    29             }
    30             case STAT_ATTR:
    31             {
    32                 /*进入“属性”子状态*/
    33                 xml_parser_parse_attrs(thiz, '?');
    34                 state = STAT_END;
    35                 break;
    36             }
    37             default:break;
    38          }
    39 
    40          if(state == STAT_END)
    41          {
    42             break;
    43          }
    44       }
    45 
    46       tag_name = thiz->buffer + (size_t)tag_name;
    47 
    48       for(; *thiz->read_ptr != '>' && *thiz->read_ptr != '/0'; thiz->read_ptr++);
    49 
    50       return;
    51 }

    注释,结束TAG和文本的解析非常简单,这里结合代码看看就行了:

    “注释”子状态的处理:

     1 static void xml_parser_parse_comment(XmlParser* thiz)
     2 {
     3     enum _State
     4     {
     5       STAT_COMMENT,
     6       STAT_MINUS1,
     7       STAT_MINUS2,
     8     }state = STAT_COMMENT;
     9 
    10     const char* start = ++thiz->read_ptr;
    11     for(; *thiz->read_ptr != '/0'; thiz->read_ptr++)
    12     {
    13         char c = *thiz->read_ptr;
    14 
    15         switch(state)
    16         {
    17             case STAT_COMMENT:
    18             {
    19                 /*在“注释”状态下,读入‘-’,进入“减号一”状态。*/
    20                 if(c == '-')
    21                 {
    22                     state = STAT_MINUS1;
    23                 }
    24                 break;
    25             }
    26             case STAT_MINUS1:
    27             {
    28                 if(c == '-')
    29                 {
    30                     /*在“减号一”状态下,读入‘-’,进入“减号二”状态。*/
    31                     state = STAT_MINUS2;
    32                 }
    33                 else
    34                 {
    35                     state = STAT_COMMENT;
    36                 }
    37                 break;
    38             }
    39             case STAT_MINUS2:
    40             {
    41                 if(c == '>')
    42                 {
    43                     /*在“减号二”状态下,读入‘>’,结束解析。*/
    44                     return;
    45                 }
    46                 else
    47                 {
    48                     state = STAT_COMMENT;
    49                 }
    50             }
    51             default:break;
    52         }
    53     }
    54 
    55     return;
    56 }

    “结束TAG”子状态的处理:

     1 static void xml_parser_parse_end_tag(XmlParser* thiz)
     2 {
     3       char* tag_name = NULL;
     4       const char* start = thiz->read_ptr;
     5       for(; *thiz->read_ptr != '/0'; thiz->read_ptr++)
     6       {
     7         /*读入‘>’,结束解析。*/
     8         if(*thiz->read_ptr == '>')
     9         {
    10           break;
    11         }
    12       }
    13 
    14       return;
    15 }

    “文本”子状态的处理:

     1 static void xml_parser_parse_text(XmlParser* thiz)
     2 {
     3   const char* start = thiz->read_ptr - 1;
     4   for(; *thiz->read_ptr != '/0'; thiz->read_ptr++)
     5   {
     6     char c = *thiz->read_ptr;
     7     /*读入‘>’,结束解析。*/
     8     if(c == '<')
     9     {
    10       if(thiz->read_ptr > start)
    11       {
    12       }
    13       thiz->read_ptr--;
    14       return;
    15     }
    16     else if(c == '&')
    17     {
    18       /*读入‘&’,进入实体(entity)解析子状态。*/
    19       xml_parser_parse_entity(thiz);
    20     }
    21   }
    22 
    23   return;
    24 }
  • 相关阅读:
    day30 python类的继承,抽象类等
    20170702-变量说明,静态方法,类方法区别,断点调试,fork,yield协程,进程,动态添加属性等。。
    day29 面向对象入门
    day28 import,from * import *,__name__
    day27 模块:正则re, configparser, subprocess
    day26 re正则表达式
    MD5的学习与练习
    HBase
    Struts13---Ognl
    Struts12---文件的下载
  • 原文地址:https://www.cnblogs.com/dylan2011/p/2688536.html
Copyright © 2011-2022 走看看