How do you match accented and tilde characters in a perl regular expression (regexp)?

12,940

Solution 1

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

However, this is almost always the wrong(est) thing to do. What are you going to do about

  • Ævar Arnfjörð
  • Dženan Ljubović
  • King Henry Ⅷ
  • Carlos Ⅴº, el Emperador

Just embrace Unicode. The correct way to match things with or without diacritics is to instantiate a Unicode::Collator object with the strength set to ignore diacritics. Then just call the cmp or eq methods.

EDIT

This is how you should go about these things. Witness:

«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»

And here is the code that generates that.

#!/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 <[email protected]>
# 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);
}

Solution 2

Googling for it, I see the problem is quite common (I used the query "perl remove diacritic"). Remember that it isn't an "exact" science (removing diacritics and anglicizing text). Here there are some links:

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

As a suggestion, for a quick-n-dirty method:

  • Normalize the string in Normalization Form D (see this http://perldoc.perl.org/5.8.9/Unicode/Normalize.html ). This will change for example the ''è'' to ''e'' + '' ̀ '' (the combining grave, U+0300).
  • Replace all the Marks (it's an Unicode class) with string empty. The regex is based on the \p{M} (it will find all the Marks)
  • Now your string doesn't have symbols with diacritics, and you can do a "simple" comparison
  • But note that many "strange letters" survived: ßØœ for example. This was a quick-n-dirty n the end!

I can't help you more because it's many years I don't program in Perl.

Solution 3

I believe you are using as the regexp the string "Renato Núñez, David DeJesús, and Edwin Encarnación"

If I understand correctly, you are trying to match each one of the names within the phrase "Renato Núñez, David DeJesús, and Edwin Encarnación".

If that is the case, then you need to write: $string =~ /$name/ instead of $name =~ /$string/

Solution 4

It seems that you swap parameter. You type

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

Which try to replace pattern "a" by "[áa]" Try

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

Swap the match and it'll work.

$string = "Renato Núñez, David DeJesús, and Edwin Encarnación";
foreach my $name (@names) {
    print "found:$name\n" if ($string =~ /$name/);
}
  1. Unicode regular expressions works in perl since perl 5.6: http://www.regular-expressions.info/unicode.html
  2. Did you check the database encoding en your source code encoding (latin1 or utf8).
Share:
12,940

Related videos on Youtube

Sean
Author by

Sean

Updated on October 13, 2020

Comments

  • Sean
    Sean over 3 years

    A user enters a set of names with accents and tildes:

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

    My database has anglicized names for these people

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

    I wish to do a regexp match on these names.

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

    As currently presented I get no matches.

    I tried this, but it didn't work.

    $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/);
    }
    

    EDIT: sorry I had $name and $string reversed in the last line.

    Any suggestions?

    • mob
      mob about 13 years
      Suggestion 1: Regular expression operators in perldoc perlop. I think you want to say $string =~ /$name/ instead of $name =~ /$string/, and s|[áa]|a| instead of s|a|[áa]|.
    • Sean
      Sean about 13 years
      i have the s||| ordering the way I do because I want to build a regex from the string "David DeJesus" that will match the name with or without the accent.
  • Sean
    Sean about 13 years
    Yes, I had $string and $name reversed in the last command. But i don't think that reversing s|[aa']|a| will work as that will convert my names into non-spanish characters and it won't match the string?

Related