【问题标题】:Regex Replacing : to ":" etc正则表达式替换:到“:”等
【发布时间】:2009-01-09 13:22:44
【问题描述】:

我有一堆字符串,例如:

"Hello, here's a test colon:. Here's a test semi-colon&#59;"

我想将其替换为

"Hello, here's a test colon:. Here's a test semi-colon;"

等等所有printable ASCII values

目前我正在使用boost::regex_search 匹配&#(\d+);,在依次处理每个匹配项时构建一个字符串(包括附加自上次找到的匹配项以来不包含匹配项的子字符串)。

谁能想到更好的方法?我对非正则表达式方法持开放态度,但在这种情况下,正则表达式似乎是一种相当明智的方法。

谢谢,

Dom

【问题讨论】:

  • 实体中的数字不是 ASCII。它是 Unicode 代码点编号,可以在 0-255 范围之外。
  • ... 在这种情况下,我们大概可以保持不变。 (顺便说一句,可打印的 ASCII 范围是 32-126)
  • 如果已经有一个库(甚至可能作为 boost 的一部分)将 XML 实体转换为它们的 utf-8/utf-16 等效项,我不会感到惊讶。
  • 如果知道这些实体的正式名称是数字字符引用 (NCR),那就太好了。见en.wikipedia.org/wiki/Numeric_character_reference - PEZ

标签: c++ regex boost ascii ncr


【解决方案1】:

使用正则表达式的最大优点是处理像& 这样的棘手情况,实体替换不是迭代的,它是一个步骤。正则表达式也将相当有效:两个前导字符是固定的,因此它会快速跳过不以&# 开头的任何内容。最后,正则表达式解决方案对于未来的维护者来说不会有太多惊喜。

我会说正则表达式是正确的选择。

这是最好的正则表达式吗?你知道你需要两个数字,如果你有 3 个数字,第一个数字是 1。可打印的 ASCII 毕竟是 -~。因此,您可以考虑&#1?\d\d;

至于替换内容,我会使用basic algorithm described for boost::regex::replace

