如何在Perl正则表达式中匹配带有重音和波浪符号的字符?

12

用户输入了一组带有重音符和波浪符的名称:

Renato Núñez, David DeJesús, and Edwin Encarnación 

我的数据库中有这些人的英文姓名

@names = ('Renato Nunez','David DeJesus','Edwin Encarnacion');

我希望对这些名字进行正则表达式匹配。

$string = "Renato Núñez, David DeJesús, and Edwin Encarnación";
foreach my $name (@names) {
    print "found:$name\n" if ($name =~ /$string/);
}

当前呈现的方式,我没有找到任何匹配项。

我尝试了这个,但它没有起作用。

$string = "Renato Núñez, David DeJesús, and Edwin Encarnación";
foreach my $name (@names) {
    $name =~ s|a|[áa]|;
    $name =~ s|e|[ée]|;
    $name =~ s|i|[íi]|;
    $name =~ s|o|[óo]|;
    $name =~ s|u|[úu]|;
    $name =~ s|n|[ñn]|;
    # Originally: print "found:$name\n" if ($name =~ /$string/);
    # Corrected to:
    print "found:$name\n" if ($string =~ /$name/);
}

编辑:不好意思,我在最后一行中颠倒了 $name 和 $string。

有什么建议吗?


2
建议1:在perldoc perlop中了解正则表达式运算符。我认为你想要说的是$string =~ /$name/而不是$name =~ /$string/,以及s|[áa]|a|而不是s|a|[áa]| - mob
1
我这样排序 s||| 是因为我想从字符串 "David DeJesus" 构建一个正则表达式,使得该名称可以带或不带重音。 - Sean
哦,现在我明白了。你正在尝试在$name中构建一个正则表达式,而不是剥离非英语化字符。 - mob
看看我刚刚添加到我的答案中的代码,它可以展示如何匹配可能带有重音符号的字符串,而无需担心它们是否存在。 - tchrist
5个回答

8
use Unicode::Normalize;
($gutted = NFD($string)) =~ s/pM//g;

然而,这几乎总是不正确的做法。你将如何处理?
Ævar Arnfjörð Dženan Ljubović King Henry Ⅷ Carlos Ⅴº, el Emperador 只需采用Unicode即可。正确的匹配带或不带变音符号的方法是实例化一个strength设置为忽略变音符号的Unicode::Collator对象。然后调用cmp或eq方法即可。
编辑
这就是你应该处理这些事情的方式。 见证:
«La Alberguería de Argañán»    sí tiene /AN/ en  un par de sitios «añ» y «án»
                               sí tiene /AL/ en     un solo sitio «Al»
«Bóveda del Río Almar»         sí tiene /AL/ en     un solo sitio «Al»
«Cabezón de Liébana»           sí tiene /AN/ en     un solo sitio «an»
                               sí tiene /ON/ en     un solo sitio «ón»
«Doña Mencía»                  sí tiene /EN/ en     un solo sitio «en»
                               sí tiene /ON/ en     un solo sitio «oñ»
«Gallegos de Argañán»          sí tiene /AN/ en  un par de sitios «añ» y «án»
                               sí tiene /AL/ en     un solo sitio «al»
«Griñón»                       sí tiene /IN/ en     un solo sitio «iñ»
                               sí tiene /ON/ en     un solo sitio «ón»
«Logroño»                      sí tiene /ON/ en     un solo sitio «oñ»
«Lliçà d’Amunt»                sí tiene /UN/ en     un solo sitio «un»
«Madroñal»                     sí tiene /ON/ en     un solo sitio «oñ»
                               sí tiene /AL/ en     un solo sitio «al»
«Mantilla»                     sí tiene /AN/ en     un solo sitio «an»
«Mañón»                        sí tiene /AN/ en     un solo sitio «añ»
                               sí tiene /ON/ en     un solo sitio «ón»
«Matilla de los Caños del Río» sí tiene /AN/ en     un solo sitio «añ»
«Montalbán de Córdoba»         sí tiene /AN/ en     un solo sitio «án»
                               sí tiene /ON/ en     un solo sitio «on»
                               sí tiene /AL/ en     un solo sitio «al»
«La Peña»                      sí tiene /EN/ en     un solo sitio «eñ»
«Piñuécar–Gandullas»           sí tiene /AN/ en     un solo sitio «an»
                               sí tiene /IN/ en     un solo sitio «iñ»
