Ruby – Wyrażenia regularne a znaki specjalne

„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]

 

Leave a Reply