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 flag UNICODE_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 os String
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:
POSIX syntax Java syntax
[[:Lower:]] \p{Lower}
[[:Upper:]] \p{Upper}
[[:ASCII:]] \p{ASCII}
[[:Alpha:]] \p{Alpha}
[[:Digit:]] \p{Digit}
[[:Alnum:]] \p{Alnum}
[[:Punct:]] \p{Punct}
[[:Graph:]] \p{Graph}
[[:Print:]] \p{Print}
[[:Blank:]] \p{Blank}
[[:Cntrl:]] \p{Cntrl}
[[:XDigit:]] \p{XDigit}
[[:Space:]] \p{Space}
Esta é uma verdadeira bagunça, porque isso significa que as coisas gosto Alpha
, Lower
e Space
fazer não no mapa Java para o Unicode Alphabetic
, Lowercase
ou Whitespace
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:
Regex 001A 0085 00A0 2029
J P J P J P J P
\s 1 1 0 1 0 1 0 1
\pZ 0 0 0 0 1 1 1 1
\p{Zs} 0 0 0 0 1 1 0 0
\p{Space} 1 1 0 1 0 1 0 1
\p{Blank} 0 0 0 0 0 1 0 0
\p{Whitespace} - 1 - 1 - 1 - 1
\p{javaWhitespace} 1 - 0 - 0 - 1 -
\p{javaSpaceChar} 0 - 0 - 1 - 1 -
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:
\w \W \s \S \v \V \h \H \d \D \b \B \X \R
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:
\s => [\u0009-\u000D\u0020\u0085\u00A0\u1680\u180E\u2000-\u200A\u2028\u2029\u202F\u205F\u3000]
\S => [^\u0009-\u000D\u0020\u0085\u00A0\u1680\u180E\u2000-\u200A\u2028\u2029\u202F\u205F\u3000]
\v => [\u000A-\u000D\u0085\u2028\u2029]
\V => [^\u000A-\u000D\u0085\u2028\u2029]
\h => [\u0009\u0020\u00A0\u1680\u180E\u2000-\u200A\u202F\u205F\u3000]
\H => [^\u0009\u0020\u00A0\u1680\u180E\u2000\u2001-\u200A\u202F\u205F\u3000]
\w => [\pL\pM\p{Nd}\p{Nl}\p{Pc}[\p{InEnclosedAlphanumerics}&&\p{So}]]
\W => [^\pL\pM\p{Nd}\p{Nl}\p{Pc}[\p{InEnclosedAlphanumerics}&&\p{So}]]
\b => (?:(?<=[\pL\pM\p{Nd}\p{Nl}\p{Pc}[\p{InEnclosedAlphanumerics}&&\p{So}]])(?![\pL\pM\p{Nd}\p{Nl}\p{Pc}[\p{InEnclosedAlphanumerics}&&\p{So}]])|(?<![\pL\pM\p{Nd}\p{Nl}\p{Pc}[\p{InEnclosedAlphanumerics}&&\p{So}]])(?=[\pL\pM\p{Nd}\p{Nl}\p{Pc}[\p{InEnclosedAlphanumerics}&&\p{So}]]))
\B => (?:(?<=[\pL\pM\p{Nd}\p{Nl}\p{Pc}[\p{InEnclosedAlphanumerics}&&\p{So}]])(?=[\pL\pM\p{Nd}\p{Nl}\p{Pc}[\p{InEnclosedAlphanumerics}&&\p{So}]])|(?<![\pL\pM\p{Nd}\p{Nl}\p{Pc}[\p{InEnclosedAlphanumerics}&&\p{So}]])(?![\pL\pM\p{Nd}\p{Nl}\p{Pc}[\p{InEnclosedAlphanumerics}&&\p{So}]]))
\d => \p{Nd}
\D => \P{Nd}
\R => (?:(?>\u000D\u000A)|[\u000A\u000B\u000C\u000D\u0085\u2028\u2029])
\X => (?>\PM\pM*)
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. A Other_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:
- Eles são sempre apenas olhando para
\w
caracteres de palavra, não para caracteres não-palavra.
- Eles não procuram especificamente a borda da string.
Um \b
limite significa:
IF does follow word
THEN doesn't precede word
ELSIF doesn't follow word
THEN does precede word
E esses são todos perfeitamente definidos como:
- segue a palavra é
(?<=\w)
.
- precede a palavra é
(?=\w)
.
- não segue a palavra é
(?<!\w)
.
- não precede a palavra é
(?!\w)
.
Portanto, uma vez que IF-THEN
é codificado como um and
ed-juntos AB
em regexes, um or
é X|Y
, e porque o and
maior tem precedência or
, isso é simplesmente AB|CD
. Portanto, tudo o \b
que significa que um limite pode ser substituído com segurança por:
(?:(?<=\w)(?!\w)|(?<!\w)(?=\w))
com o \w
definido da maneira apropriada.
(Você pode achar estranho que os componentes A
e C
sejam opostos. Em um mundo perfeito, você deve escrever isso AB|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 é:
IF does follow word
THEN does precede word
ELSIF doesn't follow word
THEN doesn't precede word
Permitindo que todas as instâncias \B
sejam substituídas por:
(?:(?<=\w)(?=\w)|(?<!\w)(?!\w))
É 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:
0 .. 7F the ASCII range
80 .. FF the non-ASCII Latin1 range
100 .. FFFF the non-Latin1 BMP (Basic Multilingual Plane) range
10000 .. 10FFFF the non-BMP portion of Unicode (the "astral" planes)
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:
- borda esquerda como
(?:(?<=^)|(?<=\s))
- borda direita como
(?=$|\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:
switch (code_point) {
case 'b': newstr.append(boundary);
break; /* switch */
case 'B': newstr.append(not_boundary);
break; /* switch */
case 'd': newstr.append(digits_charclass);
break; /* switch */
case 'D': newstr.append(not_digits_charclass);
break; /* switch */
case 'h': newstr.append(horizontal_whitespace_charclass);
break; /* switch */
case 'H': newstr.append(not_horizontal_whitespace_charclass);
break; /* switch */
case 'v': newstr.append(vertical_whitespace_charclass);
break; /* switch */
case 'V': newstr.append(not_vertical_whitespace_charclass);
break; /* switch */
case 'R': newstr.append(linebreak);
break; /* switch */
case 's': newstr.append(whitespace_charclass);
break; /* switch */
case 'S': newstr.append(not_whitespace_charclass);
break; /* switch */
case 'w': newstr.append(identifier_charclass);
break; /* switch */
case 'W': newstr.append(not_identifier_charclass);
break; /* switch */
case 'X': newstr.append(legacy_grapheme_cluster);
break; /* switch */
default: newstr.append('\\');
newstr.append(Character.toChars(code_point));
break; /* switch */
}
saw_backslash = false;
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 é:
(?:(?:\u000D\u000A)|(?:[\u0E40\u0E41\u0E42\u0E43\u0E44\u0EC0\u0EC1\u0EC2\u0EC3\u0EC4\uAAB5\uAAB6\uAAB9\uAABB\uAABC]*(?:[\u1100-\u115F\uA960-\uA97C]+|([\u1100-\u115F\uA960-\uA97C]*((?:[[\u1160-\u11A2\uD7B0-\uD7C6][\uAC00\uAC1C\uAC38]][\u1160-\u11A2\uD7B0-\uD7C6]*|[\uAC01\uAC02\uAC03\uAC04])[\u11A8-\u11F9\uD7CB-\uD7FB]*))|[\u11A8-\u11F9\uD7CB-\uD7FB]+|[^[\p{Zl}\p{Zp}\p{Cc}\p{Cf}&&[^\u000D\u000A\u200C\u200D]]\u000D\u000A])[[\p{Mn}\p{Me}\u200C\u200D\u0488\u0489\u20DD\u20DE\u20DF\u20E0\u20E2\u20E3\u20E4\uA670\uA671\uA672\uFF9E\uFF9F][\p{Mc}\u0E30\u0E32\u0E33\u0E45\u0EB0\u0EB2\u0EB3]]*)|(?s:.))
que em Java você escreveria como:
String extended_grapheme_cluster = "(?:(?:\\u000D\\u000A)|(?:[\\u0E40\\u0E41\\u0E42\\u0E43\\u0E44\\u0EC0\\u0EC1\\u0EC2\\u0EC3\\u0EC4\\uAAB5\\uAAB6\\uAAB9\\uAABB\\uAABC]*(?:[\\u1100-\\u115F\\uA960-\\uA97C]+|([\\u1100-\\u115F\\uA960-\\uA97C]*((?:[[\\u1160-\\u11A2\\uD7B0-\\uD7C6][\\uAC00\\uAC1C\\uAC38]][\\u1160-\\u11A2\\uD7B0-\\uD7C6]*|[\\uAC01\\uAC02\\uAC03\\uAC04])[\\u11A8-\\u11F9\\uD7CB-\\uD7FB]*))|[\\u11A8-\\u11F9\\uD7CB-\\uD7FB]+|[^[\\p{Zl}\\p{Zp}\\p{Cc}\\p{Cf}&&[^\\u000D\\u000A\\u200C\\u200D]]\\u000D\\u000A])[[\\p{Mn}\\p{Me}\\u200C\\u200D\\u0488\\u0489\\u20DD\\u20DE\\u20DF\\u20E0\\u20E2\\u20E3\\u20E4\\uA670\\uA671\\uA672\\uFF9E\\uFF9F][\\p{Mc}\\u0E30\\u0E32\\u0E33\\u0E45\\u0EB0\\u0EB2\\u0EB3]]*)|(?s:.))";
¡Tschüß!