如何进行忽略控制字符的文本搜索?

时间:2013-03-19 23:58:43

标签: ruby regex search replace special-characters

我有一个源字符串,可能包含任何字符,包括空格,回车符和换行符(控制字符)。控制字符可以出现在包括单词中间的任何地方。

我有一个搜索字符串,可能与源字符串具有相同的字符选择,但通常是源的子字符串。此搜索字符串中控制字符的顺序和数量可能与来源不同。

当搜索字符串中的非控制字符匹配时,我需要从源字符串中删除该字符串,包括字符串中的任何控制字符。不应删除源字符串中其他位置的控制字符。

我的计划是在搜索字符串中的每个字符后添加\s*。这很好,但是我需要在搜索字符串中转义任何正则表达式特殊字符,否则它们将被视为正则表达式命令,而不是纯文本。

我可以在每个字符(\s*)之后添加'mytext.scan(/./).join("\\s*")'但是如何转义特殊字符而不是我插入的正则表达式代码?如果我反过来这样做,那么我可以逃避正则表达式的特殊字符,但我不能简单地在每个字符之后添加\s*;我需要避免逃脱的角色。

为清楚起见 控制字符=空格或\ t或\ r \ n或\ n或\ f

编辑:修改第3段以提高我的要求的清晰度

2 个答案:

答案 0 :(得分:0)

一种天真的方法是

1)将搜索字符串拆分为单个字符列表(每个字符串)

2)清理每个角色(仍然是一个字符串列表)

3)按\s* *

加入列表

*除了\s*不起作用,顺便说一下 - \s*将匹配0个或更多的空格,这与0个或更多个控制字符不同。请参阅http://www.regular-expressions.info/posixbrackets.html#class,并使用适用于正则表达式的“控制字符”形式:)

\W*也可能有用,因为\W是不在a-zA-Z0-9_中的任何字符。但我从未测试过是否匹配控制字符或仅打印字符。

答案 1 :(得分:0)

评论中讨论的或多或少:

  

制作源字符串和搜索字符串的副本。消除两个副本中的所有控制字符。使用源字符串副本中的搜索字符串副本进行搜索。如果需要(或重音删除,或......),您也可以进行大小写转换。使用大量\s*可能会大大减慢你的正则表达式。

     

搜索字符串只需要复制和预处理一次。每个源字符串都需要复制和预处理一次。如果最糟糕的情况发生,当你知道匹配时,你可以回到原始的源字符串并制作搜索字符串的新副本,这样你就可以在每个常规字符之间找到类似\s*的内容,并将搜索字符串的第二个(残缺的)副本的正则表达式应用于原始源字符串。因为你知道匹配,所以性能应该是合理的,即使失败匹配模式太慢也是如此。

这是所讨论的想法的Perl实现。

#!/usr/bin/env perl
use strict;
use warnings;
use Data::Dumper;

$Data::Dumper::Useqq = 1;

my $source = "'Twas (Tweedle-Dee's)\fBirthday\n\n\f\f\nand\ta\tl\tl\this friends were happy\n";
my $search = "(\fTwee\ndle\t-\tDee\r'\rs)\nBi\frth\fday";

print Data::Dumper->Dump([$source], [qw($source)]);
print Data::Dumper->Dump([$search], [qw($search)]);

my $c_source = $source;
my $c_search = $search;

$c_source =~ s/ |[[:cntrl:]]//g;    # Or s/\s//g;
$c_search =~ s/ |[[:cntrl:]]//g;    # Or s/\s//g;

print Data::Dumper->Dump([$c_source], [qw($c_source)]);
print Data::Dumper->Dump([$c_search], [qw($c_search)]);

if ($c_source =~ m/\Q$c_search\E/)
{
    # Locating the search in the original source...hard work...
    my @a_search = split //, $c_search;
    printf "Lengths: c_search %d; a_search %d\n", length($c_search), scalar(@a_search);

    @a_search = map { s/[][\\.*?+(){}]/\\$&/g; $_ } @a_search;   # Escape regex metacharacters
    #print Data::Dumper->Dump([\@a_search], [qw(@a_search)]);
    my $r_search = join "\\s*", @a_search;
    print Data::Dumper->Dump([$r_search], [qw($r_search)]);

    my $t_source = $source;
    $t_source =~ s/$r_search//g;
    print Data::Dumper->Dump([$t_source], [qw($t_source)]);
}

干净利落的象形文字乐趣 - 毫无疑问,它像泥一样清晰。前三行检查没有任何愚蠢的错误。 Data::Dumper模块明确地打印数据;它在那里进行调试。 Useqq变量可以明确地调整数据的打印方式。

变量$source$search是源字符串和搜索字符串。尽管每个人都有控制角色,但还是有一个匹配。请注意,混合中有一些正则表达式元字符 - 括号是正则表达式元字符。转储这些字符串以供参考。

接下来的两行会生成搜索和源字符串的副本。使用基于POSIX的正则表达式类删除控制字符和空格以指定所有控制字符。转储这些转换后的字符串以供检查。

if语句将转换后的源与转换后的搜索进行比较。 \Q...\E部分在两者之间抑制了正则表达式元字符的含义。如果有匹配,那么我们在大括号中输入代码块。

split操作从转换后的搜索字符串中创建单个字符数组。 printf检查理智。 map操作用反斜杠和元字符替换每个正则表达式元字符,而其他字符保持不变。 join将数组@a_search中的每个字符或字符对收集到字符串$r_search中,\s*分隔数组条目。

变量$t_source是源的另一个副本。 $r_search中的正则表达式应用于$t_search,任何匹配都不会被替换。结果被倾倒了。该脚本的输出是:

$source = "'Twas (Tweedle-Dee's)\fBirthday\n\n\f\f\nand\ta\tl\tl\this friends were happy\n";
$search = "(\fTwee\ndle\t-\tDee\r'\rs)\nBi\frth\fday";
$c_source = "'Twas(Tweedle-Dee's)Birthdayandallhisfriendswerehappy";
$c_search = "(Tweedle-Dee's)Birthday";
Lengths: c_search 23; a_search 23
$r_search = "\\(\\s*T\\s*w\\s*e\\s*e\\s*d\\s*l\\s*e\\s*-\\s*D\\s*e\\s*e\\s*'\\s*s\\s*\\)\\s*B\\s*i\\s*r\\s*t\\s*h\\s*d\\s*a\\s*y";
$t_source = "'Twas \n\n\f\f\nand\ta\tl\tl\this friends were happy\n";

字符串$t_source确实与$source对应,并删除了'(Tweedle-Dee's)生日',这似乎符合要求。

将其转换为Ruby是留给受虐待者的一种练习。

显然,您可以简单地创建并使用$r_search字符串作为正则表达式,并将其直接应用于$source的副本;它会奏效。但我非常怀疑,如果你将它应用于千字节长度的源字符串,代码将运行得非常慢。我没有做过测量来证明它。