Muitas implementações modernas de regex interpretam a \w
abreviação da classe de caracteres como "qualquer letra, dígito ou pontuação de conexão" (geralmente: sublinhado). Dessa forma, uma regex como \w+
jogos de palavras como hello
, élève
, GOÄ_432
ou gefräßig
.
Infelizmente, Java não. Em Java, \w
é limitado a [A-Za-z0-9_]
. Isso dificulta palavras correspondentes como as mencionadas acima, entre outros problemas.
Parece também que o \b
separador de palavras corresponde a locais onde não deveria.
Qual seria o equivalente correto de um .NET, compatível com Unicode \w
ou \b
em Java? Quais outros atalhos precisam ser "reescritos" para torná-los compatíveis com Unicode?
java
regex
unicode
character-properties
Tim Pietzcker
fonte
fonte
Respostas:
Código fonte
O código fonte das funções de reescrita que discuto abaixo está disponível aqui .
Atualização em Java 7
A
Pattern
classe atualizada da Sun para o JDK7 possui uma maravilhosa nova flagUNICODE_CHARACTER_CLASS
, que faz tudo funcionar novamente. Está disponível como um incorporável(?U)
para dentro do padrão, para que você também possa usá-lo com osString
invólucros da classe. Também possui definições corrigidas para várias outras propriedades. Agora ele rastreia o Padrão Unicode, tanto no RL1.2 quanto no RL1.2a do UTS # 18: Expressões regulares do Unicode . Esta é uma melhoria emocionante e dramática, e a equipe de desenvolvimento deve ser elogiada por esse importante esforço.Problemas de Unicode Regex do Java
O problema com Java expressões regulares é que os Perl 1.0 escapes charclass - o que significa
\w
,\b
,\s
,\d
e seus complementos - não estão em Java estendido para trabalhar com Unicode. Sozinho entre estes,\b
goza de certos semântica prolongados, mas estes mapa nem para\w
, nem para identificadores Unicode , nem para Unicode propriedades de quebra de linha .Além disso, as propriedades POSIX em Java são acessadas desta maneira:
Esta é uma verdadeira bagunça, porque isso significa que as coisas gosto
Alpha
,Lower
eSpace
fazer não no mapa Java para o UnicodeAlphabetic
,Lowercase
ouWhitespace
propriedades. Isso é extremamente irritante. O suporte à propriedade Unicode do Java é estritamente antemilenista , com o que quero dizer que ele não suporta nenhuma propriedade Unicode lançada na última década.Não poder falar sobre espaço em branco corretamente é super irritante. Considere a seguinte tabela. Para cada um desses pontos de código, existe uma coluna de resultados J para Java e uma coluna de resultados P para Perl ou qualquer outro mecanismo de regex baseado em PCRE:
Está vendo isso?
Praticamente todos esses resultados de espaço em branco do Java são gerados de acordo com o Unicode. É realmente um grande problema. Java é apenas uma bagunça, dando respostas "erradas" de acordo com a prática existente e também de acordo com o Unicode. Além disso, o Java nem lhe dá acesso às propriedades reais do Unicode! De fato, o Java não suporta nenhuma propriedade que corresponda ao espaço em branco Unicode.
A solução para todos esses problemas e muito mais
Para lidar com esse e muitos outros problemas relacionados, ontem escrevi uma função Java para reescrever uma cadeia de caracteres padrão que reescreve essas 14 fugas de classe:
substituindo-os por coisas que realmente funcionam para corresponder ao Unicode de maneira previsível e consistente. É apenas um protótipo alfa de uma única sessão de invasão, mas é completamente funcional.
A história curta é que meu código reescreve esses 14 da seguinte maneira:
Algumas coisas a considerar ...
Isso usa para sua
\X
definição o que o Unicode agora se refere como um cluster de grafema herdado , não um cluster de grafema estendido , pois o último é um pouco mais complicado. O próprio Perl agora usa a versão mais sofisticada, mas a versão antiga ainda é perfeitamente viável para as situações mais comuns. EDIT: Veja adendo na parte inferior.O que fazer
\d
depende da sua intenção, mas o padrão é a definição Uniode. Eu posso ver as pessoas nem sempre querendo\p{Nd}
, mas às vezes[0-9]
ou\pN
.As duas definições de limite
\b
e\B
são especificamente escritas para usar a\w
definição.Essa
\w
definição é excessivamente ampla, porque pega as letras parenned e não apenas as circuladas. AOther_Alphabetic
propriedade Unicode não está disponível até o JDK7, portanto é o melhor que você pode fazer.Explorando limites
Os limites têm sido um problema desde que Larry Wall cunhou a sintaxe
\b
e o nome\B
deles para falar sobre eles para o Perl 1.0 em 1987. A chave para entender como\b
e\B
o trabalho deles é dissipar dois mitos difundidos sobre eles:\w
caracteres de palavra, não para caracteres não-palavra.Um
\b
limite significa:E esses são todos perfeitamente definidos como:
(?<=\w)
.(?=\w)
.(?<!\w)
.(?!\w)
.Portanto, uma vez que
IF-THEN
é codificado como umand
ed-juntosAB
em regexes, umor
éX|Y
, e porque oand
maior tem precedênciaor
, isso é simplesmenteAB|CD
. Portanto, tudo o\b
que significa que um limite pode ser substituído com segurança por:com o
\w
definido da maneira apropriada.(Você pode achar estranho que os componentes
A
eC
sejam opostos. Em um mundo perfeito, você deve escrever issoAB|D
, mas por um tempo eu estava perseguindo contradições de exclusão mútua nas propriedades Unicode - das quais acho que já cuidei. , mas deixei a condição dupla no limite, apenas por precaução. Além disso, torna-se mais extensível se você receber idéias extras posteriormente.)Para os
\B
não limites, a lógica é:Permitindo que todas as instâncias
\B
sejam substituídas por:É realmente assim
\b
e\B
se comporta. Padrões equivalentes para eles são\b
usando a((IF)THEN|ELSE)
construção é(?(?<=\w)(?!\w)|(?=\w))
\B
usando a((IF)THEN|ELSE)
construção é(?(?=\w)(?<=\w)|(?<!\w))
Mas as versões com apenas
AB|CD
são boas, especialmente se você não tiver padrões condicionais em sua linguagem regex - como Java. ☹Eu já verifiquei o comportamento dos limites usando todas as três definições equivalentes com um conjunto de testes que verifica 110.385.408 correspondências por execução e que eu executei em uma dúzia de configurações de dados diferentes de acordo com:
No entanto, as pessoas geralmente querem um tipo diferente de limite. Eles querem algo com espaço em branco e com conhecimento de borda de cadeia:
(?:(?<=^)|(?<=\s))
(?=$|\s)
Corrigindo Java com Java
O código que publiquei em minha outra resposta fornece essa e várias outras conveniências. Isso inclui definições para palavras em linguagem natural, traços, hífens e apóstrofos, além de um pouco mais.
Também permite especificar caracteres Unicode em pontos de código lógico, não em substitutos idiotas do UTF-16. É difícil enfatizar o quanto isso é importante! E isso é apenas para a expansão de strings.
Para a substituição de classe de classe regex que faz com que a classe em suas expressões regulares em Java finalmente funcione em Unicode e funcione corretamente, pegue a fonte completa a partir daqui . Você pode fazer o que quiser, é claro. Se você corrigir isso, eu adoraria ouvir, mas você não precisa. É bem curto. A essência da principal função de regravação de regex é simples:
Enfim, esse código é apenas uma versão alfa, coisa que eu hackeei no fim de semana. Não vai ficar assim.
Para a versão beta, pretendo:
dobre a duplicação de código
forneça uma interface mais clara sobre escapes de string sem escape versus aumento de escapes de regex
fornecer alguma flexibilidade na
\d
expansão, e talvez o\b
forneça métodos de conveniência que tratam de virar e chamar Pattern.compile ou String.matches ou outros enfeites para você
Para liberação de produção, ele deve ter javadoc e um conjunto de testes JUnit. Posso incluir meu gigatester, mas não está escrito como testes JUnit.
Termo aditivo
Eu tenho boas e más notícias.
A boa notícia é que agora eu tenho uma aproximação muito próxima de um cluster de grafema estendido para usar para melhorar
\X
.A má notícia ☺ é que esse padrão é:
que em Java você escreveria como:
¡Tschüß!
fonte
t
@tchrist. Pode ir à minha cabeça. :)É realmente lamentável que
\w
não funcione. A solução proposta\p{Alpha}
também não funciona para mim.Parece
[\p{L}]
pegar todas as letras Unicode. Portanto, o equivalente Unicode de\w
deveria ser[\p{L}\p{Digit}_]
.fonte
\w
também corresponde a dígitos e muito mais. Eu acho que por apenas letras,\p{L}
funcionaria.\p{L}
basta. Também pensei que apenas as letras eram o problema.[\p{L}\p{Digit}_]
deve pegar todos os caracteres alfanuméricos, incluindo sublinhado.\w
é definido pelo Unicode como sendo muito mais amplo do que apenas\pL
e os dígitos ASCII, de todas as coisas tolas. Você deve escrever[\pL\pM\p{Nd}\p{Nl}\p{Pc}[\p{InEnclosedAlphanumerics}&&\p{So}]]
se quiser um compatível com Unicode\w
para Java - ou você pode simplesmente usar minhaunicode_charclass
função a partir daqui . Desculpe!\pL
funcionam (você não precisa adotar adereços de uma letra). No entanto, você raramente deseja isso, porque é necessário ter cuidado para que sua correspondência não obtenha respostas diferentes, apenas porque seus dados estão no Formulário de Normalização D Unicode (também conhecido como NFD, que significa decomposição canônica ), em vez de estar em NFC (NFD seguido por canônico). composição ). Um exemplo é que o ponto de código U + E9 ("é"
) é um\pL
formato NFC, mas seu formato NFD se torna U + 65.301, correspondendo\pL\pM
. Você pode tipo de contornar isso com\X
:(?:(?=\pL)\X)
, mas você vai precisar da minha versão do que em Java. :(Em Java
\w
e\d
não são compatíveis com Unicode; eles correspondem apenas aos caracteres ASCII[A-Za-z0-9_]
e[0-9]
. O mesmo vale para os\p{Alpha}
amigos (as "classes de caracteres" POSIX nas quais elas se baseiam devem ser sensíveis ao código do idioma, mas em Java elas apenas correspondem aos caracteres ASCII). Se você deseja corresponder os "caracteres da palavra" Unicode, é necessário explicá-lo, por exemplo[\pL\p{Mn}\p{Nd}\p{Pc}]
, para letras, modificadores não espaçadores (acentos), dígitos decimais e pontuação de conexão.No entanto, o Java
\b
é compatível com Unicode; ele usaCharacter.isLetterOrDigit(ch)
e verifica também letras acentuadas, mas o único caractere de "pontuação de conexão" que reconhece é o sublinhado. EDIT: quando tento seu código de exemplo, ele é impresso""
eélève"
como deveria ( veja em ideone.com ).fonte
\b
é habilitado para Unicode. Comete toneladas e toneladas de erros."\u2163="
,"\u24e7="
E"\u0301="
tudo não padrão combinado"\\b="
em Java, mas são supostamente para - comoperl -le 'print /\b=/ || 0 for "\x{2163}=", "\x{24e7}=", "\x{301}="'
revela. No entanto, se (e somente se) você trocar na minha versão de um limite de palavras em vez do nativo\b
em Java, todos eles também funcionarão em Java.\b
correção, apenas apontando que ele opera com caracteres Unicode (conforme implementado em Java), não apenas como\w
amigos e amigos do ASCII . No entanto, ela não funciona corretamente com relação a\u0301
quando esse personagem está emparelhado com um caractere base, como eme\u0301=
. E não estou convencido de que Java esteja errado neste caso. Como uma marca combinada pode ser considerada um caractere de palavra, a menos que faça parte de um cluster de grafema com uma letra?\X
significa uma não-marca seguida por qualquer número de marcas, é problemática, porque você deve ser capaz de descrever todos os arquivos como correspondências/^(\X*\R)*\R?$/
, mas não pode se tiver uma\pM
no início de o arquivo, ou mesmo de uma linha. Portanto, eles tentaram sempre corresponder a pelo menos um caractere. Sempre funcionou, mas agora faz o padrão acima funcionar. [... continuação ...]\b
seja parcialmente compatível com Unicode. Considere combinar a sequência"élève"
com o padrão\b(\w+)\b
. Vê o problema?\w+
encontra duas correspondências:l
eve
, o que é ruim o suficiente. Mas, com os limites das palavras, não encontra nada, porque\b
reconheceé
eè
como caracteres das palavras. No mínimo,\b
e\w
deve concordar com o que é um caractere de palavra e o que não é.