Co je míněno podporou unicode?
Když říkám, že Ruby neumí unicode, ani v nejmenším to neznamená, že nemůžete psát své skripty v UTF-8, UTF-16 nebo podobném kódování, s tím samozřejmě problém není. Problém je v tom, že string v Ruby je implementovaný jako sekvence bytů bez ohledu na délku znaku. Důsledkem toho nefungují správně metody jako například length
, reverse
, upcase
, each_char
a podobné.
Řešení?
Hackujeme přes regulární výrazy
Pointa jednoho z možných způsobů řešení spočívá v tom, že regulární výrazy unicode de facto podporují, jenom je třeba jim pomoci, aby si to „uvědomily“. Od toho slouží přepínač u
. Ukázka napoví více:
# špatně - bez přepínače "u" "čeština".gsub(/./, '\& ').each { |match| puts match } => ? ? e ? ? t i n a # správně - s přepínačem "u" "čeština".gsub(/./u, '\& ').each { |match| puts match } => č e š t i n a
Užitečné, že? Pokud se vám parametr „ u
“ nechce u každého regulárního výrazu uvádět, je možné jej nastavit globálně pomocí proměnné $KCODE
:
$KCODE = "u" "čeština".gsub(/./, '\& ').each { |match| puts match } => č e š t i n a
To je sice hezké a bezesporu také velmi užitečné, ale naši potřebu fungujících metod reverse
a podobně to neřeší. Budeme si muset pomoci sami, ale nebojte, není to těžké – stačí prostě předefinovat metodu reverse
:
$KCODE = "u" class String def reverse self.scan(/./).reverse.join end end
Nyní bude metoda reverse
fungovat bezproblémově. Stejným způsobem lze velmi snadno a přitom maximálně elegantně předefinovat další metody jako například first
, last
nebo length
a na několika málo řádcích změnit chování Ruby tak, že po načtení této vaší knihovny již nebude k poznání, že Ruby vůbec někdy unicode nepodporovalo. To je důsledek čistého objektového návrhu.
Sluší se poznamenat, že binárka Ruby umožňuje použití parametru -Ku
(resp. parametru -K
s volbou u
), který umožňuje nastavení proměnné $KCODE
při spouštění programu bez přímého zásahu do něj.
Tímto způsobem je možno předefinovat i ostatní metody Stringu:
$KCODE = "u" class String def reverse self.scan(/./).reverse.join end def first self.scan(/^./).join end def last self.scan(/.$/).join end def length self.scan(/./).length end ... end
Problémy
Pokud bychom chtěli opravdu důkladnou podporu unicode, bylo by patrně též záhodno předefinovat pár metod třídy Range
, protože ("a".."z")
bude samozřejmě zacházet pouze s ASCII sadou. Je ovšem otázka, zda je moudré něco takového dělat a když už, tak alespoň musíme brát ohled na systémové locales. Pokud bychom chtěli, aby správně fungovalo porovnávání, bylo by též třeba předefinovat metodu <=>
z modulu Comparable
.
Nejsem si příliš jistý ohledně metod upcase
, downcase
a swapcase
. Osobně používám metodu tr
, ale to předpokládá vypsání všech unicode znaků jako její parametr. Pro ty české to samozřejmě není problém, ale obecné řešení to není. Pokud někdo máte lepší řešení, budu rád, když se podělíte v diskusi.
Příště
V příštím díle se podíváme na různé knihovny, které nám při naší práci s unicode mohou přijít vhod – unicode
, ICU4R
a z Ruby on Rails známý ActiveSupport::Multibyte
. Řeč přijde též na podporu unicode v chystaném Ruby 1.9.1 a dozvíte se také, jak podporují unicode ostatní implementace Ruby, především JRuby.