Wprowadzenie

Program ttyconv powsta│ na bazie wcze╢niejszego programu ogonki, przeznaczonego do konwersji "w locie" znak≤w przechodz▒cych miΩdzy progremem u┐ytkowym i terminalem. Pocz▒tkowym przeznaczeniem tego programu by│o przekodowywanie miΩdzy r≤┐nymi standardami polskich liter, tam gdzie sam program nie umo┐liwia wybrania odpowiedniego standardu. Program ten jednak zosta│ na tyle rozbudowany, ┐e z │atwo╢ci▒ mo┐na u┐ywaµ go tak┐e do innych cel≤w, jak np. przedefiniowywanie klawiatury terminala, zdefiniowanie sobie skr≤t≤w klawiszowych do najczΩ╢ciej u┐ywanych polece±, czy u┐ywanie nietypowych znak≤w, niedostΩpnych w normalny spos≤b.

Program ttyconv umo┐liwia konwersjΩ nie tylko 1:1 (czyli jeden znak na wej╢ciu powoduje jeden znak na wyj╢ciu), ale tak┐e n:1, 1:n czy nawet n:n, czyli mo┐na zamieniaµ ca│e ci▒gi znak≤w na inne ci▒gi. Jest to szczeg≤lnie u┐yteczne w przypadku obs│ugi klawiatury, na kt≤rej naci╢niΩcie pewnej kombinacji klawiszy generuje ci▒g kilku znak≤w (zwykle rozpoczynaj▒cy siΩ od znaku ESC).

Opis u┐ytkowania

Program uruchamia siΩ podaj▒c spos≤b konwersji na wej╢ciu (to znaczy z klawiatury), oraz na wyj╢ciu (czyli wy╢wietlanych na ekranie). Mo┐na u┐ywaµ tylko jednej z tych konwersji, lub obydwu jednocze╢nie. Poszczeg≤lne konwersje zdefiniowane s▒ w plikach, kt≤re program wyszukuje w katalogach podanych - analogicznie jak w zmiennej PATH - w zmiennej ╢rodowiskowej TTYCONV_PATH.

ListΩ dostΩpnych konwersji mo┐na uzyskaµ, uruchamiaj▒c program ttyconv z opcj▒ -l.

Przy podawaniu konwersji mo┐na podaµ wiΩcej ni┐ jedn▒ tabelΩ konwersji. Po pierwsze - mo┐na tabele sumowaµ (w sensie mnogo╢ciowym), podaj▒c ich nazwy po│▒czone znakiem plus (+). W ten spos≤b np. mo┐na u┐yµ kilku zestaw≤w skr≤t≤w klawiszowych, pisz▒c skr1+skr2+skr5. Efekt podania takiego parametru jest identyczny z podaniem nazwy pliku, bΩd▒cego po│▒czeniem wszystkich tabel. Innymi s│owy - w czasie konwersji bΩd▒ wyszukiwane skr≤ty wystΩpuj▒ce kolejno we wszystkich tabelach. Je┐eli dany skr≤t ma r≤┐ne rozwiniΩcia w wystΩpuj▒cych tabelach (np. w skr1 podano ┐e kombinacja klawiszy `1 ma wygenerowaµ tekst "blabluga" a w skr2 ta sama kombinacja `1 powinna wygenerowaµ tekst "barbapapa"), to brany pod uwagΩ jest skr≤t wystΩpuj▒cy jako ostatni (w tym wypadku "barbapapa").

Drug▒ z mo┐liwo╢ci │▒czenia tabel konwersji, jest znak dwukropka. Zasadniczo ka┐de okre╢lenie spos≤bu konwersji ma format t1+t2+...+tn:u1+u2+...+un Tabele podawane po dwukropku, s▒ przed u┐yciem "obracane", tzn. skr≤ty w nich zdefiniowane z postaci A->B s▒ zamieniane na B->A (je┐eli np. tabela definiuje konwersjΩ ze standardu CP1250 na ISO-8859-2 to po umieszczeniu jej po dwukropku otrzymamy konwersjΩ z ISO-8859-2 na CP1250). Dla ka┐dego skr≤tu najpierw dokonywana jest konwersja wed│ug tabel przed dwukropkiem, a nastΩpnie wynik tej konwersji jest poddawany drugiej konwersji, za pomoc▒ tabel u1+u2+...+un.

