Eliminar las marcas diacríticas (â € ň大 ñ cuya ṇ?) de los caracteres Unicode


Estoy viendo un algoritmo que puede mapear entre caracteres con diacríticos ( tilde, circunflejo, caret, diéresis, caron ) y su carácter "simple".

Por ejemplo:

ń  ǹ  ň  ñ  ṅ  ņ  ṇ  ṋ  ṉ  ̈  ɲ  ƞ ᶇ ɳ ȵ  --> n
á --> a
ä --> a
ấ --> a
ṏ --> o

Etc.

  1. Quiero hacer esto en Java, aunque sospecho que debería ser algo Unicode-y y debería ser factible razonablemente fácilmente en cualquier lenguaje.

  2. Propósito: para permitir la búsqueda fácil de palabras con marcas diacríticas. Por ejemplo, si tengo una base de datos de jugadores de tenis, y Björn_Borg se introduce, también voy a mantener Bjorn_Borg para que pueda encontrarlo si alguien entra Bjorn y no Björn.

Author: halfer, 2009-09-21

12 answers

He hecho esto recientemente en Java:

public static final Pattern DIACRITICS_AND_FRIENDS
    = Pattern.compile("[\\p{InCombiningDiacriticalMarks}\\p{IsLm}\\p{IsSk}]+");

private static String stripDiacritics(String str) {
    str = Normalizer.normalize(str, Normalizer.Form.NFD);
    str = DIACRITICS_AND_FRIENDS.matcher(str).replaceAll("");
    return str;
}

Esto hará como usted especificó:

stripDiacritics("Björn")  = Bjorn

Pero fallará en, por ejemplo, Białystok, porque el carácter ł no es diacrítico.

Si desea tener un simplificador de cadenas completo, necesitará una segunda ronda de limpieza, para algunos caracteres especiales más que no sean diacríticos. Es este mapa, he incluido los caracteres especiales más comunes que aparecen en los nombres de nuestros clientes. No es una lista completa, pero darle la idea de cómo hacer extenderlo. El ImmutableMap es solo una clase simple de Google-collections.

public class StringSimplifier {
    public static final char DEFAULT_REPLACE_CHAR = '-';
    public static final String DEFAULT_REPLACE = String.valueOf(DEFAULT_REPLACE_CHAR);
    private static final ImmutableMap<String, String> NONDIACRITICS = ImmutableMap.<String, String>builder()

        //Remove crap strings with no sematics
        .put(".", "")
        .put("\"", "")
        .put("'", "")

        //Keep relevant characters as seperation
        .put(" ", DEFAULT_REPLACE)
        .put("]", DEFAULT_REPLACE)
        .put("[", DEFAULT_REPLACE)
        .put(")", DEFAULT_REPLACE)
        .put("(", DEFAULT_REPLACE)
        .put("=", DEFAULT_REPLACE)
        .put("!", DEFAULT_REPLACE)
        .put("/", DEFAULT_REPLACE)
        .put("\\", DEFAULT_REPLACE)
        .put("&", DEFAULT_REPLACE)
        .put(",", DEFAULT_REPLACE)
        .put("?", DEFAULT_REPLACE)
        .put("°", DEFAULT_REPLACE) //Remove ?? is diacritic?
        .put("|", DEFAULT_REPLACE)
        .put("<", DEFAULT_REPLACE)
        .put(">", DEFAULT_REPLACE)
        .put(";", DEFAULT_REPLACE)
        .put(":", DEFAULT_REPLACE)
        .put("_", DEFAULT_REPLACE)
        .put("#", DEFAULT_REPLACE)
        .put("~", DEFAULT_REPLACE)
        .put("+", DEFAULT_REPLACE)
        .put("*", DEFAULT_REPLACE)

        //Replace non-diacritics as their equivalent characters
        .put("\u0141", "l") // BiaLystock
        .put("\u0142", "l") // Bialystock
        .put("ß", "ss")
        .put("æ", "ae")
        .put("ø", "o")
        .put("©", "c")
        .put("\u00D0", "d") // All Ð ð from http://de.wikipedia.org/wiki/%C3%90
        .put("\u00F0", "d")
        .put("\u0110", "d")
        .put("\u0111", "d")
        .put("\u0189", "d")
        .put("\u0256", "d")
        .put("\u00DE", "th") // thorn Þ
        .put("\u00FE", "th") // thorn þ
        .build();


