c++中的Regex字符类减法



我正在编写一个c++程序,该程序需要接受XML Schema文件中定义的正则表达式,并使用它们来验证XML数据。问题是,XML模式使用的正则表达式的风格似乎不直接支持c++。

例如,有两个特殊的字符类ic是默认不定义的,而且XML Schema regex语言还支持"字符类减法"。c++中似乎不支持

允许使用ic特殊字符类非常简单,我可以查找" I "或"; c"在正则表达式中,并将它们替换为扩展版本,但是让字符类减法工作是一个更艰巨的问题…

例如,在XML Schema定义中有效的正则表达式在c++中抛出异常,表示它有不平衡的方括号。

#include <iostream>
#include <regex>
int main()
{
try
{
// Match any lowercase letter that is not a vowel
std::regex rx("[a-z-[aeiuo]]");
}
catch (const std::regex_error& ex)
{
std::cout << ex.what() << std::endl;
}
}

如何让c++在正则表达式中识别字符类减法?或者更好的是,是否有一种方法可以直接在c++中使用XML模式风格的正则表达式?

字符范围减法或交集在std::regex支持的任何语法中都不可用,因此您必须将表达式重写为支持的表达式之一。

最简单的方法是自己执行减法并将集合传递给std::regex,例如在您的示例中为[bcdfghjklvmnpqrstvwxyz]

另一个解决方案是找到一个功能更强大的正则表达式引擎或一个专用的XML库,该库支持XML Schema及其正则表达式语言。

从cppreference的例子开始

#include <iostream>
#include <regex>

void show_matches(const std::string& in, const std::string& re)
{
std::smatch m;
std::regex_search(in, m, std::regex(re));
if(m.empty()) {
std::cout << "input=[" << in << "], regex=[" << re << "]: NO MATCHn";
} else {
std::cout << "input=[" << in << "], regex=[" << re << "]: ";
std::cout << "prefix=[" << m.prefix() << "] ";
for(std::size_t n = 0; n < m.size(); ++n)
std::cout << " m[" << n << "]=[" << m[n] << "] ";
std::cout << "suffix=[" << m.suffix() << "]n";
}
}

int main()
{
// greedy match, repeats [a-z] 4 times
show_matches("abcdefghi", "(?:(?![aeiou])[a-z]){2,4}");
}

你可以在这里测试和检查正则表达式的细节。

选择使用非捕获组(?: ...)是为了防止它在您将在更大的正则表达式中使用它时更改您的组。

(?![aeiou])将在不消耗输入的情况下进行匹配,如果找到不匹配[aeiou]的字符,则[a-z]将匹配字母。结合这两个条件相当于字符类减法。

{2,4}是表示从2到4的量词,也可以是表示一个或多个的+,表示零或多个的*

编辑

看了其他回答中的评论,我明白你想支持XMLSchema

下一个程序展示了如何使用ECMA正则表达式来翻译"字符类差异"。转换成ECMA兼容的格式

#include <iostream>
#include <regex>
#include <string>
#include <vector>
std::string translated_regex(const std::string &pattern){
// pattern to identify character class subtraction
std::regex class_subtraction_re(
"\[((?:\\[\[\]]|[^[\]])*)-\[((?:\\[\[\]]|[^[\]])*)\]\]"
);
// translate the regular expression to ECMA compatible
std::string translated = std::regex_replace(pattern, 
class_subtraction_re, "(?:(?![$2])[$1])");
return translated;
}
void show_matches(const std::string& in, const std::string& re)
{
std::smatch m;
std::regex_search(in, m, std::regex(re));
if(m.empty()) {
std::cout << "input=[" << in << "], regex=[" << re << "]: NO MATCHn";
} else {
std::cout << "input=[" << in << "], regex=[" << re << "]: ";
std::cout << "prefix=[" << m.prefix() << "] ";
for(std::size_t n = 0; n < m.size(); ++n)
std::cout << " m[" << n << "]=[" << m[n] << "] ";
std::cout << "suffix=[" << m.suffix() << "]n";
}
}

int main()
{
std::vector<std::string> tests = {
"Some text [0-9-[4]] suffix", 
"([abcde-[ae]])",
"[a-z-[aei]]|[A-Z-[OU]] "
};
std::string re = translated_regex("[a-z-[aeiou]]{2,4}");
show_matches("abcdefghi", re);

for(std::string test : tests){
std::cout << " " << test << 'n' 
<< "   -- " << translated_regex(test) << 'n'; 
}

return 0;
}

编辑:递归和命名字符类

上述方法不适用于递归字符类否定。而且没有办法只使用正则表达式来处理递归替换。这使得解决方案远没有那么简单。

