Hlavní navigace

Ruby a unicode: bratři nebo nepřátelé?

29. 10. 2007
Doba čtení: 3 minuty

Sdílet

Unicode je jedna z nejdůležitějších dnešních technologií, přesto je jeho podpora v Ruby stejně jako v mnoha dalších programovacích jazycích problematická. Pomoc však existuje, možností pomoci je dokonce více. Jak tedy nepodporu unicode elegantně vyřešit?

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.

root_podpora

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.

Odkazy

Byl pro vás článek přínosný?

Autor článku

Jakub Šťastný byl v letech 2007 až 2008 redaktorem serveru Root.cz. Mezi jeho zájmy patří Linux, programování a typografický systém TeX.