【问题标题】:Regex: Finding all subexpressions (using boost::regex)正则表达式:查找所有子表达式(使用 boost::regex)
【发布时间】:2015-06-11 13:09:00
【问题描述】:

我有一个文件,其中包含一些 Valve 格式的“实体”数据。它基本上是一个键值交易,它看起来像这样:

{
"world_maxs" "3432 4096 822"
"world_mins" "-2408 -4096 -571"
"skyname" "sky_alpinestorm_01"
"maxpropscreenwidth" "-1"
"detailvbsp" "detail_sawmill.vbsp"
"detailmaterial" "detail/detailsprites_sawmill"
"classname" "worldspawn"
"mapversion" "1371"
"hammerid" "1"
}
{
"origin" "553 -441 322"
"targetname" "tonemap_global"
"classname" "env_tonemap_controller"
"hammerid" "90580"
}

每一对{} 算作一个实体,其中的行算作KeyValues。如您所见,它相当简单。

我想在 C++ 中将这些数据处理成vector<map<string, string> >。为此,我尝试使用 Boost 附带的正则表达式。这是我目前所拥有的:

static const boost::regex entityRegex("\\{(\\s*\"([A-Za-z0-9_]+)\"\\s*\"([^\"]+)\")+\\s*\\}");
boost::smatch what;
while (regex_search(entitiesString, what, entityRegex)) {
    cout << what[0] << endl;
    cout << what[1] << endl;
    cout << what[2] << endl;
    cout << what[3] << endl;
    break; // TODO
}

更易于阅读的正则表达式:

\{(\s*"([A-Za-z0-9_]+)"\s*"([^"]+)")+\s*\}

我还不确定正则表达式是否适合我的问题,但它似乎至少打印了最后一个键值对 (hammerid, 1)。

我的问题是,我将如何在表达式中提取“第 n 个”匹配的子表达式?还是没有真正实用的方法来做到这一点?编写两个嵌套的while-loop 会更好吗,一个搜索{} 模式,然后一个搜索实际的键值对?

谢谢!

【问题讨论】:

  • 不要使用正则表达式。使用语法。我会在一分钟内告诉你
  • Boost 正则表达式在这里表现不佳。您首先需要extract the {...} part,然后在其中使用"([A-Za-z0-9_]+?)"\s*?"([^"]+?)"。 Like this.
  • 非常有趣的网站!感谢那。我会看看@sehe 对语法的看法。

标签: c++ regex boost


【解决方案1】:

使用解析器生成器,您可以编写适当的解析器。

例如,使用 Boost Spirit,您可以将语法内联规则定义为 C++ 表达式:

    start  = *entity;
    entity = '{' >> *entry >> '}';
    entry  = text >> text;
    text   = '"' >> *~char_('"') >> '"';

这是一个完整的演示:

Live On Coliru

#include <boost/spirit/include/qi.hpp>
#include <boost/fusion/adapted/std_pair.hpp>
#include <map>

using Entity    = std::map<std::string, std::string>;
using ValveData = std::vector<Entity>;

namespace qi = boost::spirit::qi;

template <typename It, typename Skipper = qi::space_type>
struct Grammar : qi::grammar<It, ValveData(), Skipper>
{
    Grammar() : Grammar::base_type(start) {
        using namespace qi;

        start  = *entity;
        entity = '{' >> *entry >> '}';
        entry  = text >> text;
        text   = '"' >> *~char_('"') >> '"';

        BOOST_SPIRIT_DEBUG_NODES((start)(entity)(entry)(text))
    }
  private:
    qi::rule<It, ValveData(),                           Skipper> start;
    qi::rule<It, Entity(),                              Skipper> entity;
    qi::rule<It, std::pair<std::string, std::string>(), Skipper> entry;
    qi::rule<It, std::string()>                                  text;
};

int main()
{
    using It = boost::spirit::istream_iterator;
    Grammar<It> parser;
    It f(std::cin >> std::noskipws), l;

    ValveData data;
    bool ok = qi::phrase_parse(f, l, parser, qi::space, data);

    if (ok) {
        std::cout << "Parsing success:\n";

        int count = 0;
        for(auto& entity : data)
        {
            ++count;
            for (auto& entry : entity)
                std::cout << "Entity " << count << ": [" << entry.first << "] -> [" << entry.second << "]\n";
        }
    } else {
        std::cout << "Parsing failed\n";
    }

    if (f!=l)
        std::cout << "Remaining unparsed input: '" << std::string(f,l) << "'\n";
}