解决方案具有以下级别

  • 一个函数扫描正则表达式查找[
  • 当找到[时,当找到'-['时,有一个函数递归地处理字符类。
  • 模式p{xxxxx}被单独处理以识别命名字符模式。命名类在specialCharClass映射中定义,我举两个例子。
#include <iostream>
#include <regex>
#include <string>
#include <vector>
#include <map>
std::map<std::string, std::string> specialCharClass = {
{"IsDigit", "0-9"},
{"IsBasicLatin", "a-zA-Z"}
// Feel free to add the character classes you want
};
const std::string getCharClassByName(const std::string &pattern, size_t &pos){
std::string key;
while(++pos < pattern.size() && pattern[pos] != '}'){
key += pattern[pos];
}
++pos;
return specialCharClass[key];
}
std::string translate_char_class(const std::string &pattern, size_t &pos){

std::string positive;
std::string negative;
if(pattern[pos] != '['){
return "";
}
++pos;

while(pos < pattern.size()){
if(pattern[pos] == ']'){
++pos;
if(negative.size() != 0){
return "(?:(?!" + negative + ")[" + positive + "])";
}else{
return "[" + positive + "]";
}
}else if(pattern[pos] == '\'){
if(pos + 3 < pattern.size() && pattern[pos+1] == 'p'){
positive += getCharClassByName(pattern, pos += 2);
}else{
positive += pattern[pos++];
positive += pattern[pos++];
}
}else if(pattern[pos] == '-' && pos + 1 < pattern.size() && pattern[pos+1] == '['){
if(negative.size() == 0){
negative = translate_char_class(pattern, ++pos);
}else{
negative += '|';
negative = translate_char_class(pattern, ++pos);
}
}else{
positive += pattern[pos++];
}
}
return '[' + positive; // there is an error pass, forward it
}
std::string translate_regex(const std::string &pattern, size_t pos = 0){
std::string r;
while(pos < pattern.size()){
if(pattern[pos] == '\'){
r += pattern[pos++];
r += pattern[pos++];
}else if(pattern[pos] == '['){
r += translate_char_class(pattern, pos);
}else{
r += pattern[pos++];
}
}
return r;
}
void show_matches(const std::string& in, const std::string& re)
{
std::smatch m;
std::regex_search(in, m, std::regex(re));
if(m.empty()) {
std::cout << "input=[" << in << "], regex=[" << re << "]: NO MATCHn";
} else {
std::cout << "input=[" << in << "], regex=[" << re << "]: ";
std::cout << "prefix=[" << m.prefix() << "] ";
for(std::size_t n = 0; n < m.size(); ++n)
std::cout << " m[" << n << "]=[" << m[n] << "] ";
std::cout << "suffix=[" << m.suffix() << "]n";
}
}

int main()
{
std::vector<std::string> tests = {
"[a]",
"[a-z]d",
"[\p{IsBasicLatin}-[\p{IsDigit}-[89]]]",
"[a-z-[aeiou]]{2,4}",
"[a-z-[aeiou-[e]]]",
"Some text [0-9-[4]] suffix", 
"([abcde-[ae]])",
"[a-z-[aei]]|[A-Z-[OU]] "
};

for(std::string test : tests){
std::cout << " " << test << 'n' 
<< "   -- " << translate_regex(test) << 'n'; 
// Construct a reegx (validate syntax)
std::regex(translate_regex(test)); 
}
std::string re = translate_regex("[a-z-[aeiou-[e]]]{2,10}");
show_matches("abcdefghi", re);

return 0;
}

尝试使用具有XPath支持的库中的库函数,如libxml中的xmlregexp(是一个C库),它可以处理XML正则表达式并将它们直接应用于XML

http://www.xmlsoft.org/html/libxml-xmlregexp.html xmlRegexp

——比;http://web.mit.edu/outland/share/doc/libxml2-2.4.30/html/libxml-xmlregexp.html& lt;——

另一种选择可能是PugiXML (c++库,我应该在c++中使用什么XML解析器?)但是我认为它没有实现XML正则表达式的功能…

好了,在浏览了其他答案之后,我尝试了一些不同的东西,最终使用了libxml2xmlRegexp功能。

xmlRegexp相关函数的文档很少,所以我想在这里发布一个示例,因为其他人可能会发现它很有用:

#include <iostream>
#include <libxml/xmlregexp.h>
int main()
{
LIBXML_TEST_VERSION;
xmlChar* str = xmlCharStrdup("bcdfg");
xmlChar* pattern = xmlCharStrdup("[a-z-[aeiou]]+");
xmlRegexp* regex = xmlRegexpCompile(pattern);
if (xmlRegexpExec(regex, str) == 1)
{
std::cout << "Match!" << std::endl;
}
free(regex);
free(pattern);
free(str);
}

输出:

匹配!

我也试图使用Xerces-C++库中的XMLString::patternMatch,但它似乎没有在下面使用符合XML模式的正则表达式引擎。(老实说,我不知道它使用的是什么正则表达式引擎,文档非常糟糕,我在网上找不到任何例子,所以我就放弃了。)

最新更新