Brzmi to mo┐e nieco skomplikowanie, ale daje mo┐liwo╢µ tworzenia ca│ych zestaw≤w tabel, co ma zastosowanie m.in. przy tabelach konwersji polskich znak≤w. S▒ one zdefiniowane tak, ┐e ka┐da z nich definiuje konwersjΩ ze standardu X na standard ISO-8859-2. Je┐eli teraz chcemy np. otrzymaµ konwersjΩ z mazovii na CP1250, to wystarczy podaµ mazovia:cp1250. W ten spos≤b dane wej╢ciowe najpierw bΩd▒ konwertowane za pomoc▒ tabeli mazovia, dziΩki czemu otrzymamy dane zgodne ze standardem ISO, a nastΩpnie wynik tej konwersji zostanie poddany dalszej konwersji za pomoc▒ odwr≤conej tabeli cp1250, kt≤ra dokona zamiany z ISO na CP1250, w wyniku czego otrzymamy ┐▒dan▒ konwersjΩ z mazovii na CP1250. Je┐eli chcemy dokonaµ konwersji z/na standard ISO, to mo┐emy podaµ oczywi╢cie konwersjΩ w postaci (np.) cp1250: lub :cp1250. Aby jednak wygl▒da│o to "│adniej", zdefiniowana jest r≤wnie┐ tabela iso, kt≤ra jest tabel▒ pust▒ (nie dokonuje ┐adnej konwersji), mo┐na zatem napisaµ cp1250:iso lub iso:cp1250.

Je┐eli kt≤ra╢ z czΩ╢ci przed lub po dwukropku jest pusta, to przyjmowana jest oczywi╢cie pusta tabela, czyli dana konwersja nie jest dokonywana. Je┐eli w definicji konwersji w og≤le nie wystΩpuje dwukropek, traktowana jest ona jako czΩ╢µ przed dwukropkiem t1+t2+...+tn, natomiast czΩ╢µ po dwukropku jest przyjmowana jako pusta.

Kompilacja i instalacja

Pierwsz▒ rzecz▒ do zrobienia jest oczywi╢cie ╢ci▒gniΩcie ╝r≤de│ programu.

Program sk│ada siΩ z jednego pliku ╝r≤d│owego, napisanego w jΩzyku C z u┐yciem funkcji dostΩpnych w wiΩkszo╢ci standardowych system≤w Unix, w zwi▒zku z czym jest skompilowanie powinno ograniczyµ siΩ do wydania polecenia:

cc -o ttyconv ttyconv.c
Przed kompilacj▒ nale┐y jednak sprawdziµ czy odpowiadaj▒ nam opcje definiowane w pliku ttyconv.c w czΩ╢ci zaznaczonej jako "Configurable section". A oto co mo┐na tam ustawiµ:

Po ustawieniu wy┐ej opisanych parametr≤w program mo┐na ju┐ skompilowaµ. Otrzymany w wyniku skompilowany program mo┐emy przetestowaµ i je┐eli wszystko dzia│a prawid│owo mo┐na go ju┐ umie╢ciµ w publicznie dostΩpnym katalogu. Drugim krokiem instalacji jest umieszczenie w innym publicznie dostΩpnym katalogu (np. /usr/local/lib/ttyconv) plik≤w *.cnv, dostarczanych wraz z programem. Ostatnim krokiem jest globalne ustawienie zmiennej ╢rodowiskowej TTYCONV_PATH tak, aby wskazywa│a na katalog z (globalnymi) plikami *.cnv, a nastΩpnie na katalog aktualny (czyli np. /usr/local/lib/ttyconv:.). Oczywi╢cie u┐ytkownicy w razie potrzeby mog▒ sobie zdefiniowaµ tΩ zmienn▒ zgodnie z w│asnymi wymaganiami.

Pliki *.cnv

Pliki te okre╢laj▒ tabele konwersji. Ka┐dy taki plik zawiera definicje konwersji w postaci 'skr≤t' -> 'rozwiniΩcie', gdzie skr≤t to ci▒g bajt≤w kt≤ry nale┐y odnale╝µ, a rozwiniΩcie to ci▒g jakim nale┐y go zast▒piµ. Zar≤wno skr≤t jak i rozwiniΩcie musz▒ byµ ograniczone znakami cudzys│owu lub innymi, praktycznie dowolnymi znakami, z tym tylko ograniczeniem ┐e znak rozpoczynaj▒cy skr≤t musi byµ identyczny ze znakiem ko±cz▒cym, lub w przypadku rozpoczΩcia skr≤tu od nawiasu otwieraj▒cego, na ko±cu nale┐y umie╢ciµ nawias zamykaj▒cy. Innymi s│owy mo┐na pisaµ np. 'Jasio', "Jasio", $Jasio$ czy {Jasio}, ale ju┐ nie 'Jasio's' (pierwszy apostrof zosta│by potraktowany jako koniec tekstu). W tek╢cie mo┐na stosowaµ powszechnie znan▒ notacjΩ z u┐yciem znaku backslash, a konkretnie - \n, \r, \t, oznaczaj▒ odpowiednio znaki o kodach 13, 10 i 9, kombinacja \\ oznacza pojedynczy backslash, \ddd gdzie ddd to cyfry dziesiΩtne oznacza znak o podanym ≤semkowo kodzie, natomiast w pozosta│ych przypadkach znak \ oznacza skopiowanie nastΩpnego znaku, nawet gdyby normalnie mia│ on oznaczaµ koniec ci▒gu (czyli przytoczony powy┐ej przyk│ad mo┐na by zapisaµ jako 'Jasio\'s').

