„There ain’t no such thing as plain text”. Joel Spolsky (what every developer must know about unicode).
„Nie istnieje nic takiego jak czysty tekst”.
Do dalszych rozważań korzystam z systemu używającego kodowania UTF-8 oraz Ruby 2.0. Rezultaty mogą być inne jeśli użyje się innego kodowania.
Ostatnio parsowałem trochę tekstu polskiego. Jako że wyrażenia regularne w każdym języku działają trochę inaczej jeśli chodzi o zestaw znaków większy niż ASCII (w PHP: /u), opisałem te scenariusze w Rubym.
Polskie znaki
Najpierw zobaczmy, jak Polskie znaki są reprezentowane w stringu:
[cc lang=”ruby” escaped=”true”]
[11] pry(main)> „test”.bytes.to_a
=> [116, 101, 115, 116]
[12] pry(main)> „teść”.bytes.to_a
=> [116, 101, 197, 155, 196, 135]
[/cc]
Locale w moim systemie to LANG=pl_PL.UTF-8. Jak widać, zarówno „ś” jak i „ć” zajęły dwa bajty (w UTF-8 może być maksymalnie 6 bajtów na znak). Zobaczmy dwa wyrażenia:
[cc lang=”ruby” escaped=”true”]
[5] pry(main)> /\w+/.match(’test’)
=> #<MatchData „test”>
[6] pry(main)> /\w+/.match(’teść’)
=> #<MatchData „te”>
[/cc]
zgodnie z oczekiwaniami, ść przysporzyło trochę kłopotu. „\w” nie dopasowuje polskich znaków. Rozwiązaniem może być użycie klasy POSIX dopasowującej znaki z alfabetu:
[cc lang=”ruby” escaped=”true”]
[8] pry(main)> /[[:alpha:]]+/.match(’teść’)
=> #<MatchData „teść”>
[/cc]
Można także wykorzystać „character properties„, które opisane są także tutaj.
[cc lang=”ruby” escaped=”true”]
[9] pry(main)> /\p{Word}+/.match(’teść’)
=> #<MatchData „teść”>
[/cc]
Białe znaki
Jeśli tekst pochodzi z edytora tekstu lub z internetu, nie należy zakładać, że „\s” dopasuje każdą spację 😉 Otóż, jak się okazuje:
[cc lang=”ruby” escaped=”true”]
[1] pry(main)> /\w+\s\w+/.match „word word” # zwykła spacja
=> #<MatchData „word word”>
[2] pry(main)> /\w+\s\w+/.match „word word” # twarda spacja
=> nil
[/cc]
w drugimi przypadku do oddzielenia słów użyta została twarda spacja. W Windowsie można ją wstawić poprzez przystrzymanie alt+0160 (klawiatura numeryczna), w VIM przez naciśnięcie <C-k> <spacja> <spacja>.
W każdym razie, żeby rozwiązać problem można albo zamienić wszystkie twarde spacje na spacje w stringu, lub zmienić wyrażenie regularne coby wykorzystywało klasę POSIX [[:space:]], można także dopisać twardą spację do definicji grupy wyrażenia regularnego:
[cc lang=”ruby” escaped=”true”]
[3] pry(main)> /\w+[[:space:]]\w+/.match „word word” # twarda spacja
=> #<MatchData „word word”>
# or
[4] pry(main)> /\w+[\s ]\w+/.match „word word”
=> #<MatchData „word word”>
[/cc]
Dodaj komentarz