今天看了个帖子,模仿http解析方式写了一个函数,挺长的,分享出来看看大家是否会能获益这种解析模式。

qq120848369 2011-11-28 02:02:41
加精
原帖地址: 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里我们完全把精力集中在一部分,简化了逻辑复杂性,希望大家有所体会。

#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> ./main
iconNo:1
seq:1
devStatus:1
devdescribe:asd
iconNo:2
seq:2
devStatus:2
devdescribe:asasdsasdas师大d



...全文
4092 158 打赏 收藏 转发到动态 举报
写回复
用AI写文章
158 条回复
切换为时间正序
请发表友善的回复…
发表回复
sunylf 2011-12-10
  • 打赏
  • 举报
回复
C++,崇拜.
qq120848369 2011-12-09
  • 打赏
  • 举报
回复
[Quote=引用 152 楼 wgm001 的回复:]

跟开源风格还是差很远的, 很多细节不符合开源风格, 以下几点不行:

break; 直接跟在{}后面.
state 不应该直接使用数字, 至少定义几个宏.
// 注解符后面偶尔留空格, 偶尔又不留, 不够一至.
key_end = i-1; i+=2; if (line[i+1] != '{') 像这种+,-运算符旁边不留空格.
有些条件并不多也使用switch, 大量的switch……
[/Quote]

我看得代码里+=这些都保留有空格呀,真的。。。
聪明亮 2011-12-09
  • 打赏
  • 举报
回复
真的很给力
wine12 2011-12-09
  • 打赏
  • 举报
回复
看晕了
wgm001 2011-12-07
  • 打赏
  • 举报
回复
跟开源风格还是差很远的, 很多细节不符合开源风格, 以下几点不行:

break; 直接跟在{}后面.
state 不应该直接使用数字, 至少定义几个宏.
// 注解符后面偶尔留空格, 偶尔又不留, 不够一至.
key_end = i-1; i+=2; if (line[i+1] != '{') 像这种+,-运算符旁边不留空格.
有些条件并不多也使用switch, 大量的switch显得程序冗长难看, 适当情况下可以使用if else if可使代码紧凑.
花括号太多也会显得程序冗长难看, 一条语句的代码尽量不要用花括号, 像 if 后面只有一个return -1就不必用花括号包起来.

PS: 上面所说几点, 绝不是鸡蛋里挑骨头, 楼主可参考Linux内核或其它等优秀开源代码的风格.
yanghua1127 2011-12-07
  • 打赏
  • 举报
回复
这不是json吗
qq591285015 2011-12-06
  • 打赏
  • 举报
回复
那有什么用呢?
wangye11 2011-12-06
  • 打赏
  • 举报
回复
看不懂
ao929929fei 2011-12-06
  • 打赏
  • 举报
回复
楼主的代码写的真恶心,技术可能也不咋地
对象 2011-12-06
  • 打赏
  • 举报
回复
功能上应该是没问题,但是健壮性一般,还有就是没有用宏来替代常数,代码可读性会降低啊。
  • 打赏
  • 举报
回复
挺好!
glyc 2011-12-05
  • 打赏
  • 举报
回复
楼上的代码写得不错
SonicLing 2011-12-05
  • 打赏
  • 举报
回复
target -> "[{" key-value-list "}]"
key-value-list -> key-value-pair "," key-value-list
key-value-list -> key-value-pair
key-value-pair -> key ":" value
key -> literal
value -> literal


typedef multimap<string, string> key_value_table_t;
void parse_target(token_reader &reader, key_value_table_t &table)
{
string token = reader.read();
check(token == "[{");
parse_key_value_list(reader, table);
token = reader.read();
check(token == "}]");
}

void parse_key_value_list(token_reader &reader, key_value_table_t &table)
{
parse_key_value_pair(reader, table);
string token = reader.peek();
if (token == ",")
{
reader.read(); // consume ","
parse_key_value_list(reader, table);
}
/* 或者
while (true)
{
parse_key_value_pair(reader, table);
string token = reader.peek();
if (token != ",") break;
reader->read();
// continue;
}
*/
}

void parse_key_value_pair(token_reader &reader, key_value_table_t &table)
{
string key = reader.read();
if (!is_literal(key))
error("expect literal as key, but got ", key);
string colon = reader.read();
if (colon != ":")
error("expect ':', but got", colon);
string value = reader.read();
if (!is_literal(value))
error("expect literal as value, but got ", value);

table[key] = value;
}
hankanling123 2011-12-05
  • 打赏
  • 举报
回复
wjlazio 2011-12-05
  • 打赏
  • 举报
回复
不错,学习了。。。。
zhyzdl 2011-12-04
  • 打赏
  • 举报
回复
太长了,看着好晕啊!
wenwen20144567 2011-12-04
  • 打赏
  • 举报
回复
不错,值得学习
minggepi 2011-12-04
  • 打赏
  • 举报
回复
不知道好不好。。。。
yuhaizhan 2011-12-03
  • 打赏
  • 举报
回复
为得分而来
ZHL351720237 2011-12-03
  • 打赏
  • 举报
回复
帮顶一下,看起来很费劲
加载更多回复(91)

64,674

社区成员

发帖
与我相关
我的任务
社区描述
C++ 语言相关问题讨论,技术干货分享,前沿动态等
c++ 技术论坛(原bbs)
社区管理员
  • C++ 语言社区
  • encoderlee
  • paschen
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告
  1. 请不要发布与C++技术无关的贴子
  2. 请不要发布与技术无关的招聘、广告的帖子
  3. 请尽可能的描述清楚你的问题,如果涉及到代码请尽可能的格式化一下

试试用AI创作助手写篇文章吧