    public static String simplifiedString(String orig) {
        String str = orig;
        if (str == null) {
            return null;
        }
        str = stripDiacritics(str);
        str = stripNonDiacritics(str);
        if (str.length() == 0) {
            // Ugly special case to work around non-existing empty strings
            // in Oracle. Store original crapstring as simplified.
            // It would return an empty string if Oracle could store it.
            return orig;
        }
        return str.toLowerCase();
    }

    private static String stripNonDiacritics(String orig) {
        StringBuffer ret = new StringBuffer();
        String lastchar = null;
        for (int i = 0; i < orig.length(); i++) {
            String source = orig.substring(i, i + 1);
            String replace = NONDIACRITICS.get(source);
            String toReplace = replace == null ? String.valueOf(source) : replace;
            if (DEFAULT_REPLACE.equals(lastchar) && DEFAULT_REPLACE.equals(toReplace)) {
                toReplace = "";
            } else {
                lastchar = toReplace;
            }
            ret.append(toReplace);
        }
        if (ret.length() > 0 && DEFAULT_REPLACE_CHAR == ret.charAt(ret.length() - 1)) {
            ret.deleteCharAt(ret.length() - 1);
        }
        return ret.toString();
    }

    /*
    Special regular expression character ranges relevant for simplification -> see http://docstore.mik.ua/orelly/perl/prog3/ch05_04.htm
    InCombiningDiacriticalMarks: special marks that are part of "normal" ä, ö, î etc..
        IsSk: Symbol, Modifier see http://www.fileformat.info/info/unicode/category/Sk/list.htm
        IsLm: Letter, Modifier see http://www.fileformat.info/info/unicode/category/Lm/list.htm
     */
    public static final Pattern DIACRITICS_AND_FRIENDS
        = Pattern.compile("[\\p{InCombiningDiacriticalMarks}\\p{IsLm}\\p{IsSk}]+");


    private static String stripDiacritics(String str) {
        str = Normalizer.normalize(str, Normalizer.Form.NFD);
        str = DIACRITICS_AND_FRIENDS.matcher(str).replaceAll("");
        return str;
    }
}
 70
Author: Andreas Petersson,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-04-09 07:02:24

El núcleo java.el paquete de texto fue diseñado para abordar este caso de uso (cadenas coincidentes sin preocuparse por los diacríticos, el caso, etc.).

Configurar una Collator para ordenar en PRIMARY diferencias de caracteres. Con eso, crear un CollationKey para cada cadena. Si todo su código está en Java, puede usar el CollationKey directamente. Si necesita almacenar las claves en una base de datos u otro tipo de índice, puede convertirlo en una matriz de bytes.

Estas clases utilizan el Unicode standard case folding data para determinar qué caracteres son equivalentes, y soporta varias estrategias de descomposición .

Collator c = Collator.getInstance();
c.setStrength(Collator.PRIMARY);
Map<CollationKey, String> dictionary = new TreeMap<CollationKey, String>();
dictionary.put(c.getCollationKey("Björn"), "Björn");
...
CollationKey query = c.getCollationKey("bjorn");
System.out.println(dictionary.get(query)); // --> "Björn"

Tenga en cuenta que los colladores son específicos de la configuración regional. Esto se debe a que el" orden alfabético " es diferente entre los lugares (e incluso con el tiempo, como ha sido el caso con el español). La clase Collator le libera de tener que rastrear todas estas reglas y mantenerlas actualizadas.

 23
Author: erickson,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2009-09-21 14:32:03

Es parte de Apache Commons Lang a partir de ver. 3.1.

org.apache.commons.lang3.StringUtils.stripAccents("Añ");

Devuelve An

 13
Author: Kenston Choi,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-09-22 09:11:56

Puedes usar la clase Normalizer de java.text:

System.out.println(new String(Normalizer.normalize("ń ǹ ň ñ ṅ ņ ṇ ṋ", Normalizer.Form.NFKD).getBytes("ascii"), "ascii"));

Pero todavía hay trabajo por hacer, ya que Java hace cosas extrañas con caracteres Unicode inconvertibles (no los ignora, y no lanza una excepción). Pero creo que podrías usar eso como punto de partida.

 11
Author: nils,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2012-11-25 10:41:21

Hay un borrador de informe sobre el plegado de caracteres en el sitio web de unicode que tiene una gran cantidad de material relevante. Ver específicamente la Sección 4.1. "Algoritmo de plegado".

Aquí hay una discusión e implementación de la eliminación de marcadores diacríticos usando Perl.

Estas preguntas SO existentes están relacionadas:

 10
Author: ire_and_curses,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-05-23 11:47:04

