Kaynak kodu
Aşağıda tartıştığım yeniden yazma işlevlerinin kaynak kodu burada mevcuttur .
Java 7'de güncelleme
Sun'ın güncellenmiş Pattern
JDK7 sınıfı UNICODE_CHARACTER_CLASS
, her şeyin yeniden düzgün çalışmasını sağlayan harika bir yeni bayrağa sahip. Kalıbın (?U)
içine gömülebilir olarak mevcuttur , böylece String
sınıfın sarmalayıcılarıyla da kullanabilirsiniz. Aynı zamanda çeşitli diğer özellikler için düzeltilmiş tanımlamalara da sahiptir. Şimdi Unicode hem Standard, izler RL1.2 ve RL1.2a gelen UTS # 18: Unicode Düzenli İfadeler . Bu heyecan verici ve dramatik bir gelişmedir ve geliştirme ekibi bu önemli çabadan ötürü takdir edilmelidir.
Java'nın Regex Unicode Sorunları
Anlam - Java Regexes sorun Perl 1.0 charClass içine kaçar olmasıdır \w
, \b
, \s
, \d
ve onların tamamlayıcıları - olmayan Java Unicode ile çalışmak uzatıldı. Bu arasında Yalnız, \b
bazı genişletilmiş semantiğini sahiptir ama bunlar harita ne kadar \w
, ne kadar Unicode tanımlayıcılar , ne kadar Unicode satır kesme özellikleri .
Ek olarak, Java'daki POSIX özelliklerine şu şekilde erişilir:
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}
Bu işler gibi; çünkü bu, gerçek bir felaket Alpha
, Lower
ve Space
bunu değil Unicode Java haritası Alphabetic
, Lowercase
veya Whitespace
özellikleri. Bu son derece sinir bozucu. Java'nın Unicode özellik desteği kesinlikle yıl öncesidir , yani son on yılda ortaya çıkan hiçbir Unicode özelliğini desteklemediğini kastediyorum.
Beyaz uzay hakkında düzgün konuşamamak çok can sıkıcıdır. Aşağıdaki tabloyu düşünün. Bu kod noktalarının her biri için, hem Java için bir J sonuçları sütunu hem de Perl veya başka herhangi bir PCRE tabanlı normal ifade motoru için bir P sonuçları sütunu vardır:
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 -
Gördün mü?
Unicode'a göre bu Java beyaz boşluk sonuçlarının hemen hemen her biri ̲w̲r̲o̲n̲g is şeklindedir. Bu gerçekten büyük bir problem. Java, mevcut uygulamaya göre ve ayrıca Unicode'a göre "yanlış" cevaplar vererek sadece berbat durumda. Ayrıca Java, size gerçek Unicode özelliklerine erişim bile sağlamaz! Aslında Java, Unicode beyaz boşluğuna karşılık gelen herhangi bir özelliği desteklemez .
Tüm Bu Sorunların Çözümü ve Daha Fazlası
Bu ve diğer birçok ilgili sorunla başa çıkmak için, dün bu 14 karakter çıkışını yeniden yazan bir kalıp dizesini yeniden yazmak için bir Java işlevi yazdım:
\w \W \s \S \v \V \h \H \d \D \b \B \X \R
Onları öngörülebilir ve tutarlı bir şekilde Unicode ile eşleşmek için gerçekten çalışan şeylerle değiştirerek. Bu yalnızca tek bir hack oturumundan alınan bir alfa prototipidir, ancak tamamen işlevseldir.
Kısa hikaye, kodumun bu 14'ü aşağıdaki gibi yeniden yazmasıdır:
\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*)
Dikkate alınması gereken bazı şeyler ...
Onun için bu kullanımları \X
Ne tanımına Unicode şimdi atıfta bir şekilde eski sesletim küme değil, bir genişletilmiş sesletim küme ikincisi çok daha karmaşık olduğu gibi. Perl'in kendisi artık daha süslü sürümü kullanıyor, ancak eski sürüm hala en yaygın durumlar için mükemmel şekilde çalışabilir. DÜZENLEME: En alttaki eke bakın.
Ne yapacağınız \d
, amacınıza bağlıdır, ancak varsayılan, Uniode tanımıdır. İnsanların her zaman istemediğini görebiliyorum \p{Nd}
, bazen ya ya [0-9]
da \pN
.
İki sınır tanımı \b
ve \B
, \w
tanımı kullanmak için özel olarak yazılmıştır .
Bu \w
tanım aşırı derecede geniştir, çünkü sadece daire içine alınmış harfleri değil, parlatılmış harfleri de alır. Unicode Other_Alphabetic
özelliği JDK7'ye kadar mevcut değildir, bu yüzden yapabileceğiniz en iyi şey budur.
Sınırları Keşfetmek
Sınırlar Larry Duvar ilk icat beri bir sorun olmuştur \b
ve \B
nasıl anlaşılması için 1987 yılında Perl 1.0 sırt için onlar hakkında anahtarını konuşmak için sözdizimi \b
ve \B
her iki çalışma onlar hakkında iki yaygın mitler gidermek için:
- Sadece
\w
kelime karakterleri arıyorlar , asla kelime olmayan karakterler için değiller .
- Özellikle ipin kenarını aramıyorlar.
Bir \b
sınır şu anlama gelir:
IF does follow word
THEN doesn't precede word
ELSIF doesn't follow word
THEN does precede word
Ve bunların hepsi mükemmel bir şekilde şu şekilde tanımlanır:
- Aşağıdaki kelime olduğunu
(?<=\w)
.
- ilerlettiği kelime olduğunu
(?=\w)
.
- kelimesi takip etmez ise
(?<!\w)
.
- does not Precede kelime olduğunu
(?!\w)
.
Bu nedenle, düzenli ifadelerde birlikte ed-birlikte IF-THEN
olarak kodlandığından , bir eşittir ve önceliği daha yüksek olduğu için bu basittir . Yani her biri, bir sınırın güvenli bir şekilde aşağıdakilerle değiştirilebileceği anlamına gelir:and
AB
or
X|Y
and
or
AB|CD
\b
(?:(?<=\w)(?!\w)|(?<!\w)(?=\w))
ile \w
, uygun bir şekilde tanımlanmaktadır.
( Bileşenlerin A
ve C
bileşenlerinin birbirine zıt olması garip gelebilir . Kusursuz bir dünyada bunu yazabilmelisiniz AB|D
, ancak bir süredir Unicode özelliklerinde karşılıklı dışlama çelişkilerini araştırıyordum - ki bunu hallettiğimi düşünüyorum , ancak her ihtimale karşı çifte koşulu sınırda bıraktım. Artı bu, daha sonra fazladan fikir alırsanız daha da genişletilebilir hale getirir.)
For \B
olmayan sınırları, mantık geçerli:
IF does follow word
THEN does precede word
ELSIF doesn't follow word
THEN doesn't precede word
Tüm örneklerinin \B
aşağıdakilerle değiştirilmesine izin vermek :
(?:(?<=\w)(?=\w)|(?<!\w)(?!\w))
Bu gerçekten nasıl \b
ve \B
davranmaktır. Onlar için eşdeğer modeller
\b
((IF)THEN|ELSE)
yapıyı kullanmak(?(?<=\w)(?!\w)|(?=\w))
\B
((IF)THEN|ELSE)
yapıyı kullanmak(?(?=\w)(?<=\w)|(?<!\w))
Ancak AB|CD
, özellikle normal ifade dilinizde (Java gibi) koşullu kalıplar yoksa, iyi olan sürümler iyidir. ☹
Çalıştırma başına 110.385.408 eşleşmeyi kontrol eden ve bir düzine farklı veri yapılandırmasında çalıştırdığım bir test paketi ile üç eşdeğer tanımı da kullanarak sınırların davranışını zaten doğruladım:
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)
Ancak, insanlar genellikle farklı türde bir sınır isterler. Boşluk ve dize kenarının farkında olan bir şey istiyorlar:
- sol kenar olarak
(?:(?<=^)|(?<=\s))
- sağ kenarı olarak
(?=$|\s)
Java'yı Java ile Düzeltme
Ben gönderilmiş kod benim diğer cevap bu ve epeyce diğer kolaylıklar sağlar. Bu, doğal dilde sözcükler, tireler, kısa çizgiler ve kesme işaretlerinin tanımlarını ve biraz daha fazlasını içerir.
Ayrıca, aptal UTF-16 temsillerinde değil mantıksal kod noktalarında Unicode karakterleri belirtmenize de olanak tanır. Bunun ne kadar önemli olduğunu vurgulamak zor! Ve bu sadece dizi genişletmesi için.
Regex charClass içine değiştirme işlemi, Java charClass içine yapar Regexes için nihayet Unicode üzerinde çalışmaya, doğru ve çalışmalarını kapmak buradan tam kaynağını . Tabii ki dilediğin gibi yapabilirsin. Düzeltmeler yaparsan, duymak isterim ama zorunda değilsin. Oldukça kısa. Ana normal ifade yeniden yazma işlevinin özü basittir:
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;
Her neyse, bu kod sadece bir alfa sürümü, haftasonu hacklediğim şeyler. Böyle kalmayacak.
Beta için şunları yapmayı planlıyorum:
kod çoğaltmayı bir araya getirin
normal ifade çıkışlarını artırmaya kıyasla çıkış dizesi çıkışlarına ilişkin daha net bir arayüz sağlar
\d
genişletmede biraz esneklik sağlar ve belki de\b
Döndürmeyi ve Pattern.compile veya String.matches veya sizin için başka şeyleri çağırmayı işleyen kolaylık yöntemleri sağlayın
Üretim sürümü için javadoc ve bir JUnit test paketine sahip olmalıdır. Gigatester'ımı ekleyebilirim, ancak JUnit testleri olarak yazılmamış.
ek
İyi ve kötü haberlerim var.
İyi haber şu ki , geliştirilmiş bir grafem kümesine şimdi çok yakın bir yaklaşıma sahibim .\X
Kötü haber şu ki, bu kalıp:
(?:(?:\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:.))
Java'da şu şekilde yazarsınız:
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üss!