今天看了个帖子,模仿http解析方式写了一个函数,挺长的,分享出来看看大家是否会能获益这种解析模式。
原帖地址: http://topic.csdn.net/u/20111128/08/e8b21ab3-bb8c-46ab-a5b4-c6f8a8f3ba48.html?seed=1914154426&r=76692619#r_76692619
问题:
[{"iconNo":"1","seq":"1","devStatus":"1","devdescribe":"asd"}]
[{"iconNo":"2","seq":"2","devStatus":"2","devdescribe":"asasdsasdas师大d"}]
解析出各个字段,得到key-value。
我看Lighttpd解析http那一块代码大约1000行,不过它里边包括了http协议的处理,这里特意为了模仿它,写了一个基本类似的函数。
说说我为什么要写这么长吧, 因为我假设用户的输入是任意风格的, 也就是会有很多地方敲一些空格或者TAB, 格式也比较乱, 这里我还没有处理折叠格式,写时候忘了搞了,无非就是特殊对待一下\n。
http里的"\r\n" 对应这里的','。
另外,这里由于这里的key,value都被""包裹,我假定""内不允许存在空白字符,对于http,需要特殊判断key中间是否有空白字符。
这个办法靓点在哪里呢? 就是一趟扫描,不需要回溯再去检查合法性,那么复杂的情况为何可以做到呢? 就是用了state这个变量, 将解析过程划分成了不同的阶段, 0阶段是解析一行第一次进入的状态,以后都不会再进入了, 1是解析key,2是解析:并且过度到3阶段,3阶段是解析val,4阶段是解析,或者行结尾。 通过state,保证每一个case里我们完全把精力集中在一部分,简化了逻辑复杂性,希望大家有所体会。
- C/C++ code
#include <stdio.h>#include <stdlib.h>#include <string.h>static const char* parse_test[] = { "[{\"iconNo\":\"1\",\"seq\":\"1\",\"devStatus\":\"1\",\"devdescribe\":\"asd\"}]", "[{\"iconNo\":\"2\",\"seq\":\"2\",\"devStatus\":\"2\",\"devdescribe\":\"asasdsasdas师大d\"}]"};int parse_key_value(const char *line){ // 主要检查以下不合法性: //1, key或者value没有被'"'包裹 //2, ""内没有空白字符 //3, key : value 形式不合法 int key_start, dot, key_end, val_start, val_end; int line_len; int state = 0; //0:初始化,1:解析key,2:解析',',3:解析val,4:解析',' or '\0' //or }]序列 int left_over = 0; char key[20], value[30]; key_start = dot = key_end = val_start = val_end = -1; line_len = strlen(line); for (int i = 0; i <= line_len; /*<=line_len目的: 对','和'\0'同等对待*/) { switch (state) { case 0: { switch (line[i]) { case ' ': case '\t': { ++i; }break; case '[': { // 多余的[ if (left_over != 0) { return -1; } if (line[i+1] != '{') { return -1; } left_over++; //左侧"[{"序列合法 i+=2; }break; case '{': //不应该单独出现,此分支可以不写,被default处理 { return -1; }break; case '"': { if (left_over == 0) { //还没遇到[{序列 return -1; } // key的左'"',记录key_start key_start = ++ i; ++ state; //进入解析key状态 }break; default: { // 非空白字符 or 非[{序列 or 非'"' or '\0' return -1; }; } }break; case 1: { switch (line[i]) { case ' ': case '\t': case '{': case '[': case ',': case ':': case '\0': { // key不应该包含非字母,这里只列举一些 return -1; }break; case '"': { //key结束,记录key_end key_end = i-1; if (key_end < key_start) //key为空 { return -1; } ++ i; state ++; // 进入解析':'状态 }break; default: { // 任意合法字母 ++ i; }break; } }break; case 2: { switch (line[i]) { case '\t': case ' ': { // 空白字符掠过 ++ i; }break; case ':': { //找到':' dot = i++; state++; //向后掠过所有的空白,检测"val的'"' while (line[i] != '\0') { if (line[i] != ' ' && line[i] != '\t') { if (line[i] != '"') { return -1; //遇到非空白非"字符 } val_start = ++ i; break; } ++ i; } if (val_start == -1) { return -1; } }break; default: { //非合法,出错 return -1; }break; } }break; case 3: //开始解析value { switch (line[i]) { //val内不应该有非字母字符 case '\t': case ' ': case ',': case '\0': case '[': case '}': case ']': case '{': { return -1; }break; case '"': { val_end = i - 1; if (val_end < val_start) { return -1; } ++ i; state++; }break; default: { ++ i; //正常字母 }break; } }break; case 4: //最后阶段:如果是line末尾需要检测}] //不是line末尾需要检测',',并且重新进入state = 1 { // 直接快速的处理过去 while (line[i] == ' ' || line[i] == '\t') { ++ i; } if (line[i] == '\0') { return -1; // line末尾,却没有}] } if (line[i] == ',') { // 一个字段结束,打印key,value strncpy(key, line + key_start, key_end - key_start + 1); key[key_end - key_start + 1] = '\0'; strncpy(value, line + val_start, val_end - val_start + 1); value[val_end - val_start + 1] = '\0'; printf("%s:%s\n", key, value); key_start = key_end = val_start = val_end = -1; // 向后找到value的" while (line[++i] != '\0' && (line[i] == ' ' || line[i] == '\t')); if (line[i] != '"') { return -1; // , 之后非",错误 } key_start = ++ i; state = 1; //直接进入解析value状态 } else if (line[i] == '}') { if (line[i+1] != ']') { return -1; //}]不完整 } strncpy(key, line + key_start, key_end - key_start + 1); key[key_end - key_start + 1] = '\0'; strncpy(value, line + val_start, val_end - val_start + 1); value[val_end - val_start + 1] = '\0'; printf("%s:%s\n", key, value); return 0; //解析完毕,}]之后再有字符也不理会了 } else { // 意外的字符 return -1; } }break; default: { fprintf(stderr, "unknown state %d \n", state); return -1; }break; } }}int main(){ char test_buffer[1000]; while (scanf("%s", test_buffer) == 1) { parse_key_value(test_buffer); } for (int i = 0; i < 2; ++ i) { parse_key_value(parse_test[i]); } return 0;}owenliang@linux-7lsl:~/csdn/src> ./mainiconNo:1seq:1devStatus:1devdescribe:asdiconNo:2seq:2devStatus:2devdescribe:asasdsasdas师大d
[解决办法]
顶了再看,楼主是解析json吧
[解决办法]
顶一个。
http头对于空白符是有明确规定的,不和规定就算错了。所以也就没有任意风格的输入
[解决办法]
这种手工写状态机的代码,都是写起来比看起来容易啊。。。你写完痛快了,我真的看不懂了。
[解决办法]
呵呵,这个都是想起来简单,写起来就不是那么回事了,看别人写的就更难了
[解决办法]
[解决办法]
我觉得吧,代码风格太差了点。
是没看过《代码大全》,还是没看过《重构:改善既有代码的设计》?
[解决办法]
这个 求多加点分啊...楼主大大 我欣赏你
[解决办法]
状态机 是不错的选择、、
[解决办法]
感觉没有json解析强大。。。这个格式很固定。反正lz比我厉害。。
json的还可以解析整数,bool类型
[解决办法]
有点复杂...
俺以前用C++写的CGI代码和楼主给出的代码比较,看来是太piece of cake了。
[解决办法]
辛苦了,赞一个
[解决办法]
支持一下,
[解决办法]
完完全全看不懂
[解决办法]
天旋地转的感觉
[解决办法]
- C/C++ code
con->http_status = 400; con->keep_alive = 0; con->response.keep_alive = 0; log_error_write(srv, __FILE__, __LINE__, "sbsds", "invalid character in key", con->request.request, cur, *cur, "-> 400"); return 0;
[解决办法]
菜鸟来学习
[解决办法]
其实我写过状态机这类东西啊,就是分析语法而已。写完后,两天不看,自己都不知道是怎么回事了。
[解决办法]
不懂不懂。。。
[解决办法]
好长,好累,好难懂……
[解决办法]
看不懂...
[解决办法]
一点也看不懂...
这是C++吗
[解决办法]
大哥你还是说国语吧
[解决办法]
- C/C++ code
while (line[i] == ' ' || line[i] == '\t') { ++ i; }
[解决办法]
[解决办法]
[解决办法]
顶。。
[解决办法]
[解决办法]
dfa。。。哈哈
[解决办法]
帮顶一下,看起来很费劲
[解决办法]
c 气息,扑面而来。。