Zamiast tekstu w cudzys│owach, mo┐na te┐ wpisaµ liczbΩ ca│kowit▒ - zostanie ona wtedy potraktowana jako kod znaku (np. 122 oznacza literΩ "z" - zgodnie z kodem ASCII).

Ka┐dy skr≤t musi byµ umieszczony w oddzielnej linijce i musi siΩ w tej linijce zmie╢ciµ w ca│o╢ci. Linie puste oraz zaczynaj▒ce siΩ od znaku # s▒ traktowane jako komentarze i ignorowane przez program.

Pierwsza linijka programu musi byµ komentarzem (zaczynaµ siΩ od znaku #) i powinna zawieraµ kr≤tki (do 50 znak≤w) opis danego pliku. Opis ten bΩdzie wy╢wietlany przy uruchomieniu programu ttyconv z opcj▒ -l (wypisanie dostΩpnych tablic konwersji).

Program "od ╢rodka"

Spos≤b dzia│ania programu jest do╢µ prosty, opiera siΩ on na wykorzystaniu terminali wirtualnych (plik≤w /dev/ptyXX i /dev/ttyXX), kt≤re pozwalaj▒ na uruchamianie programu jak na zwyk│ym terminalu, ale z pe│n▒ kontrol▒ wszystkich danych na wej╢ciu i wyj╢ciu tego terminalu.

Po uruchomieniu program przygotowuje drzewa konwersji (o czym dalej), po czym otwiera pierwsz▒ woln▒ parΩ plik≤w terminala wirtualnego. Po otwarciu terminalu program uruchamia nowy proces, ustawia otwarty wcze╢niej terminal wirtualny jako g│≤wny terminal tego procesu, po czym funkcj▒ exec dokonuje uruchomienia w│a╢ciwego procesu, podanego jako argument dla programu ttyconv (lub programu pow│oki je┐eli nie podano nazwy programu do uruchomienia).

W tym samym czasie proces podstawowy rozpoczyna cykliczne odczytywanie standardowego wej╢cia oraz wyj╢cia z terminalu wirtualnego. Je┐eli zostan▒ odczytane jakie╢ dane, s▒ one poddawane konwersji i przesy│ane dalej, odpowiednio - dane ze standardowego wej╢cia przesy│ane s▒ na wej╢cie terminalu wirtualnego, a dane z wyj╢cia terminalu wirtualnego przesy│ane s▒ na standardowe wyj╢cie. Przetwarzanie takie jest wykonynane tak d│ugo, jak d│ugo istnieje proces potomny.

Znacznie ciekawszy od samego mechanizmu terminali wirtualnych, jest spos≤b konwersji danych. Poniewa┐ z za│o┐enia tabele konwersji mog▒ zawieraµ skr≤ty w postaci n:n, czyli zamiana mo┐e dotyczyµ nie tylko pojedynczych znak≤w ale tak┐e ich ci▒g≤w, dlatego te┐ nie mo┐na by│o zastosowaµ prostej tabeli z gotowymi skr≤tami dla 256 mo┐liwych warto╢ci ka┐dego bajtu na wej╢ciu. Najprostszym rozwi▒zaniem jest zbieranie przychodz▒cych danych w tablicy, oraz cykliczne przeszukiwanie tabeli ze zdefiniowanymi skr≤tami za ka┐dym razem gdy pojawi siΩ nowy bajt (lub wiΩcej bajt≤w). Jest to jednak bardzo nieoptymalne, poniewa┐ wymaga przeszukiwania tabeli skr≤t≤w dla ka┐dego bajtu pojawiaj▒cego siΩ na wej╢ciu. Nawet gdyby zastosowaµ najszybsze algorytmy wyszukiwania, to i tak program wykonywa│by dla ka┐dego bajtu masΩ operacji, kt≤rych mo┐na unikn▒µ.

W programie ttyconv zastosowano szybsz▒ i optymalniejsz▒ metodΩ, wymagaj▒c▒ jedynie dodatkowego nak│adu pracy przy uruchamianiu programu, a mianowicie bezpo╢rednio po uruchomieniu programu na podstawie podanych definicji tabel konwersji, tworzone s▒ struktury danych nazwane przeze mnie drzewami konwersji.

Drzewo konwersji jest to klasyczne drzewo, w kt≤rym wΩz│ami s▒ kolejne bajty, a dodatkowo w ka┐dym wΩ╝le mo┐e znajdowaµ siΩ ci▒g znak≤w, wskazuj▒cy na rozwiniΩcie danego skr≤tu. Proces przetwarzania wygl▒da tak, ┐e zaczynaj▒c od korzenia przeskakujemy do kolejnych wΩz│≤w drzewa zgodnie z otrzymywanymi znakami, zapamiΩtuj▒c jednocze╢nie ostatnio odnalezione rozwiniΩcie skr≤tu. W momencie gdy znajdziemy siΩ w miejscu z kt≤rego nie jeste╢my w stanie przej╢µ do kolejnego wΩz│a, zwracamy (jako wynik konwersji) ostatnio odnaleziony skr≤t (oraz ewentualnie bajty kt≤ry pojawi│y siΩ p≤╝niej i nie mog│y zostaµ wykorzystanie w konwersji).

Przyk│ad. Mamy zdefiniowan▒ nastΩpuj▒c▒ tablicΩ konwersji:

'!x' -> 'extraordinary'
'!i' -> 'internationalization'
'!is' -> 'i18n'

Na jej podstawie zostanie przy uruchomieniu programu wygenerowane nastΩpuj▒ce drzewko (mo┐na je obejrzeµ po skompilowaniu programu z opcj▒ #define DEBUG i podaniu opcji -d):

(ROOT)
 |
 +--(!)
     |
     +--(x) -> 'extraordinary'
     |
     +--(i) -> 'internationalization'
         |
         +--(s) -> 'i18n'

Przypu╢µmy ┐e na wej╢ciu mamy znak 'u'. Program rozpocznie od korzenia drzewa, nie znajdzie mo┐liwo╢ci przej╢cia dalej, w zwi▒zku z czym zwr≤ci ostatnio znaleziony skr≤t (w tym wypadku pusty) wraz ze znakiem 'u' nie wykorzystanym do konwersji. W efekcie znak 'u' zostanie po prostu zamieniony na znak 'u'. (i podobnie bΩdzie ze wszystkimi znakami r≤┐nymi od '!').

Sprawa wygl▒da inaczej dopiero gdy na wej╢ciu pojawi siΩ znak '!'. Przypu╢µmy ┐e mamy na wej╢ciu ci▒g '!i?'. Pierwszy znak spowoduje przej╢cie do wΩz│a z wykrzyknikiem. Drugi znak przej╢cie do wΩz│a ze znakiem 'i' oraz zapisanie ci▒gu internalization jako ostatnio odnalezionego skr≤tu. Wreszcie znak '?' spowoduje ┐e program nie bΩdzie m≤g│ znale╝µ dalszej drogi w drzewie konwersji, zwr≤ci zatem ci▒g internalization? (znak '?' jest dodany na ko±cu, poniewa┐ nie zosta│ u┐yty do konwersji). A zatem wszystko zgodnie z tym, co zosta│o zdefiniowane w tabeli konwersji.

Patrz▒c na powy┐sze, nale┐y zwr≤ciµ uwagΩ, ┐e znaki na wyj╢ciu programu pojawiaj▒ siΩ dopiero wtedy, gdy mo┐na jednoznacznie ustaliµ wynik konwersji. W zwi▒zku z tym, je┐eli zdefiniujemy sobie filtr wej╢ciowy wy│apuj▒cy skr≤ty rozpoczynaj▒ce siΩ od wykrzynika, ten┐e wykrzyknik stanie siΩ automatycznie klawiszem "martwym" - jego naci╢niΩcie nie bΩdzie powodowaµ ┐adnej reakcji, dopiero naci╢niΩcie kolejnego klawisza spowoduje reakcjΩ.

DziΩki drzewom konwersji program dzia│a szybko - oczywi╢cie nic za darmo, jest to okupione wiΩkszym zu┐yciem pamiΩci. Jednak przeciΩtne drzewo konwersji zajmuje w zaledwie kilka KB pamiΩci, nie jest to zatem du┐y problem.

Piotr Pi▒tkowski, kompas@uci.agh.edu.pl