«A Pobra do Caramiñal»         sí tiene /IN/ en     un solo sitio «iñ»
                               sí tiene /AL/ en     un solo sitio «al»
«Prats de Lluçanès»            sí tiene /AN/ en     un solo sitio «an»
«Ribamontán al Monte»          sí tiene /AN/ en     un solo sitio «án»
                               sí tiene /ON/ en  un par de sitios «on» y «on»
                               sí tiene /AL/ en     un solo sitio «al»
«La Roca del Vallès»           sí tiene /AL/ en     un solo sitio «al»
«San Martín del Castañar»      sí tiene /AN/ en  un par de sitios «an» y «añ»
                               sí tiene /IN/ en     un solo sitio «ín»
«Santa Eulàlia de Ronçana»     sí tiene /AN/ en  un par de sitios «an» y «an»
                               sí tiene /ON/ en     un solo sitio «on»
                               sí tiene /AL/ en     un solo sitio «àl»
«Santa María de Cayón»         sí tiene /AN/ en     un solo sitio «an»
                               sí tiene /ON/ en     un solo sitio «ón»
«Valverde de Alcalá»           sí tiene /AL/ en          3 sitios «al», «Al» y «al»
«Villar de Argañán»            sí tiene /AN/ en  un par de sitios «añ» y «án»

这里是生成该内容的代码。

#!/usr/bin/env perl
#
# búsqueda-libre:
#
#    Cómo se debiera ordenar y buscar palabras en Unicode
#    que pueden llevarse marcas diacríticas (o no) sin que
#    éstas afecten la búsqueda.  También cómo cambiar el
#    el orden para que no cuente con articulos al principio
#    del los nombres, como se hace con los títulos de libros &c.
#
# Tom Christiansen <tchrist@perl.com>
# Fri Mar  4 21:06:35 MST 2011
#
#############################################

use utf8;
use 5.10.1;
use strict;
use warnings; # FATAL => "all";
use autodie;
use charnames qw< :full >;

use List::Util qw< max first >;
use Unicode::Collate;

my $INCLUÍR_NINGUNOS               = 0;
my $SI_IMPORTAN_MARCAS_DIACRÍTICAS = 0;

sub sí_ó_no(_) { $_[0] ? "sí" : "no" }

sub encomillar(_) {
    return join $_[0] =>
        "\N{LEFT-POINTING DOUBLE ANGLE QUOTATION MARK}",
        "\N{RIGHT-POINTING DOUBLE ANGLE QUOTATION MARK}",
    ;
}

binmode(STDOUT, ":utf8");
# Ésta está demasiada larga para la pantalla. :(
#
#    La Ciudad de Nuestra Señora la Reina de Los Ángeles de Porciúncula, California Alta
#

my @ciudades_españolas = ordenar_a_la_española(<<'LA_ÚLTIMA' =~ /\S.*\S/g);
        Santa Eulàlia de Ronçana
        Mañón
        A Pobra do Caramiñal
        La Alberguería de Argañán
        Logroño
        La Puebla del Río
        Villar de Argañán
        Piñuécar–Gandullas
        Mantilla
        Gallegos de Argañán
        Madroñal
        Griñón
        Lliçà d’Amunt
        Valverde de Alcalá
        Montalbán de Córdoba
        San Martín del Castañar
        La Peña
        Cabezón de Liébana
        Doña Mencía
        Santa María de Cayón
        Bóveda del Río Almar
        La Roca del Vallès
        Matilla de los Caños del Río
        Prats de Lluçanès
        Ribamontán al Monte
LA_ÚLTIMA

my $cmáx = -(2 + max map { length } @ciudades_españolas);

my @búsquedas = < {A,E,I,O,U}N AL >;
my $bmáx = -(2 + max map { length } @búsquedas);

my $ordenador = new Unicode::Collate::
                    level           => $SI_IMPORTAN_MARCAS_DIACRÍTICAS ? 2 : 1,
                 ## variable        => "non-ignorable",  # blanked, non-ignorable, shifted, shift-trimmed
                    normalization   => undef,
                ;

for my $aldea (@ciudades_españolas) {
    my $déjà_imprimée;
    for my $búsqueda (@búsquedas) {
        my @resultados = $ordenador->gmatch($aldea, $búsqueda);
        next unless @resultados || $INCLUÍR_NINGUNOS;
        printf qq(%*s %s tiene %*s en %17s %s\n),
                $cmáx => !$déjà_imprimée++ && encomillar($aldea),
                sí_ó_no(@resultados),
                $bmáx => "/$búsqueda/",
                cuántos_sitios(@resultados),
                enfilar(@resultados);
    }
}