Tenga en cuenta que no todas estas marcas son solo "marcas" en algún carácter "normal", que puede eliminar sin cambiar el significado.

En sueco, å ä y ö son verdaderos y propios caracteres de primera clase, no una "variante" de algún otro carácter. Suenan diferentes de todos los demás personajes, son diferentes y hacen que las palabras cambien de significado ("mätt "y" matt " son dos palabras diferentes).

 4
Author: unwind,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2010-03-01 15:46:15

Unicode tiene caracteres diátricos específicos (que son caracteres compuestos) y se puede convertir una cadena de modo que el carácter y las diátricas estén separados. Entonces, puedes simplemente quitar las diatrices de la cadena y básicamente estás hecho.

Para obtener más información sobre normalización, descomposiciones y equivalencia, consulte el estándar Unicode en la página de inicio Unicode.

Sin embargo, cómo se puede lograr esto depende del framework/OS/... estás trabajando. Si si estás usando. NET, puedes usar la cadena .Normalizar el método aceptando el sistema .Texto.NormalizationForm enumeration.

 2
Author: Lucero,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2009-09-21 07:10:10

La forma más fácil (para mí) sería simplemente mantener una matriz de asignación dispersa que simplemente cambia sus puntos de código Unicode en cadenas visualizables.

Tales como:

start    = 0x00C0
size     = 23
mappings = {
    "A","A","A","A","A","A","AE","C",
    "E","E","E","E","I","I","I", "I",
    "D","N","O","O","O","O","O"
}
start    = 0x00D8
size     = 6
mappings = {
    "O","U","U","U","U","Y"
}
start    = 0x00E0
size     = 23
mappings = {
    "a","a","a","a","a","a","ae","c",
    "e","e","e","e","i","i","i", "i",
    "d","n","o","o","o","o","o"
}
start    = 0x00F8
size     = 6
mappings = {
    "o","u","u","u","u","y"
}
: : :

El uso de una matriz sparse le permitirá representar eficientemente reemplazos incluso cuando estén en secciones ampliamente espaciadas de la tabla Unicode. Los reemplazos de cadenas permitirán que secuencias arbitrarias reemplacen sus diacríticos (como el grafema æ convirtiéndose en ae).

Esto es un respuesta agnóstica del lenguaje por lo tanto, si tiene un lenguaje específico en mente, habrá mejores maneras (aunque es probable que todos desciendan a esto en los niveles más bajos de todos modos).

 2
Author: paxdiablo,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2009-09-21 07:46:57

Algo a considerar: si usted va la ruta de tratar de obtener una sola "traducción" de cada palabra, es posible que se pierda algunas posibles alternativas.

Por ejemplo, en alemán, al reemplazar el "s-set", algunas personas podrían usar "B", mientras que otros podrían usar "ss". O bien, sustituyendo una o disimulada por "o " u"oe". Cualquier solución que se te ocurra, idealmente, creo que debería incluir ambas.

 2
Author: Beska,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2009-09-21 14:58:09

En Windows y.NET, simplemente convierto usando codificación de cadenas. De esa manera evito el mapeo manual y la codificación.

Intenta jugar con la codificación de cadenas.

 2
Author: Viktor Jevdokimov,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2012-11-25 10:43:32

En el caso del alemán no se quiere eliminar los diacríticos de los diéresis (ä, ö, ü). En su lugar, se reemplazan por una combinación de dos letras (ae, oe, ue) Por ejemplo, Björn debería escribirse como Bjoern (no Bjorn) para tener una pronunciación correcta.

Para eso, preferiría una asignación codificada, donde puede definir la regla de reemplazo individualmente para cada grupo de caracteres especiales.

 2
Author: jalbert,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2013-02-08 10:16:12

Para referencia futura, aquí hay un método de extensión de C# que elimina los acentos.

public static class StringExtensions
{
    public static string RemoveDiacritics(this string str)
    {
        return new string(
            str.Normalize(NormalizationForm.FormD)
                .Where(c => CharUnicodeInfo.GetUnicodeCategory(c) != 
                            UnicodeCategory.NonSpacingMark)
                .ToArray());
    }
}
static void Main()
{
    var input = "ŃŅŇ ÀÁÂÃÄÅ ŢŤţť Ĥĥ àáâãäå ńņň";
    var output = input.RemoveDiacritics();
    Debug.Assert(output == "NNN AAAAAA TTtt Hh aaaaaa nnn");
}
 1
Author: Nathan Baulch,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2009-09-26 17:06:42