邓作恒的博客 +

正则表达式解析xml的可能性

目标

我们要不使用第三方xml解析库, 自己编写C++代码解析 <skin name="test_bg" src="test.bmp" subwidth="0" top="10" left="10"></skin>.

python re

虽然我们可以用字符串那套方法慢慢一步一步地提取出来, 但是直觉上, 这里应该可以用上正则表达式. 方便起见, 我们用python先试下, 毕竟python标准库就有正则表达式, C++11才在标准库带正则表达式, C++98只能用第三方的, 所以待会再折腾.

利用子表达式, 或运算我们可以写出以下pattern:

'<skin((\s*)|(name="(.*?)")|(src="(.*?)")|(subwidth="(.*?)")|(top="(.*?)")|(left="(.*?)"))*?>.*?</skin>'

不管双引号里面的是什么, 都提取出来. 然后应re.findall, 得出的匹配是一个元组, 依次是上面正则表达式中的子表达式, 第一个就是字符串最后一个匹配 ((\s*)|(name="(.*?)")|(src="(.*?)")|(subwidth="(.*?)")|(top="(.*?)")|(left="(.*?)")) 的子串, 比如空耳, left=”10”.

那么, test_bg, test.bmp, 0, 10, 10的索引值应该分别是3,5,7,9,11. 于是我们可以这么提取:

# -*- coding: utf-8 -*-
import re

xml =''
pattern = r'<skin((\s*)|(name="(.*?)")|(src="(.*?)")|(subwidth="(.*?)")|(top="(.*?)")|(left="(.*?)"))*?>.*?</skin>'
r = re.findall(pattern,xml)
for i in r:
    print('name=%s'%i[3])
    print('src=%s'%i[5])
    print('subwidth=%s'%i[7])
    print('top=%s'%i[9])
    print('left=%s'%i[11])

能工作, 不过, 我们还可以对双引号内的内容格式做出一些限制, 或者等号附近不限空耳什么的, 结果如下:

# -*- coding: utf-8 -*-
import re

xml =''
pattern = r'<skin((\s*)|(name\s*=\s*"([^"]*?)")|(src\s*=\s*"([^"]*?)")|(subwidth\s*=\s*"([0-9]*?)")|(top\s*=\s*"([0-9]*?)")|(left\s*=\s*"([0-9]*?)"))*?>.*?</skin>'
r = re.findall(pattern,xml)
for i in r:
    print('name=%s'%i[3])
    print('src=%s'%i[5])
    print('subwidth=%s'%i[7])
    print('top=%s'%i[9])
    print('left=%s'%i[11])

但是, 我能否判断xml是否符合格式呢, 我觉得是做不到一步到位的, 如果是我, 先findall, 然后看看有没有缺什么属性, 没缺就算过了.

boost::regex

python试完了就该按照正题用C++实现了, 现在我们需要一个正则表达式库, 相信无论上哪问C++的正则表达式库, 十有八九都会回答boost的正则表达式库, 既然听起来怎么牛, 那我们就用用看吧.

首先你需要一个boost…

代码:

#include <iostream>
#include <string>
#include <boost/regex.hpp>

int main()
{
    std::string xml = "<skin name = \"test_bg\" src = \"test.bmp\" left = \"10\" top = \"11\" subwidth = \"0\" ></skin>";
    std::string pattern = "<skin((\\s*)|(name\\s*=\\s*\"([^\"]*?)\")|(src\\s*=\\s*\"([^\"]*?)\")|(subwidth\\s*=\\s*\"([0-9]*?)\")|(top\\s*=\\s*\"([0-9]*?)\")|(left\\s*=\\s*\"([0-9]*?)\"))*?>.*?</skin>";
    boost::regex reg(pattern);
    boost::smatch match;
    bool r = boost::regex_match(xml, match, reg);
    if (true == r)
    {
       std::cout << "name=" << match[4] << std::endl
            << "src=" << match[6] << std::endl
            << "subwidth=" << match[8] << std::endl
            << "top=" << match[10] << std::endl
            << "left=" << match[12] << std::endl;
    }
    return 0;
}

boost::regex的match略有不同, 整个字符串会作为match集的第一个子串, 所以对应提取出来的属性值下标都加1.

Reference