Regex: o que é InCombiningDiacriticalMarks?

86

O código a seguir é muito conhecido por converter caracteres acentuados em texto simples:

Normalizer.normalize(text, Normalizer.Form.NFD).replaceAll("\\p{InCombiningDiacriticalMarks}+", "");

Substituí meu método "feito à mão" por este, mas preciso entender a parte "regex" do replaceAll

1) O que é "InCombiningDiacriticalMarks"?
2) Onde está a documentação disso? (e similares?)

Obrigado.

marcolopes
fonte
Consulte também stackoverflow.com/a/29111105/32453, aparentemente, há mais "marcas de combinação" em Unicode do que apenas as diacríticas, apenas como uma observação.
rogerdpack de

Respostas:

74

\p{InCombiningDiacriticalMarks}é uma propriedade de bloco Unicode. No JDK7, você poderá escrevê-lo usando a notação de duas partes \p{Block=CombiningDiacriticalMarks}, o que pode ser mais claro para o leitor. Está documentado aqui em UAX # 44: “The Unicode Character Database” .

O que isso significa é que o ponto de código está dentro de um intervalo específico, um bloco, que foi alocado para uso para as coisas com aquele nome. Esta é uma abordagem ruim, porque não há garantia de que o ponto de código nesse intervalo seja ou não uma coisa particular, nem que os pontos de código fora desse bloco não sejam essencialmente do mesmo caractere.

Por exemplo, existem letras latinas no \p{Latin_1_Supplement}bloco, como é, U + 00E9. No entanto, há coisas que não são letras latinas também. E, claro, também há letras latinas por todo o lugar.

Os blocos quase nunca são o que você deseja.

Nesse caso, suspeito que você queira usar a propriedade \p{Mn}, também conhecida como \p{Nonspacing_Mark}. Todos os pontos de código no bloco Combining_Diacriticals são desse tipo. Existem também (a partir do Unicode 6.0.0) 1087 Nonspacing_Marks que não estão naquele bloco.

Isso é quase o mesmo que verificar \p{Bidi_Class=Nonspacing_Mark}, mas não exatamente, porque esse grupo também inclui as marcas delimitadoras \p{Me},. Se você quiser os dois, pode dizer [\p{Mn}\p{Me}]se está usando um mecanismo regex Java padrão, já que ele só dá acesso à propriedade General_Category.

Você teria que usar JNI para chegar à biblioteca regex ICU C ++ da maneira que o Google faz para acessar algo como \p{BC=NSM}, porque agora apenas ICU e Perl dão acesso a todas as propriedades Unicode. A biblioteca Java regex normal suporta apenas algumas propriedades Unicode padrão. No entanto, no JDK7 haverá suporte para a propriedade Unicode Script, que é quase infinitamente preferível à propriedade Block. Assim, você pode escrever em JDK7 \p{Script=Latin}ou \p{SC=Latin}, ou o atalho \p{Latin}, para obter qualquer caractere do script latino. Isso leva ao muito comumente necessário [\p{Latin}\p{Common}\p{Inherited}].

Esteja ciente de que isso não removerá o que você pode pensar como marcas de “acento” de todos os personagens! Há muitos pelos quais ele não fará isso. Por exemplo, você não pode converter Đ em D ou ø em o dessa maneira. Para isso, você precisa reduzir os pontos de código para aqueles que correspondem à mesma intensidade de agrupamento primário na Tabela de agrupamento Unicode.

Outro ponto onde a \p{Mn}coisa falha é, claro, marcas encerradas como \p{Me}, obviamente, mas também há \p{Diacritic}caracteres que não são marcas. Infelizmente, você precisa de suporte total de propriedade para isso, o que significa JNI para ICU ou Perl. Java tem muitos problemas com o suporte a Unicode, infelizmente.

Oh espere, vejo que você é português. Você não deverá ter problemas se estiver lidando apenas com texto em português.

No entanto, você não quer realmente remover os acentos, aposto, mas sim ser capaz de combinar as coisas “de maneira insensível”, certo? Em caso afirmativo, você pode fazer isso usando a classe de intercalador ICU4J (ICU para Java) . Se você comparar na intensidade primária, os acentos não contarão. Eu faço isso o tempo todo porque frequentemente processo textos em espanhol. Eu tenho um exemplo de como fazer isso para o espanhol sentado aqui em algum lugar, se você precisar.

tchrist
fonte
Portanto, devo assumir que o método fornecido em toda a web (e mesmo aqui no SO) não é o recomendado para uma palavra "DeAccent". Fiz um direto só para o português, mas vi essa abordagem estranha (e como você disse, funciona para o meu propósito, mas meu último método funcionou!). Portanto, existe uma abordagem melhor "bem implementada" que cubra a maioria dos cenários? Um exemplo seria muito bom. Obrigado pelo seu tempo.
marcolopes
1
@Marcolopes: Tenho deixado os dados intactos e usando o Unicode Collation Algorithm para fazer comparações de força primária. Dessa forma, ele apenas compara letras, mas ignora maiúsculas e minúsculas e acentos. Também permite que coisas que deveriam ser a mesma letra sejam a mesma letra, o que remover os acentos é apenas uma aproximação pálida e insatisfatória. Além disso, é mais limpo não zapear os dados se você puder trabalhar com eles de uma maneira que faça o que você deseja, mas não exija isso.
tchrist,
Muito boa resposta, uma pergunta, porém, posso usar o Normalizer em java e usar InCombiningDiacriticalMarks, mas excluir alguns caracteres, como ü da conversão para u?
AlexCon
6
sim, eu entendi
perfeitamente
4

Demorei um pouco, mas pesquei todos eles:

Aqui está o regex que deve incluir todos os caracteres zalgo, incluindo aqueles ignorados no intervalo 'normal'.

([\u0300–\u036F\u1AB0–\u1AFF\u1DC0–\u1DFF\u20D0–\u20FF\uFE20–\uFE2F\u0483-\u0486\u05C7\u0610-\u061A\u0656-\u065F\u0670\u06D6-\u06ED\u0711\u0730-\u073F\u0743-\u074A\u0F18-\u0F19\u0F35\u0F37\u0F72-\u0F73\u0F7A-\u0F81\u0F84\u0e00-\u0eff\uFC5E-\uFC62])

Espero que isso economize algum tempo.

Matas Vaitkevicius
fonte