哪个打印(对于显示的输入):

Parsing success:
Entity 1: [classname] -> [worldspawn]
Entity 1: [detailmaterial] -> [detail/detailsprites_sawmill]
Entity 1: [detailvbsp] -> [detail_sawmill.vbsp]
Entity 1: [hammerid] -> [1]
Entity 1: [mapversion] -> [1371]
Entity 1: [maxpropscreenwidth] -> [-1]
Entity 1: [skyname] -> [sky_alpinestorm_01]
Entity 1: [world_maxs] -> [3432 4096 822]
Entity 1: [world_mins] -> [-2408 -4096 -571]
Entity 2: [classname] -> [env_tonemap_controller]
Entity 2: [hammerid] -> [90580]
Entity 2: [origin] -> [553 -441 322]
Entity 2: [targetname] -> [tonemap_global]

【讨论】:

    【解决方案2】:

    我认为使用一个正则表达式来完成这一切很难,因为每个实体 {} 中的条目数量是可变的。就我个人而言,我会考虑简单地使用std::readline 来进行解析。

    #include <map>
    #include <vector>
    #include <string>
    #include <sstream>
    #include <iostream>
    
    std::istringstream iss(R"~(
        {
        "world_maxs" "3432 4096 822"
        "world_mins" "-2408 -4096 -571"
        "skyname" "sky_alpinestorm_01"
        "maxpropscreenwidth" "-1"
        "detailvbsp" "detail_sawmill.vbsp"
        "detailmaterial" "detail/detailsprites_sawmill"
        "classname" "worldspawn"
        "mapversion" "1371"
        "hammerid" "1"
        }
        {
        "origin" "553 -441 322"
        "targetname" "tonemap_global"
        "classname" "env_tonemap_controller"
        "hammerid" "90580"
        }
    )~");
    
    int main()
    {
        std::string skip;
        std::string entity;
    
        std::vector<std::map<std::string, std::string> > vm;
    
        // skip to open brace, read entity until close brace
        while(std::getline(iss, skip, '{') && std::getline(iss, entity, '}'))
        {
            // turn entity into input stream
            std::istringstream iss(entity);
    
            // temporary map
            std::map<std::string, std::string> m;
    
            std::string key, val;
    
            // skip to open quote, read key to close quote
            while(std::getline(iss, skip, '"') && std::getline(iss, key, '"'))
            {
                // skip to open quote read val to close quote
                if(std::getline(iss, skip, '"') && std::getline(iss, val, '"'))
                    m[key] = val;
            }
    
            // move map (no longer needed)
            vm.push_back(std::move(m));
        }
    
        for(auto& m: vm)
        {
            for(auto& p: m)
                std::cout << p.first << ": " << p.second << '\n';
            std::cout << '\n';
        }
    }
    

    输出:

    classname: worldspawn
    detailmaterial: detail/detailsprites_sawmill
    detailvbsp: detail_sawmill.vbsp
    hammerid: 1
    mapversion: 1371
    maxpropscreenwidth: -1
    skyname: sky_alpinestorm_01
    world_maxs: 3432 4096 822
    world_mins: -2408 -4096 -571
    
    classname: env_tonemap_controller
    hammerid: 90580
    origin: 553 -441 322
    targetname: tonemap_global
    

    【讨论】:

    • 我在度假,忘记接受这个作为正确答案;它非常适合我的需求,在稍微调整迭代器后我得到了完美的结果:谢谢!
    • @bombax 仅供参考,您接受了不同的答案。还在度假吗?
    • 抱歉,我正在处理问题,并认为我没有回答这个问题,我之前一定标记错了......
    【解决方案3】:

    我会这样写:

    ^\{(\s*"([A-Za-z0-9_]+)"\s*"([^"]+)")+\s*\}$
    

    或者将正则表达式拆分为两个字符串。先匹配大括号,然后逐行循环大括号的内容。

    匹配大括号:^(\{[^\}]+)$ 匹配行:^(\s*"([A-Za-z0-9_]+)"\s*"([^"]+)"\s*)$

    【讨论】:

      猜你喜欢
      • 2011-08-24
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-04-02
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多