5

我有一堆字符串,例如:

"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;"

对于所有可打印的 ASCII 值,依此类推。

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

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

谢谢,

多姆

4

12 回答 12

9

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

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

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

至于替换内容,我将使用为 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.
于 2009-01-09T13:56:10.260 回答
3

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

        NUMS = '1234567890'
MAIN    LINE = INPUT                                :F(END)
SWAP    LINE ?  '&#' SPAN(NUMS) . N ';' = CHAR( N ) :S(SWAP)
        OUTPUT = LINE                               :(MAIN)
END
于 2009-01-09T14:54:40.907 回答
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
于 2009-01-09T18:42:17.577 回答
2

我不知道 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,我会检查一下。

于 2009-01-09T13:31:00.420 回答
1

由于只有一个“&”,现有的 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
于 2009-01-09T21:53:21.233 回答
1

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

echo "你好,这里是一个测试冒号:。这里是一个测试分号;
进一步测试&#65;.abc.~.def。"
| perl -we '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; 打印; }'

漂亮的打印:

#!/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 代码:

你也可以滚动你自己的有限状态机。但是以后维护起来会变得混乱和麻烦。

于 2009-01-09T23:57:27.330 回答
1

这是另一个 Perl 的单行代码(请参阅@mrree 的回答):

  • 一个测试文件:
$猫ent.txt
你好,这是一个测试冒号:。
这是一个测试分号; 'ƒ'
  • 单线:
$ perl -pe's~&#(1?\d\d);~
> sub{ return chr($1) if (31 < $1 && $1 < 127); $& }->()~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
  • 两个单行产生相同的输出:
你好,这是一个测试冒号:。
这是一个测试分号;'ƒ'
于 2009-01-10T16:25:19.163 回答
1

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

// 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
  • 输出:
    “你好,这是一个测试冒号:。” 
于 2009-01-21T15:23:24.277 回答
0

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

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

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

这有任何意义吗?

于 2009-01-09T13:53:45.083 回答
0

这是使用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

它为非递归版本打印:

你好,这是一个测试冒号:。
这是一个测试分号;'ƒ'
; <-- 可能是递归的

递归的产生:

你好,这是一个测试冒号:。
这是一个测试分号;'ƒ'
; <-- 可能是递归的
于 2009-01-11T20:08:32.503 回答
0

这是原始问题陈述显然不是很完整的情况之一,但如果您真的只想触发产生 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
于 2009-01-18T17:27:24.467 回答
0

这是一个基于boost::regex_token_iterator. 该程序将读取的十进制NCR替换stdin为相应的 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';
  }
}
于 2009-01-21T01:13:44.010 回答