For each match // Using regex_iterator<>
    Print the prefix of the match
    Remove the first 2 and last character of the match (&#;)
    lexical_cast the result to int, then truncate to char and append.

Print the suffix of the last match.

【讨论】:

  • 关于 ?\d\d 的好建议 - 谢谢(和 +1)。你能想出用正则表达式替换的方法吗?
  • 谢谢-您给出的算法大致就是我所做的(尽管我使用 (1?\d\d); 允许我访问数值而不剥离字符)。在 320 个字符串上运行生成的算法 100,000 次,其中要替换 20 个值需要 10 秒。不错!
  • 哦 - 如果速度很重要,请滥用您已经验证输入的事实。结果是 ((match[5]==';') ? (match[3]*10+match[4]) : (100+match[4]*10+match[5])) -'0 '*11
【解决方案2】:

这可能会为我赢得一些反对票,因为这不是 c++、boost 或正则表达式响应,但这是一个 SNOBOL 解决方案。这个适用于 ASCII。我正在为 Unicode 做一些事情。

        NUMS = '1234567890'
MAIN    LINE = INPUT                                :F(END)
SWAP    LINE ?  '&#' SPAN(NUMS) . N ';' = CHAR( N ) :S(SWAP)
        OUTPUT = LINE                               :(MAIN)
END

【讨论】:

  • 我印象深刻。根本无法理解该代码,现在必须检查 SNOBOL。 +1
  • 这是不正确的。它将解码“&#65;”到“A”而不是“A”
  • @Glomek 是对的,我认为正确的代码可能是 SWAP LINE ? REM '' 跨度(NUMS)。 N';' = 字符(N):S(交换)
  • 这根本不会改变输入字符串。
【解决方案3】:
* Repaired SNOBOL4 Solution
* &#38;#38; -> &#38;
     digit = '0123456789'
main line = input                        :f(end)
     result = 
swap line arb . l
+    '&#' span(digit) . n ';' rem . line :f(out)
     result = result l char(n)           :(swap)
out  output = result line                :(main)
end

【讨论】:

  • "&A" => “A”而不是“&A”
  • 我试图让它更有效率——显然没有奏效:尝试“arb”而不是“break('&')”
  • 我将更改放入您的帖子中。到目前为止,我还没有找到破坏这个版本的输入。
  • +1 -- 周围仍然有 SNOBOL 程序员给我留下了深刻的印象 -- 我喜欢这种语言,但已经超过 30 年没有使用它了。
【解决方案4】:

我不知道 boost 中的正则表达式支持,但请检查它是否具有支持回调或 lambda 等的 replace() 方法。这是使用其他语言的正则表达式执行此操作的常用方法。

这是一个 Python 实现:

s = "Hello, here's a test colon&#58;. Here's a test semi-colon&#59;"
re.sub(r'&#(1?\d\d);', lambda match: chr(int(match.group(1))), s)

制作:

"Hello, here's a test colon:. Here's a test semi-colon;"

我现在看过一些 boost,我发现它有一个 regex_replace 函数。但是 C++ 真的让我很困惑,所以我不知道你是否可以对替换部分使用回调。但是,如果我正确阅读了 boost 文档,则 (\d\d) 组匹配的字符串应该在 $1 中可用。如果我使用 boost,我会检查一下。

【讨论】:

  • 我添加了 '1?'到正则表达式。
  • lamdba .. 是错误的。它替换了不可打印的 ASCII 字符,例如 ''。注意:c 是 ASCII 可打印 iff 31
  • 是的,它主要是作为方法的一个例子。
  • 要在不触及 lambda 的情况下修复代码,您可以使用更具限制性的正则表达式,例如 r'(3[2-9]|[4-9]\d|1(?:[ 01]\d|2[0-6]));'见stackoverflow.com/questions/428013/…
【解决方案5】:

现有的 SNOBOL 解决方案不能正确处理多模式的情况,因为只有一个“&”。以下解决方案应该会更好:

        dd = "0123456789"
        ccp = "#" span(dd) $ n ";" *?(s = s char(n)) fence (*ccp | null)
   rdl  line = input                              :f(done)
   repl line "&" *?(s = ) ccp = s                 :s(repl)
        output = line                             :(rdl)
   done
   end

【讨论】:

  • # 后面的单引号应该是双引号。对不起。
  • 这会转换“&#38;”进入“&&”而不是“&”正如它应该。它还转换“A;”到“A”而不是“A”正如它应该的那样,除非打开 &fullscan,否则它不适用于行尾的代码。
【解决方案6】:

你知道,只要我们离开这里,perl 替换就有一个 'e' 选项。如评估表达式。例如

echo "你好,这是一个测试冒号:。这是一个测试分号;
进一步测试 &#65;.abc.~.def。"
| perl -we 'sub translate { my $x=$_[0]; if ( ($x >= 32) && ($x { return sprintf("%c",$x); } else { return "".$x.";"; } }
while () { s/(1?\d\d);/&translate($1)/ge;打印; }'

漂亮的印刷品:

#!/usr/bin/perl -w

sub translate
{
  my $x=$_[0];

  if ( ($x >= 32) && ($x <= 126) )
  {
    return sprintf( "%c", $x );
  }
  else
  {
    return "&#" . $x . ";" ;
  }
}

while (<>)
{
  s/&#(1?\d\d);/&translate($1)/ge;
  print;
}

虽然 perl 是 perl,但我相信还有更好的写法...


返回 C 代码:

您也可以滚动自己的有限状态机。但这会变得杂乱无章,以后维护起来也很麻烦。

【讨论】:

  • @mrree:我已经发布了另一个 perl 单行代码 stackoverflow.com/questions/428013/…
  • JF:嗯,谢谢。我想知道为什么 perl 代码不再运行。当我在那个回声中使用单引号而不是双引号时,我想我一定是在睡眠编码。 (现在已经修复了。)
  • 要正确显示示例,请在标记中将 &amp;amp;# 替换为 &amp;amp;#。顺便说一句,单引号可以与 echo 一起使用。
  • "&":我没有意识到 SO 没有正确翻译“&”。我确实添加了一些 '\\' 字符以避免误解其他字符。单/双引号对 TCSH/BASH 有不同的影响。在这种特殊情况下,文本中有单引号。 echo 'here's a' vs echo "here's a"。
【解决方案7】:

这是 Perl 的另一个单行代码(参见 @mrree's answer):

  • 一个测试文件:
$猫ent.txt 你好,这是一个测试冒号:。 这是一个测试分号; 'ƒ'
  • 单线:
$ perl -pe's~(1?\d\d);~ > sub{ return chr($1) if (31 ()~eg' ent.txt
  • 或使用更具体的正则表达式:
$ perl -pe"s~(1(?:[01][0-9]|2[0-6])|3[2-9]|[4-9][0-9]);~chr ($1)~eg" ent.txt
  • 两个单行程序产生相同的输出:
你好,这是一个测试冒号:。 这是一个测试分号; 'ƒ'

【讨论】:

  • 非常聪明!我印象深刻!一次建议:您可能想使用 '|'而不是 '!'如果您打算在 CSH/TCSH 的命令行上运行它,则作为 s// 分隔符。 (!即使在单引号内也是特殊的。)
  • @mrree:我已经替换了 '!'通过'~'。
【解决方案8】:

boost::spirit 解析器生成器框架允许轻松创建一个解析器来转换所需的NCRs。

// spirit_ncr2a.cpp
#include <iostream>
#include <string>
#include <boost/spirit/include/classic_core.hpp>

int main() {
  using namespace BOOST_SPIRIT_CLASSIC_NS; 

  std::string line;
  while (std::getline(std::cin, line)) {
    assert(parse(line.begin(), line.end(),
         // match "&#(\d+);" where 32 <= $1 <= 126 or any char
         *(("&#" >> limit_d(32u, 126u)[uint_p][&putchar] >> ';')
           | anychar_p[&putchar])).full); 
    putchar('\n');
  }
}
  • 编译:
$ g++ -I/path/to/boost -o spirit_ncr2a spirit_ncr2a.cpp
  • 运行:
$ echo "你好,这里是一个测试冒号:。" |精神_ncr2a
  • 输出:
“你好,这是一个测试冒号:。”

【讨论】:

    【解决方案9】:

    我确实认为我很擅长正则表达式,但我从未见过在正则表达式中使用过 lambda,请赐教!

    我目前正在使用 python,并且可以用这个 oneliner 解决它:

    ''.join([x.isdigit() and chr(int(x)) or x for x in re.split('&#(\d+);',THESTRING)])
    

    这有意义吗?

    【讨论】:

    • 如果你想要简洁,(pythyness?),你可以去掉 [] 括号。仅供参考。
    • 我在答案中添加了一个 Python 示例,以防您仍然好奇。您可以调用命名/常规函数来代替 lambda。
    • 你的回答很有道理,但我认为做一个 re.sub() 会更清楚。
    【解决方案10】:

    这是使用Flex 创建的 NCR 扫描仪:

    /** ncr2a.y: Replace all NCRs by corresponding printable ASCII characters. */
    %%
    &#(1([01][0-9]|2[0-6])|3[2-9]|[4-9][0-9]); { /* accept 32..126 */
      /**recursive: unput(atoi(yytext + 2)); skip '&#'; `atoi()` ignores ';' */
      fputc(atoi(yytext + 2), yyout); /* non-recursive version */
    }
    

    制作可执行文件:

    $ flex ncr2a.y
    $ gcc -o ncr2a lex.yy.c -lfl
    

    例子:

    $ echo "Hello, &#12; here's a test colon&#58;. 
    > Here's a test semi-colon&#59; '&#131;'
    > &#38;#59; <-- may be recursive" \
    > | ncr2a
    

    它为非递归版本打印:

    你好,这是一个测试冒号:。 这是一个测试分号; 'ƒ' ;

    递归的产生:

    你好,这是一个测试冒号:。 这是一个测试分号; 'ƒ' ;

    【讨论】:

      【解决方案11】:

      这是原始问题陈述显然不是很完整的情况之一,但如果您真的只想触发产生 32 到 126 之间字符的情况,那是对解决方案的一个微不足道的改变较早发布。请注意,我的解决方案还可以处理多模式情况(尽管第一个版本不会处理某些相邻模式在范围内而其他模式不在范围内的情况)。

            dd = "0123456789"
            ccp = "#" span(dd) $ n *lt(n,127) *ge(n,32) ";" *?(s = s char(n))
       +      fence (*ccp | null)
       rdl  line = input                              :f(done)
       repl line "&" *?(s = ) ccp = s                 :s(repl)
            output = line                             :(rdl)
       done
       end
      

      处理这种情况并不是特别困难(例如 ;#131;#58; 也会产生“;#131;:”:

            dd = "0123456789"
            ccp = "#" (span(dd) $ n ";") $ enc
       +      *?(s = s (lt(n,127) ge(n,32) char(n), char(10) enc))
       +      fence (*ccp | null)
       rdl  line = input                              :f(done)
       repl line "&" *?(s = ) ccp = s                 :s(repl)
            output = replace(line,char(10),"#")       :(rdl)
       done
       end
      

      【讨论】:

        【解决方案12】:

        这是基于boost::regex_token_iterator 的版本。该程序将从stdin读取的十进制NCRs替换为相应的ASCII字符并将它们打印到stdout

        #include <cassert>
        #include <iostream>
        #include <string>
        #include <boost/lexical_cast.hpp>
        #include <boost/regex.hpp>
        
        int main()
        {
          boost::regex re("&#(1(?:[01][0-9]|2[0-6])|3[2-9]|[4-9][0-9]);"); // 32..126
          const int subs[] = {-1, 1}; // non-match & subexpr
          boost::sregex_token_iterator end;
          std::string line;
        
          while (std::getline(std::cin, line)) {
            boost::sregex_token_iterator tok(line.begin(), line.end(), re, subs);
        
            for (bool isncr = false; tok != end; ++tok, isncr = !isncr) {
              if (isncr) { // convert NCR e.g., '&#58;' -> ':'
                const int d = boost::lexical_cast<int>(*tok);
                assert(32 <= d && d < 127);
                std::cout << static_cast<char>(d);
              }
              else
                std::cout << *tok; // output as is
            }
            std::cout << '\n';
          }
        }
        

        【讨论】:

          猜你喜欢
          • 2022-01-01
          • 1970-01-01
          • 1970-01-01
          • 2019-01-24
          • 2015-01-24
          • 1970-01-01
          • 1970-01-01
          • 2016-10-20
          相关资源
          最近更新 更多