【问题标题】:Regular expression to validate syntax of fields in any order, with acceptable values正则表达式以任何顺序验证字段的语法,具有可接受的值
【发布时间】:2018-08-09 01:05:46
【问题描述】:

考虑以下情况: 我们想使用正则表达式来验证具有 X 个字段的命令的语法 - 一个是强制性的,两个是可选的。这三个字段可以按任意顺序显示,用任意数量的空格分隔它们,并且可接受值的字典有限

Mandatory Field:  "-foo"
Optional Field 1:  Can be either of "-handle" "-bar" or "-mustache"
Optional Field 2:  Can be either of "-meow" "-mix" or "-want"

有效输入示例:

-foo
-foo           -bar
-foo-want
-foo -meow-bar
-foo-mix-mustache
-handle      -foo-meow
-mustache-foo
-mustache -mix -foo
-want-foo
-want-meow-foo
-want-foo-meow

无效输入示例:

woof
-handle-meow
-ha-foondle
meow
-foobar
stackoverflow
- handle -foo -mix
-handle -mix
-foo -handle -bar
-foo -handle -mix -sodium

我猜你可以说,有三个捕获组,第一个是强制性的,最后两个是可选的:

(\-foo){1}
(\-handle|\-bar|\-mustache)?
(\-meow|\-mix|\-want)?

但我不确定如何编写它,以便它们可以按任何顺序排列,可能被任意数量的空格分隔,并且没有其他任何东西。

到目前为止,我拥有的是三个前瞻性捕获组:(% 符号表示要完成的工作)

^(?=.*?(foo))(?=.*?(\-handle|\-bar|\-mustache))(?=.*?(\-meow|\-mix|\-want))%Verify that group 1 is present once, optional groups 2 and 3 zero or one times, in any order,  with any spaces%$ 

添加一个新的捕获组很简单,或者为现有组扩展可接受的输入,但我肯定对反向引用感到困惑,并且不太确定如何扩展检查以适应第 4 组将如何影响反向引用。

或者在“-”字符上使用诸如 boost::split 或 boost::tokenize 之类的东西会更有意义,然后遍历它们,计算适合第 1、2、3 组和“的标记”以上都不是,”并验证计数?

看起来应该是 boost 库的简单扩展或应用。

【问题讨论】:

  • 您决定使用正则表达式有什么特别的原因吗?虽然这里有一个解决方案,但它似乎过于复杂。
  • 我知道我过度设计了这个。也许正则表达式不是答案,但我觉得必须有一个 boost 库可以优雅地实现这个功能。上下文无关语法什么的。我们基本上有 3 个字段,每个字段都有一组定义的可接受值。一个字段是必填的,另外两个是可选的,这三个字段可以以任意顺序出现。
  • Jared B 的答案可能与您使用 Boost 获得的优雅功能一样接近。有了它,我相信你仍然可以使用正则表达式对字符串 parameter values 进行操作,如果这就是你想要的。正则表达式旨在解决一组特定的字符串问题,虽然您可以做到这一点,但它肯定处于其目的的边缘:)
  • 是否允许来自 same 字段的两个项目?为什么-want-meow-foo 有效而-foo -handle -bar 无效?
  • @Galik 我认为我的回答中不应允许它。

标签: c++ regex boost tokenize


【解决方案1】:

你提到了提升。你看过program_options吗? http://www.boost.org/doc/libs/1_55_0/doc/html/program_options/tutorial.html

【讨论】:

  • 这看起来和我想要的差不多,但我正在寻找专门对字符串数据进行操作,不一定是程序选项。
  • 使用 Boost PO 实现您想要的目标将是非常困难的™,请参见例如stackoverflow.com/questions/33701144/…
【解决方案2】:

确实,上下文无关的语法会很好。让我们将您的命令解析为如下结构:

struct Command {
    std::string one, two, three;
};

现在,当我们将其改编为融合序列时,我们可以为其编写灵气语法并享受自动属性传播:

