/**************/ /* language.c */ /**************************************************************************** PROGRAM: language.c PURPOSE: Kääntää tekstejä suomesta toiselle kielelle. Toimii kaikkialla missä ANSI-C-toimii. AUTHOR: Vesa Lappalainen 1991 - 29.8.1993 USAGE: Projektiin laitettava: language.c, mjonot.c Jos halutaan tehdä ohjelma aluksi suomen kielellä ja sitten myöhemmin saada ohjelmasta vieraskielinen, menetellään seuraavasti: 1) Jokainen tulostettava merkkijono ajetaan makron T-läpi. Esimerkiksi printf("Terve mualima!"); kirjoitetaankin muodossa printf(T("Terve mualima!"); 2) Pidetään yllä käännöstiedostoa. Makro T lisää tiedostoon ajon aikana puuttuvia sanoja. Esimerkiksi jos edelliselle ei löydy käännöstä, lisäytyy käännöstiedostoon rivi: "Terve mualima!" "@@Terve mualima!" Tämä sitten muutetaan haluttuun muotoon: "Terve mualima!" "Hello World!" 3) Ohjelman alkuun lisätään käännöstiedoston lukeva kutsu: read_translate(kaannos_tiedosto,kieli) jossa kieli on -1,0,...,KIELIA,ETSI_KAANNOS_AINA -1 = ei käännöstä lainkaan, eikä edes lueta tiedostoa. => säästyy muistia ohjelman ajon aikana 0 = käännöstiedoston 1. sarakkeen kieli. Käännöstä ei tehdä, mutta tiedosto luetaan valmiiksi 1 = 1. vieras kieli. Nyt makro T -kääntää. ETSI_KAANNOS_AINA = luetaan tiedosto ja käytetään kieltä 0, mutta etsitään aina löytyykö jonoa käännöstiedostosta. Näin käännöstiedosto saadaan täydennettyä, vaikka ei käytetäkään vierasta kieltä. 4) Ohjelman lopuksi muistetaan vapauttaa kieli-tietorakenne: free_translate(); 5) Jos kesken ohjelman halutaan muuttaa kieltä, kutsutaan set_language(kieli); ============ Aliohjelmat: (language.c) ============ int lisaa_kaannos_rivi(char *st) -------------------------------- - lisää käännöstietorakenteeseen (muttei tiedostoon) muotoa "suomi" "finnish" oleva rivin int set_language(int kieli) --------------------------- - vaihtaa oletuskielen. Jos muutetaan -1:ksi, ei enää voida muuttaa muuksi. Oletuksena kieli on 0. int read_translate(char *tiedosto,int kieli) -------------------------------------------- - lukee käännöstiedoston ja laittaa oletuskielen Uusi kutsu lisää edellisen perään, jolloin voidaa lukea useita eri tiedostoja. Tosin lisäykset tulevat aina viimeksi luettuun käännöstiedostoon. Ei haittaa vaikka kutsutaan peräkkäin monta kertaa samalla nimellä. Ei lueta tiedostoa uudestaan. Jos kuitenkin nimi vaihtuu välillä ja tulee takaisin samaksi, niin silloin haittaa (ei pidä nimilistaa!) void free_translate(void) ------------------------- - vapauttaa käännöstietorakenteen viemän tilan ja lopettaa käännöksen. char *translate(char *s,int add) -------------------------------- - palauttaa jonoa s vastaavan käännöksen ja lisää tiedostoa jos add==1 ja s:ää vastaavaa käännöstä ei löydy. char *translate_back(char *s) ----------------------------- - palauttaa jonoa s vastaavan takaisinkäännöksen, eli lisää tiedostoa. int language(void) ------------------ - palauttaa oletuskielen numeron ======= MAKROT: (language.h) ======= KIELIA 2 T(s) translate(s,1) TA(s,a) translate(s,a) TB(s) translate_back(s) ======= Vikoja: ======= Tietorakenne on yksinkertainen linkitetty lista, josta sanojen haku tehdään peräkkäishakuna. Tämä kuitenkin riittää pienille ohjelmille, jossa käännettäviä lauseita on vain muutamia satoja. Dynaamista tietorakennetta ylläpidetään malloc-kutsuilla ja tämähän varaa Windowsissa tilan lokaalista muistista, joka näin ollen voi loppua. Pitäisi tehdä GlobalAllocia käyttävä versio Windowsia varten. Dynaamisen muistin ottaminen pitäisi muutenkin hoitaa isompina "möykkyinä" (kuin mallocin antamat pienet palaset), joista sitten jaetaan itse pienempiä möykkyjä. Tässä homma kävisi helposti kun muistia ei vapautella kuin kaikki "kerralla tyyliin". ========== Muutoksia: ========== 24.10.1993/vl: + \n jne. talletetaan \n, ei rivinvaihtona + käännöstiedostossa täytyy kirjoittaa: lf => \n cr => \r tab => \t \ => \\ " => \'' 23.8.1995/vl: - #include " " käsittely - puutteet lisätään joka tapauksessa vain viimeksi käytettyyn read_translate -tiedostoon - saman nimistä tiedostoa ei lueta kuin yhden kerran *****************************************************************************/ #include #include #include #include "mjonot.h" #include "language.h" #define SANAP 200 #define RIVIP 400 #define beep(i) static int read_this_translate(const char *tiedosto); /* Rekursiota varten */ typedef struct tTiedostolista_alkio{ const char *tiednimi; struct tTiedostolista_alkio *seuraava; } tTiedostolista_alkio; typedef struct { tTiedostolista_alkio *eka; tTiedostolista_alkio *viimeinen; } tTiedostolista; typedef struct { int kieli; tTiedostolista tiedostot; char tiedosto[80]; int etsi_aina; } GlobalType; typedef struct kaannos_tyyppi { /* Tulkkilistan yksi alkio */ struct kaannos_tyyppi *seuraava; char *kielet[KIELIA]; } kaannos_tyyppi; typedef struct { /* Tulkkilistan alku */ kaannos_tyyppi *eka; kaannos_tyyppi *vika; } tulkki_tyyppi; char TYHJA[1]=""; static tulkki_tyyppi tulkki={NULL,NULL}; static GlobalType Globals; static GlobalType *G=&Globals; static char *muunnokset[][2] = { {"\n","\\n"}, {"\r","\\r"}, {"\t","\\t"}, {"\\","\\\\"}, {"\"","\\''"}, /* " -> \'' */ {NULL,NULL} }; /****************************************************************************/ /* Tiedostolistan ja #include -käsittely */ /****************************************************************************/ /****************************************************************************/ static int lisaa_tiedosto_listaan(tTiedostolista *t,const char *nimi) { if ( t == NULL || nimi == NULL || *nimi == 0 ) return 1; { char *p = tee_jono(nimi); tTiedostolista_alkio *a = malloc(sizeof(*a)); if ( p == NULL ) { if ( a ) free(a); return 1; } if ( a == NULL ) { if ( p ) free(p); return 1; } a->tiednimi = p; a->seuraava = NULL; if ( t->eka == NULL ) t->eka = a; else t->viimeinen->seuraava = a; t->viimeinen = a; return 0; } } /****************************************************************************/ static int tyhjenna_tiedosto_lista(tTiedostolista *t) { tTiedostolista_alkio *a,*temp; if ( t == NULL || t->eka == NULL ) return 1; for ( a = t->eka; a != NULL; ) { temp = a; a = a->seuraava; free(temp->tiednimi); free(temp); } return 0; } /****************************************************************************/ static int onko_tiedosto_listassa(tTiedostolista *t,const char *nimi) { tTiedostolista_alkio *a; int i; if ( t == NULL || nimi == NULL || *nimi == 0 ) return -1; for ( i = 0, a = t->eka; a != NULL; i++, a = a->seuraava ) { if ( strcmp(a->tiednimi,nimi) == 0 ) return i; } return -1; } /****************************************************************************/ static char nimi_osa[20]; static const char *vain_nimi(const char *tiedosto) { const char *p = strrchr(tiedosto,'\\'); if ( p == NULL ) p = strrchr(tiedosto,':'); if ( p ) p++; else p = tiedosto; kopioi_jono(N_S(nimi_osa),p); jono_pieneksi(nimi_osa); return nimi_osa; } /****************************************************************************/ static char *polku_osa(char *polku,int mp,const char *tiedosto) { char *p; kopioi_jono(polku,mp,tiedosto); p = strrchr(polku,'\\'); if ( p == NULL ) p = strrchr(polku,':'); if ( p ) p++; else p = polku; *p = 0; return polku; } /****************************************************************************/ static int kasittele_preposessor(char *s) /* Käsittelee vain rivin #include "tiedosto". ** Tämä johtaa rekursiiviseen kutsuun lukemisfunktiossa. ** Tiedostosta hyväsksytään muodot: ** oma.tra - luetaan samasta polusta kuin varsinainen tiedosto ** ..\oma.tra - luetaan tasoa ylempää kuin varsinainen tiedosto ** c:\tra\oma.tra - luetaan absoluuttisesta polusta ----------------------------------------------------------------------------*/ { const char *include = "#include \""; int li = strlen(include); const char *nimi; static char polku1[90],polku2[70]; char *tiedosto = polku1; poista_tyhjat(s); if ( strncmp(s,include,li) != 0 ) return 0; s = s+li; poista_merkit(s,"\"\n"); nimi = vain_nimi(s); if ( onko_tiedosto_listassa(&G->tiedostot,nimi) >= 0 ) return 0; polku_osa(N_S(polku1),G->tiedosto); polku_osa(N_S(polku2),s); if ( polku2[0] == '.' || strpbrk(polku2,":\\") == NULL ) liita_jono(N_S(polku1),s); else tiedosto = s; read_this_translate(tiedosto); return 0; } /****************************************************************************/ /* Tiedostolistan ja #include -käsittely loppu */ /****************************************************************************/ /****************************************************************************/ static int muutos_index(const char *s,int suunta) { int i; for (i=0; muunnokset[i][0]; i++) { if ( strncmp(s,muunnokset[i][suunta],strlen(muunnokset[i][suunta])) == 0 ) return i; } return -1; } /****************************************************************************/ static const char *muuta(const char *s,int suunta) /* suunta = 0 => muunnos muuttuja->tiedosto ** = 1 => muunnos tiedosto->muuttuja */ { static char muutos[SANAP]; const char *p; int i,m=0; if ( suunta < 0 || 1 < suunta ) return s; for (p=s; *p; ) { if ( ( i = muutos_index(p,suunta)) >= 0 ) { /* Onko muutettava? */ int lm = strlen(muunnokset[i][1-suunta]), /* Pituudet jemmaan */ lp = strlen(muunnokset[i][suunta]); if ( sizeof(muutos)-1 <= m+lm ) break; /* Mahtuuko muutokseen? */ strcpy(muutos+m,muunnokset[i][1-suunta]); m+=lm; p+=lp; } else /* Tavallinen merkki */ muutos[m++] = *p++; if ( m >= sizeof(muutos)-1 ) break; } muutos[m]=0; return muutos; } /****************************************************************************/ static int lisaa_kaannos(kaannos_tyyppi *kaannos) { kaannos_tyyppi *uusi; uusi = malloc(sizeof(kaannos_tyyppi)); if (!uusi) return 1; *uusi = *kaannos; if (tulkki.eka==NULL) tulkki.eka=uusi; else tulkki.vika->seuraava = uusi; uusi->seuraava=NULL; tulkki.vika=uusi; return 0; } /****************************************************************************/ int lisaa_kaannos_rivi(char *st) /* Rivi muotoa: "Nimi","Name" tai ;.... kommentti */ { int k; char *p,*up; kaannos_tyyppi kaannos; if ( st == NULL || *st==';' || *st==0 || *st=='\n') return 0; if ( *st == '#' ) return kasittele_preposessor(st); for (k=0; ketsi_aina = 0; if ( G->kieli < 0 ) return -2; if ( kieli >= KIELIA ) { if ( kieli == ETSI_KAANNOS_AINA ) { kieli = 0; G->etsi_aina = 1; } else kieli = KIELIA-1; } G->kieli = kieli; return 0; } /****************************************************************************/ static int read_this_translate(const char *tiedosto) { FILE *f; static char st[RIVIP]; const char *nimi = vain_nimi(tiedosto); if ( onko_tiedosto_listassa(&G->tiedostot,nimi) >= 0 ) return -4; lisaa_tiedosto_listaan(&G->tiedostot,nimi); f = fopen(tiedosto,"rt"); if ( !f ) { f = fopen(tiedosto,"wt"); /* Luodaan tyhjä tiedosto */ if ( f ) fclose(f); return -1; } while (1) { if ( !fgets(st,sizeof(st),f) ) break; if ( lisaa_kaannos_rivi(st) ) break; } fclose(f); return 0; } /****************************************************************************/ int read_translate(const char *tiedosto,int kieli) { set_language(kieli); if ( kieli < 0 ) return -2; if ( strcmp(tiedosto,G->tiedosto) == 0 ) return -3; /* Ei lueta samaa uud.*/ kopioi_jono(N_S(G->tiedosto),tiedosto); return read_this_translate(tiedosto); } /****************************************************************************/ static const char *add_string(const char *s) { char st[RIVIP],sa[SANAP]; FILE *f = fopen(G->tiedosto,"at"); int k; s = muuta(s,0); if ( strlen(s) > sizeof(sa)-10 ) { fprintf(f,";### Liian pitkä : %s\n",s); fclose(f); return "### Liian pitkä ###"; } sprintf(st,"\"%s\"",s); for (k=1; kseuraava; for (k=0; kkielet[k] != TYHJA ) free(t->kielet[k]); } free(t); } tulkki.eka = NULL; G->kieli = 0; tyhjenna_tiedosto_lista(&G->tiedostot); } /****************************************************************************/ const char *translate(const char *s,int add) { kaannos_tyyppi *p = tulkki.eka; int oli_tuloksena = 0; /* Siltä varalta, että jo käännettyä käännetään */ if ( G->kieli <= 0 && !G->etsi_aina ) return s; while ( p != NULL ) { if ( strcmp(p->kielet[0],s) == 0 ) return p->kielet[G->kieli]; if ( strcmp(p->kielet[G->kieli],s) == 0 ) oli_tuloksena = 1; p = p->seuraava; } if ( oli_tuloksena ) return s; return add ? add_string(s) : s; } /****************************************************************************/ const char *translate_back(const char *s) { kaannos_tyyppi *p = tulkki.eka; if ( G->kieli <= 0 && !G->etsi_aina ) return s; while ( p != NULL ) { if ( strcmp(p->kielet[G->kieli],s) == 0 ) return p->kielet[0]; p = p->seuraava; } return s; } /****************************************************************************/ int language(void) { return (G->etsi_aina ? ETSI_KAANNOS_AINA : G->kieli); }