Problem
Mamy formularz z polami, które musimy zwalidować pod kątem poprawności danych. Pole może zawierać jedynie litery (duże, małe – wszystko jedno).
Jakim wyrażeniem regualarnym realizujemy sprawdzanie? Pierwsza myśl to [a-zA-Z]… niestety walidacja nie zadziała poprawnie, gdyż w zakres [a-z] uwzględnia jedynie 26 liter alfabetu łacińskiego natomiast nie uwzględnia znaków diakrytycznych czyli litery [ą, ć, ę, ł, ń, ó, ś, ź, ż] nie spełnią kryterium walidacji.
Zatem aby walidacja działała poprawnie trzeba by dodać do zbioru [a-zA-Z] litery ze znakami diakrytycznymi. Otrzymujemy zatem wyrażenie: [a-zA-ZąćęłńóśźżĄĆĘŁŃÓŚŹŻ]. Wyrażenie to zadziała poprawnie, jednak jest narażone na potencjalne błędy, łatwo zapomieć o którejś literze lub zrobić literówkę. W przypadku gdy tworzymy aplikacje multijęzykową czynność tą będziemy musieli powtórzyć dla każdego z obsługiwanych języków.
Poprawne rozwiązanie
W programowaniu jak już coś sie robi to należy zrobić to porządnie, tak aby swojego kodu nie trzeba było poprawiać, modyfikować etc…
Z pomocą przychodzi nam kodowanie UNICODE, w które umożliwia napisać reguły na litery posiadające znaki diakrytyczne. Wyrażenie \p{L} oznacza jakąkolwiek literę w jakimkolwiek języku, może by być: Ł, ź, ü, ß czy cookolwiek co jest literą (także nie łacińską). Zatem nasze wyrażenie walidujące pole zawierające włącznie litery będzie wyglądać następująco:
preg_match('#^\pL+$#', 'MałyŁoß');
Gwoli wyjaśnienia \pL to skrót od \p{L} a \p{L} oznacza znak (code point) unicode \p należący do kategorii liter {L}.
Klasy/kategorie znaków w unicode:
\p{L} – wszystkie litery
\p{M} – wszystkie znaki, które samotnie nie występują, musi być połączony z innym znakiem i składa się na np. akcent, umlaut, etc…
\p{Z} – wszystkie znaki niewidoczne
\p{S} – wszystkie symbole, znaki walut, znaki matematyczne
\p{N} – wszystkie znaki numeryczne
\p{P} – wszystkie znaki interpunkcyjne: przecinki, średniki etc…
\p{C} – wszystkie niewidoczne znaki kontrolne i niewykorzystywane znaki code point
Przeczytaj więcej o właściwościach wyrażeń regualrnych w unicode.
Niestety zdarza się, że znaki są kodowane nie jako jeden znak, ale jako dwa znaki, np. „ż” może być zakodowane jako jeden znak „ż” lub „z” ze znakiem diakrytycznym. Natknąłem się na ten problem, gdy musiałem sprawdzić ciąg znaków rozpoczynający się od „polskiej” litery.
preg_match(’#^\pL+$#’, 'żubr’); // wyświetla false
preg_match(’#^\pL+$#’, 'użbr’); // jest ok
Trzeba więc dodać test „na okoliczność” znaku diakrytycznego:
preg_match(’#^[\p{L}\p{M}*]+$#’, 'żubr’); // teraz jest ok
Dlaczego pierwszy znak w takim przypadku jest rozbijany na dwa – nie wiem, może to jest kwestia vima/gedita/linuksa, a może jakaś generalna zasada…