CommandParser() : CommandParser::base_type(start) {
    using namespace qi;

    command = field(Ref(&f1)) ^ field(Ref(&f2)) ^ field(Ref(&f3));
    field   = '-' >> raw[lazy(*_r1)];

    f1 += "foo";
    f2 += "handle", "bar", "mustache";
    f3 += "meow", "mix", "want";

    start   = skip(blank) [ command >> eoi ] >> eps(is_valid(_val));
}

在这里,一切都很简单:permutation parser (operator^) 允许所有三个字段以任意顺序排列。

f1、f2、f3 是各个字段可接受的符号(Options,如下)。

最后,开始规则添加了空格的跳过,并在末尾检查(我们是否达到了eoi?是否存在必填字段?)。

现场演示

Live On Coliru

#include <boost/fusion/adapted/struct.hpp>
struct Command {
    std::string one, two, three;
};

BOOST_FUSION_ADAPT_STRUCT(Command, one, two, three)

#include <boost/spirit/include/qi.hpp>
#include <boost/spirit/include/phoenix.hpp>

namespace qi = boost::spirit::qi;

template <typename It> 
struct CommandParser : qi::grammar<It, Command()> {
    CommandParser() : CommandParser::base_type(start) {
        using namespace qi;

        command = field(Ref(&f1)) ^ field(Ref(&f2)) ^ field(Ref(&f3));
        field   = '-' >> raw[lazy(*_r1)];

        f1 += "foo";
        f2 += "handle", "bar", "mustache";
        f3 += "meow", "mix", "want";

        start   = skip(blank) [ command >> eoi ] >> eps(is_valid(_val));
    }
  private:
    // mandatory field check
    struct is_valid_f {
        bool operator()(Command const& cmd) const { return cmd.one.size(); }
    };
    boost::phoenix::function<is_valid_f> is_valid;

    // rules and skippers
    using Options = qi::symbols<char>;
    using Ref     = Options const*;
    using Skipper = qi::blank_type;

    qi::rule<It, Command()> start;
    qi::rule<It, Command(), Skipper> command;
    qi::rule<It, std::string(Ref)> field;

    // option values
    Options f1, f2, f3;
};

boost::optional<Command> parse(std::string const& input) {
    using It = std::string::const_iterator;

    Command cmd;
    bool ok = parse(input.begin(), input.end(), CommandParser<It>{}, cmd);

    return boost::make_optional(ok, cmd);
}

#include <iomanip>
void run_test(std::string const& input, bool expect_valid) {
    auto result = parse(input);

    std::cout << (expect_valid == !!result?"PASS":"FAIL") << "\t" << std::quoted(input) << "\n";
    if (result) {
        using boost::fusion::operator<<;
        std::cout << " --> Parsed: " << *result << "\n";
    }
}

int main() {
    char const* valid[] = { 
        "-foo",
        "-foo           -bar",
        "-foo-want",
        "-foo -meow-bar",
        "-foo-mix-mustache",
        "-handle      -foo-meow",
        "-mustache-foo",
        "-mustache -mix -foo",
        "-want-foo",
        "-want-meow-foo",
        "-want-foo-meow",
    };
    char const* invalid[] = {
        "woof",
        "-handle-meow",
        "-ha-foondle",
        "meow",
        "-foobar",
        "stackoverflow",
        "- handle -foo -mix",
        "-handle -mix",
        "-foo -handle -bar",
        "-foo -handle -mix -sodium",
    };

    std::cout << " === Positive test cases:\n";
    for (auto test : valid)   run_test(test, true);
    std::cout << " === Negative test cases:\n";
    for (auto test : invalid) run_test(test, false);
}

打印

 === Positive test cases:
PASS    "-foo"
 --> Parsed: (foo  )
PASS    "-foo           -bar"
 --> Parsed: (foo bar )
PASS    "-foo-want"
 --> Parsed: (foo  want)