sub cuántos_sitios {
    my @lista = @_;
    my $cantidad = @_;
    given ($cantidad) {
        when (0)  { return    "ningún sitio"    }
        when (1)  { return   "un solo sitio"    }
        when (2)  { return "un par de sitios"   }
        default   { return "$cantidad sitios"   }
    }
}

sub enfilar {
    my @lista = map { encomillar } @_;

    my $separador  = "\N{COMMA}";
       $separador  = "\N{SEMICOLON}"   if first { /$separador/ } @lista;
       $separador .= "\N{SPACE}";

    given (scalar @lista) {
        when (0)  { return ""                       }
        when (1)  { return "@lista"                 }
        when (2)  { return join " y " => @lista     }
        default   { return
            join($separador  => @lista[ 0 .. ($#lista-1) ])
                     . " y $lista[$#lista]";
        }
    }
}

###################################################
# Para ordenar los elementos de la lista
# en el estilo tradicional del castellano.
#
# Tenemos en cuenta que sí pueden aparecerse nombres
# de ciudades que no son nombres sólo castellanos
# sino tambíen catalanes y gallegos — y tal vez más,
# como en asturianu or aranés, pero no he pensado
# mucho es estos.
###################################################

sub ordenar_a_la_española {
    my @lista = @_;

    state $ordenador_a_la_española = new Unicode::Collate::

        # Si se tuviese Unicode::Collate::Locale con "es__traditional",
        # no haría falta este primer lío con su entrada especial,
        # con la excepción de la c-cedilla, la cual aquí se ordena
        # como si fuese catalán, no castellano.

        # Vamos a meter las nuevas entradas después de éstas,
        # que son copiadas del DUCET v6.0.0.  Tuve que cambiar unos
        # valores que tenía este código desde otra versión anterior.
        #
        # 0043  ; [.123D.0020.0008.0043] # LATIN CAPITAL LETTER C
        # 00C7  ; [.123D.0020.0008.0043][.0000.0056.0002.0327] # LATIN CAPITAL LETTER C WITH CEDILLA; QQCM
        # 004C  ; [.1330.0020.0008.004C] # LATIN CAPITAL LETTER L
        # 004E  ; [.136D.0020.0008.004E] # LATIN CAPITAL LETTER N
        # 00D1  ; [.136D.0020.0008.004E][.0000.004E.0002.0303] # LATIN CAPITAL LETTER N WITH TILDE; QQCM

        entry => <<'SALIDA',   # :)

               00E7      ; [.123E.0020.0002.0327] # c-cedilla
               0063 0327 ; [.123E.0020.0002.0327] # c-cedilla
               00C7      ; [.123E.0020.0002.0327] # C-cedilla
               0043 0327 ; [.123E.0020.0002.0327] # C-cedilla

               0063 0068 ; [.123F.0020.0002.0043] # ch
               0043 0068 ; [.123F.0020.0007.0043] # Ch
               0043 0048 ; [.123F.0020.0008.0043] # CH

               006C 006C ; [.1331.0020.0002.004C] # ll
               004C 006C ; [.1331.0020.0007.004C] # Ll
               004C 004C ; [.1331.0020.0008.004C] # LL

               00F1      ; [.136E.0020.0002.0303] # n-tilde
               006E 0303 ; [.136E.0020.0002.0303] # n-tilde
               00D1      ; [.136E.0020.0008.0303] # N-tilde
               004E 0303 ; [.136E.0020.0008.0303] # N-tilde

SALIDA

       upper_before_lower => 1,

       normalization => "NFKD",  # ¿Y porqué no?

       preprocess => sub {
           my $_ = shift;

       ###
       # no incluye los artículos definitivos ni indefinitivos
       ###

           s/^L\p{QMARK}//;    # puede encontrarse en el catalán

           s{ ^

             (?:         # del castellano
                 El
               | Los
               | La
               | Las
                         # del catalán
               | Els
               | Les
               | Sa
               | Es
                         # del gallego
               | O
               | Os
               | A
               | As
             )

             \h +

          }{}x;

        # Luego quita las palabras no-importantes interiores.

           s/\b[dl]\p{QMARK}//g;   # del catalán

           s{
               \b
               (?:
                   el  | los | la | las | de  | del | y          # ES
                 | els | les | i  | sa  | es  | dels             # CA
                 | o   | os  | a  | as  | do  | da | dos | das   # GAL
               )
               \b
           }{}gx;

          return $_;

       },   # fin de rutina preprocesadora

  ## ¡Fijaos que no borréis esta marca!
  ##     Este punto y coma marca el fin
  ##     de los argumentos del constructor
  ##     empezado ya muchas lineas arriba.
  ##   ˅
       ;  # ←←← Sí, ése — dejadlo en paz o muy tristes os quedaréis.
  ##   ˄

    return $ordenador_a_la_española->sort(@lista);
}

谢谢这个。我会更仔细地看一下。 - Sean

2
通过谷歌搜索,我发现这个问题相当普遍(我使用了查询“perl remove diacritic”)。请记住,这并不是一门“精确”的科学(即去除变音符号和将文本转化为英语)。以下是一些链接:

http://www.ahinea.com/en/tech/accented-translate.html

http://search.cpan.org/~wollmers/Text-Undiacritic-0.02/lib/Text/Undiacritic.pm

http://search.cpan.org/~ldachary/Text-Unaccent-1.08/Unaccent.pm

作为建议,可以使用一个快速且简单的方法:
  • 将字符串规范化为 D 形式(参见此 http://perldoc.perl.org/5.8.9/Unicode/Normalize.html )。例如,这将把 ''è'' 转换为 ''e'' + '' ̀ ''(组合重音,U+0300)。
  • 用空字符串替换所有符号标记(它是一个 Unicode 类)。正则表达式基于 \p{M}(它将找到所有标记)。
  • 现在你的字符串没有带变音符号的符号了,你可以进行 "简单" 的比较。
  • 但请注意,许多 "奇怪的字母" 仍然存在:例如 ßØœ。这只是一个快速且简单的方法!

我不能提供更多帮助,因为我已经很多年没用 Perl 编程了。


1

看起来你交换了参数。 你输入的是

$name =~ s|a|[áa]|;

尝试用"[áa]"替换模式"a",请尝试。
$name =~ s|[áa]|a|;

交换匹配项,它就会起作用。

$string = "Renato Núñez, David DeJesús, and Edwin Encarnación";
foreach my $name (@names) {
    print "found:$name\n" if ($string =~ /$name/);
}
  1. 自 Perl 5.6 以来,Unicode 正则表达式在 perl 中可用: http://www.regular-expressions.info/unicode.html
  2. 您是否检查过数据库编码和源代码编码(latin1 或 utf8)。

是的,在上一个命令中,我把$string和$name弄反了。但我不认为将s | [aa'] | a反转会起作用,因为它会将我的名字转换为非西班牙字符,并且它不会匹配该字符串。 - Sean

1

这可能更符合你想要做的事情。

use strict;
use warnings;

my @AngloNames = ('Renato Nunez','David DeJesus','Edwin Encarnacion');
my @AngEthRx;

for my $val (@AngloNames) {
   $_ = $val;
   s/a/[áa]/g;
   s/e/[ée]/g;
   s/i/[íi]/g;
   s/o/[óo]/g;
   s/u/[úu]/g;
   s/n/[ñn]/g;
   push @AngEthRx, $_;
}

# User input query string ...
my $AngEthQuery = "Renato Núñez, David DeJesús, and Edwin Encarnación";

for my $i (0 .. $#AngEthRx) {
   if ( $AngEthQuery =~ /($AngEthRx[$i])/ ) {
      print "found: $AngloNames[$i] ~ $1\n";
   }
}

输出

找到:Renato Nunez ~ Renato Núñez
找到:David DeJesus ~ David DeJesús
找到:Edwin Encarnacion ~ Edwin Encarnación


我会尝试这个。这看起来像之前我尝试过但没有成功的东西。这将通过网络表单发送,因此字符串中的字符可能以某种方式被编码。我需要检查一下。 - Sean

1

我相信你正在使用字符串“Renato Núñez,David DeJesús和Edwin Encarnación”作为正则表达式。

如果我理解正确,你正在尝试匹配短语“Renato Núñez,David DeJesús和Edwin Encarnación”中的每个名称。

如果是这样,那么你需要写:$string =~ /$name/而不是$name =~ /$string/


1
是的,谢谢。我搞反了。现在我已经修正了问题。 - Sean

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接