PASS    "-foo -meow-bar"
 --> Parsed: (foo bar meow)
PASS    "-foo-mix-mustache"
 --> Parsed: (foo mustache mix)
PASS    "-handle      -foo-meow"
 --> Parsed: (foo handle meow)
PASS    "-mustache-foo"
 --> Parsed: (foo mustache )
PASS    "-mustache -mix -foo"
 --> Parsed: (foo mustache mix)
PASS    "-want-foo"
 --> Parsed: (foo  want)
FAIL    "-want-meow-foo"
FAIL    "-want-foo-meow"
 === Negative test cases:
PASS    "woof"
PASS    "-handle-meow"
PASS    "-ha-foondle"
PASS    "meow"
PASS    "-foobar"
PASS    "stackoverflow"
PASS    "- handle -foo -mix"
PASS    "-handle -mix"
PASS    "-foo -handle -bar"
PASS    "-foo -handle -mix -sodium"

【讨论】:

  • 只是为了展示 automagic 属性传播有多好,这里有一个 slightly modified (2 lines...) 版本,它使数据成员 optional&lt;string&gt;。它确实使开箱即用的输出更漂亮。
  • 有趣的是,我一定要阅读所用库的语法,了解它是如何工作的,以及如何根据我想要的目的调整它。尤其是自从我涉足 CFG 以来已经有 8 到 10 年的时间了。显得无比强大。置换解析器的存在肯定有帮助。我对必填字段的手动验证有点惊讶。如果有一些默认验证可以通过使用 boost::optional 来触发,将会做一些阅读。感谢您的帮助。如果我想出来,这将是一个强大的工具。
  • *咳咳* 是否
  • boost-optional 案例的验证显示在我的第一条评论中。真的,Spirit 的解析器模型是 PEG,它比 CFG 更强大
  • 一个问题:为什么我们需要使用融合结构,当所有的容器项都是字符串的时候?
【解决方案3】:

这是一个蛮力解决方案,应该适用于相当简单的情况。

这个想法是从这些捕获组可以出现的顺序的所有排列中构建一个正则表达式

在测试数据中只有6 排列。显然,这种方法很容易变得笨拙。

// Build all the permutations into a regex.
std::regex const e{[]{

    std::string e;

    char const* grps[] =
    {
        "\\s*(-foo)",
        "\\s*(-handle|-bar|-mustache)?",
        "\\s*(-meow|-mix|-want)?",
    };

    // initial permutation
    std::sort(std::begin(grps), std::end(grps));

    auto sep = "";

    do
    {
        e = e + sep + "(?:";
        for(auto const* g: grps)
            e += g;
        e += ")";
        sep = "|"; // separate each permutation with |
    }
    while(std::next_permutation(std::begin(grps), std::end(grps)));

    return e;

}(), std::regex_constants::optimize};

// Do some tests

std::vector<std::string> const tests =
{
    "-foo",
    "-foo           -bar",
    "-foo-want",
    "-foo -meow-bar",
    "-foo-mix-mustache",
    "-handle      -foo-meow",
    "-mustache-foo",
    "-mustache -mix -foo",
    "-want-foo",
    "-want-meow-foo",
    "-want-foo-meow",
    "woof",
    "-handle-meow",
    "-ha-foondle",
    "meow",
    "-foobar",
    "stackoverflow",
    "- handle -foo -mix",
    "-handle -mix",
    "-foo -handle -bar",
    "-foo -handle -mix -sodium",
};

std::smatch m;
for(auto const& test: tests)
{
    if(!std::regex_match(test, m, e))
    {
        std::cout << "Invalid: " << test << '\n';
        continue;
    }
    std::cout << "Valid: " << test << '\n';
}

【讨论】:

    猜你喜欢
    • 2021-03-04
    • 1970-01-01
    • 2013-10-07
    • 2022-06-15
    • 1970-01-01
    • 1970-01-01
    • 2013-09-27
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多