/* Copyright (c) 2000-2010, Dirk Krause All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above opyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. * Neither the name of the Dirk Krause nor the names of contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ /** @file stc.c The stc program. */ #include #include "dk.h" #include "dksf.h" #include "dkmem.h" #include "dkenc.h" #include "dksto.h" #include "dkstr.h" #include "dkapp.h" #include "dkstream.h" #include "dklogc.h" #include "dkerror.h" #if DK_HAVE_STDLIB_H #include #endif #if DK_HAVE_UNISTD_H #include #endif #if DK_HAVE_CTYPE_H #include #endif #include "dklic.h" #include "dktools-version.h" #line 73 "stc.ctr" #ifndef INBUFFERSIZE /** Size of input buffer. */ #define INBUFFERSIZE 16384 #endif /** Separator for parts of a path name. */ #if DK_HAVE_FEATURE_BACKSLASH static char fn_sep[] = { "\\" }; #else static char fn_sep[] = { "/" }; #endif /** System configuration directory. */ static char sysconfdir[] = { DK_SYSCONFDIR }; /** Program group name. */ static char packagename[] = { "dktools" }; /** Error messages (filled using string finder). */ static char *error_messages[24]; /** String finder entries. */ static dk_string_finder_t error_finder[] = { { (char *)"/e/00", &(error_messages[0]), (char *)"No entry for key\"" }, { (char *)"/e/01", &(error_messages[1]), (char *)"\" in language \"" }, { (char *)"/e/02", &(error_messages[2]), (char *)"\"!" }, { (char *)"/e/03", &(error_messages[3]), (char *)"Not enough memory!" }, { (char *)"/e/04", &(error_messages[4]), (char *)"Empty language spec!" }, { (char *)"/e/05", &(error_messages[5]), (char *)"" }, { (char *)"/e/06", &(error_messages[6]), (char *)"No value!" }, { (char *)"/e/07", &(error_messages[7]), (char *)"" }, { (char *)"/e/08", &(error_messages[8]), (char *)"Missing \"=\"!" }, { (char *)"/e/09", &(error_messages[9]), (char *)"" }, { (char *)"/e/10", &(error_messages[10]), (char *)"Failed to write to file \"" }, { (char *)"/e/11", &(error_messages[11]), (char *)"\"!" }, { (char *)"/e/12", &(error_messages[12]), (char *)"Failed to create directory \"" }, { (char *)"/e/13", &(error_messages[13]), (char *)"\"!" }, { (char *)"/e/14", &(error_messages[14]), (char *)"Unfinished entry name." }, { (char *)"/e/15", &(error_messages[15]), (char *)"Entry value should be quoted." }, { (char *)"/e/16", &(error_messages[16]), (char *)"Missing final quote sign." }, { (char *)"/m/cc", &(error_messages[17]), (char *)"Current configuration:" }, { (char *)"/m/on", &(error_messages[18]), (char *)"on" }, { (char *)"/m/off", &(error_messages[19]), (char *)"off" }, { (char *)"/m/plain", &(error_messages[20]), (char *)"force plain output" }, { (char *)"/e/17", &(error_messages[21]), (char *)"Language_Region.Encoding string too long!" }, { (char *)"/e/18", &(error_messages[22]), (char *)"Failed to UTF-8 decode string!" }, { (char *)"/e/19", &(error_messages[23]), (char *)"UTF-8 encoded string contains characters above 0xFF!" }, { NULL, NULL, NULL} }; /** Preference key: Force plain output. */ static char str_pref_plain[] = { "/plain" }; /** Boolean value: on. */ static char str_pref_on[] = { "on" }; /** Boolean value: off. */ static char str_pref_off[] = { "off" }; /** Preference key to retrieve languages for automatic completion. */ static char str_pref_auto_lang[] = { "/languages/mostly-ascii7" }; /** File open mode. */ static char str_r[] = { "r" }; /** Exit status code. */ static int exval = 0; /** Application. */ dk_app_t *app = NULL; /** Input buffer. */ static char inbuffer[INBUFFERSIZE]; /** Table entry for one language. */ typedef struct { char *lang; /**< Language. */ char *value; /**< Entry value. */ unsigned long lineno; /**< Line number in source file. */ } entry_for_lang; /** Convert string to lower-case. @param str String to convert. */ static void string_lower DK_P1(char *,str) { char *ptr; ptr = str; while(*ptr) { if(isupper(*ptr)) { *ptr = tolower(*ptr); } ptr++; } } /** Release one entry for a language. @param e Entry to release. */ static void free_entry_for_lang DK_P1(entry_for_lang *,e) { char *cptr; cptr = e->value; if(cptr) dk_delete(cptr) ; dk_delete(e); } /** Entry for a key. */ typedef struct { char *key; /**< Key (entry name). */ dk_storage_t *e; /**< Container of entry_for_lang entries. */ dk_storage_iterator_t *ei; /**< Iterator for \a e. */ entry_for_lang *current; /**< Current language-entry to process. */ unsigned long lineno; /**< Source file line number. */ } entry_for_key; /** Free entry for key. @param e Entry to release. */ static void free_entry_for_key DK_P1(entry_for_key *,e) { char *cptr; entry_for_lang *el; cptr = e->key; if(cptr) dk_delete(cptr) ; if((e->e) && (e->ei)) { dksto_it_reset(e->ei); while((el = (entry_for_lang *)dksto_it_next(e->ei)) != NULL) { free_entry_for_lang(el); } } if(e->e) { dksto_close(e->e); } e->key = NULL; e->e = NULL; e->ei = NULL; e->current = NULL; dk_delete(e) ; } /** Compare two language names (language/region/encoding). @param p1 Left language. @param p2 Right language. @param cr Comparison criteria. @return Comparison result. */ int compare_languages DK_P3(void *,p1, void *,p2, int,cr) { int back = 0; if(p1 && p2) { back = strcmp((char *)p1, (char *)p2); if(back < 0) back = -1; if(back > 0) back = 1; } return back; } /** Compare entries by language. @param p1 Left entry. @param p2 Right entry. @param cr Comparison criteria (ignored). @return Comparison result. */ int compare_entries_for_lang DK_P3(void *,p1, void *,p2, int,cr) { int back = 0; entry_for_lang *e1, *e2; if(p1 && p2) { switch(cr) { case 1: { e1 = (entry_for_lang *)p1; if(e1->lang) { back = strcmp((e1->lang), (char *)p2); } } break; default: { e1 = (entry_for_lang *)p1; e2 = (entry_for_lang *)p2; if((e1->lang) && (e2->lang)) { back = strcmp((e1->lang), (e2->lang)); } } break; } } if(back > 0) back = 1; if(back < 0) back = -1; return back; } /** Compare entries by key name. @param p1 Left entry. @param p2 Right entry. @param cr Comparison criteria (ignored). @return Comparison result. */ int compare_entries_for_key DK_P3(void *,p1, void *,p2, int,cr) { int back = 0; entry_for_key *e1, *e2; if(p1 && p2) { switch(cr) { case 1: { e1 = (entry_for_key *)p1; if(e1->key) { back = strcmp((e1->key),(char *)p2); } } break; default: { e1 = (entry_for_key *)p1; e2 = (entry_for_key *)p2; if((e1->key) && (e2->key)) { back = strcmp((e1->key),(e2->key)); } } break; } } if(back > 0) back = 1; if(back < 0) back = -1; return back; } /** Create new entry for a key. @param name Key name. @param lineno Line number in source file. @return Entry pointer on success, NULL on error. */ static entry_for_key * new_entry_for_key DK_P2(char *,name, unsigned long,lineno) { entry_for_key *back = NULL; back = dk_new(entry_for_key,1); if(back) { back->key = dkstr_dup(name); back->e = dksto_open(0); back->ei = NULL; back->current = NULL; back->lineno = lineno; if(back->e) { back->ei = dksto_it_open(back->e); if(back->ei) { dksto_set_comp(back->e, compare_entries_for_lang, 0); } } back->current = NULL; if(!((back->key) && (back->e) && (back->ei))) { free_entry_for_key(back); back = NULL; } } return back; } /** Stc job. */ typedef struct { char *filename; /**< Source file name. */ char *dirname; /**< Output directory name. */ dk_storage_t *l; /**< Languages container. */ dk_storage_iterator_t *li; /**< Languages iterator. */ dk_storage_t *e; /**< Entries sorted by key container. */ dk_storage_iterator_t *ei; /**< Entries sorted by key iterator. */ FILE *inputfile; /**< Input file. */ dk_uword vers_major; /**< Major version number of input file. */ dk_uword vers_minor; /**< Minor version number of input file. */ dk_uword number; /**< ?. */ entry_for_key *cefk; /**< Current entry for key to process. */ size_t output_filename_length; /**< Output file name length. */ size_t output_language_length; /**< Output language string length. */ char *output_filename_buffer; /**< Output file name buffer. */ char *language_buffer; /**< Language name buffer. */ char *current_language; /**< Current language processed. */ char *language_test; /**< Language for test. */ char *language_comp; /**< Language for comparison. */ int force_plain; /**< Flag: Force plain output. */ char *language_auto; /**< Language for automatic compl. */ char *language_aut2; /**< Language for autoamtic compl 2. */ } StcCommand; /** Clean up stc job. @param c Stc job. */ static void cleanup DK_P1(StcCommand *,c) { entry_for_key *efk; char *cptr; if(c->language_auto) { dk_delete(c->language_auto); } if(c->language_aut2) { dk_delete(c->language_aut2); } c->language_auto = c->language_aut2 = NULL; if(c->e) { if(c->ei) { dksto_it_reset(c->ei); while((efk = (entry_for_key *)dksto_it_next(c->ei)) != NULL) { free_entry_for_key(efk); } } dksto_close(c->e); } if(c->l) { if(c->li) { dksto_it_reset(c->li); while((cptr = (char *)dksto_it_next(c->li)) != NULL) { dk_delete(cptr); } } dksto_close(c->l); } } /** Languages we can attempt to auto complete. I.e. create la/re/UTF-8 entries from la/re or la/re entries from la/re/UTF-8. Only possible for languages with all characters in character set 0x00 ... 0xFF. */ static char auto_complete_languages[] = { "de en fr nl be sp pt pl cs hu sv no da" }; /** Initialize stc job. @param c Stc job. @return 1 on success, 0 on error. */ static int initialize DK_P1(StcCommand *,c) { int back = 0; char auto_lang_buffer[4096]; c->l = dksto_open(0); if(c->l) { c->li = dksto_it_open(c->l); dksto_set_comp(c->l, compare_languages, 0); } c->e = dksto_open(0); if(c->e) { c->ei = dksto_it_open(c->e); dksto_set_comp(c->e, compare_entries_for_key, 0); } if(dkapp_get_pref(app, str_pref_auto_lang, auto_lang_buffer, sizeof(auto_lang_buffer), 0)) { c->language_auto = dkstr_dup(auto_lang_buffer); c->language_aut2 = dkstr_dup(auto_lang_buffer); } else { c->language_auto = dkstr_dup(auto_complete_languages); c->language_aut2 = dkstr_dup(auto_complete_languages); } if((c->l) && (c->li) && (c->e) && (c->ei)) { if((c->language_auto) && (c->language_aut2)) { back = 1; c->vers_major = c->vers_minor = 1; } } if(!back) { /* ERROR: Not enough memory */ dkapp_log_msg(app, DK_LOG_LEVEL_ERROR, &(error_messages[3]), 1); } return back; } /** Copy contents of string one place to the left. @param str String to modify. */ static void copy_to_left DK_P1(char *,str) { char *xptr, *yptr; xptr = str; if(*xptr) { yptr = xptr; yptr++; while(*yptr) { *xptr = *yptr; xptr++; yptr++; } *xptr = '\0'; } } /** Squeeze a string. @param str String to squeeze. */ static void squeeze_string DK_P1(char *,str) { char *ptr; ptr = str; while(*ptr) { if(*ptr == '\\') { switch(ptr[1]) { case '\0' : { *ptr = '\0'; } break; case 'n' : { *ptr = '\n'; ptr++; copy_to_left(ptr); } break; case 't' : { *ptr = '\t'; ptr++; copy_to_left(ptr); } break; case 'b' : { *ptr = '\b'; ptr++; copy_to_left(ptr); } break; case 'r' : { *ptr = '\r'; ptr++; copy_to_left(ptr); } break; default : { *ptr = ptr[1]; ptr++; copy_to_left(ptr); } break; } } else { ptr++; } } } /** String: opening bracket. */ static char str_bropen[] = { "(" }; /** String: Closing bracket. */ static char str_brclose[] = { ")" }; /** String: space. */ static char str_space[] = { " " }; /** Set of separators. */ static char str_separators[] = { " \t.," }; /** Read input file. @param c Stc job. @return 1 on success, 0 on error. */ static int read_input DK_P1(StcCommand *,c) { int back = 0; int can_continue; char *cptr, *linestart, *valstart; entry_for_key *efk; entry_for_lang *efl; unsigned long lineno; char *errmsgs[5]; if(app) { c->inputfile = dkapp_fopen(app, c->filename, str_r); } else { c->inputfile = dksf_fopen(c->filename, str_r); } if(c->inputfile) { lineno = 0UL; back = 1; can_continue = 1; c->cefk = NULL; dkapp_set_source_filename(app, c->filename); while(can_continue && back) { if(fgets(inbuffer,sizeof(inbuffer),(c->inputfile))) { lineno++; dkapp_set_source_lineno(app, lineno); cptr = dkstr_chr(inbuffer, '#'); if(cptr) *cptr = '\0'; linestart = dkstr_start(inbuffer, NULL); if(linestart) { switch(*linestart) { case '$' : { /* special instruction */ linestart++; linestart = dkstr_start(linestart, NULL); if(linestart) { cptr = dkstr_next(linestart, NULL); if(strcmp(linestart, "version") == 0) { linestart = dkstr_next(cptr, str_separators); if(linestart) { unsigned u1; if(sscanf(cptr, "%u", &u1) == 1) { c->vers_major = u1; } if(sscanf(linestart, "%u", &u1) == 1) { c->vers_minor = u1; } } } } } break; case '"' : { /* new key */ dkstr_chomp(linestart, NULL); linestart++; cptr = dkstr_rchr(linestart, '"'); if(cptr) { *cptr = '\0'; } else { errmsgs[0] = error_messages[14]; errmsgs[1] = str_space; errmsgs[2] = str_bropen; errmsgs[3] = dkstr_start(inbuffer, NULL); if(!(errmsgs[3])) { errmsgs[3] = str_space; } errmsgs[4] = str_brclose; dkapp_log_msg(app,DK_LOG_LEVEL_WARNING,errmsgs,5); } squeeze_string(linestart); efk = (entry_for_key *)dksto_it_find_like(c->ei, linestart, 1); if(!efk) { efk = new_entry_for_key(linestart, lineno); if(efk) { efk->lineno = lineno; if(dksto_add((c->e),(void *)efk)) { c->cefk = efk; } else { /* ERROR NOT ENOUGH MEMORY */ errmsgs[0] = error_messages[3]; dkapp_log_msg(app,DK_LOG_LEVEL_ERROR,errmsgs,1); back = 0; } } else { /* ERROR NOT ENOUGH MEMORY */ errmsgs[0] = error_messages[3]; dkapp_log_msg(app,DK_LOG_LEVEL_ERROR,errmsgs,1); back = 0; } } } break; default : { /* value */ valstart = dkstr_chr(linestart, '='); if(valstart) { *(valstart++) = '\0'; valstart = dkstr_start(valstart, NULL); if(valstart) { linestart = dkstr_start(linestart, NULL); if(linestart) { dkstr_chomp(linestart, NULL); dkstr_chomp(valstart, NULL); if(*valstart == '"') { valstart++; cptr = dkstr_rchr(valstart, '"'); if(cptr) { *cptr = '\0'; } else { errmsgs[0] = error_messages[16]; dkapp_log_msg(app,DK_LOG_LEVEL_WARNING,errmsgs,1); } } else { errmsgs[0] = error_messages[15]; dkapp_log_msg(app,DK_LOG_LEVEL_WARNING,errmsgs,1); } string_lower(linestart); cptr = (char *)dksto_it_find_like(c->li, linestart, 1); if(!cptr) { cptr = dkstr_dup(linestart); if(cptr) { if(!(dksto_add(c->l, (void *)cptr))) { /* ERROR NOT ENOUGH MEMORY */ errmsgs[0] = error_messages[3]; dkapp_log_msg(app,DK_LOG_LEVEL_ERROR,errmsgs,1); back = 0; } } else { /* ERROR NOT ENOUGH MEMORY */ errmsgs[0] = error_messages[3]; dkapp_log_msg(app,DK_LOG_LEVEL_ERROR,errmsgs,1); back = 0; } } if(back) { squeeze_string(valstart); efl = dk_new(entry_for_lang,1); if(efl) { efl->lang = cptr; efl->value = dkstr_dup(valstart); efl->lineno = lineno; if(efl->value) { if(!(dksto_add((c->cefk)->e, (void *)efl))) { /* ERROR NOT ENOUGH MEMORY */ errmsgs[0] = error_messages[3]; dkapp_log_msg(app,DK_LOG_LEVEL_ERROR,errmsgs,1); back = 0; dk_delete(efl->value); efl->value = NULL; efl->lang = NULL; dk_delete(efl); efl = NULL; } } else { /* ERROR NOT ENOUGH MEMORY */ errmsgs[0] = error_messages[3]; dkapp_log_msg(app,DK_LOG_LEVEL_ERROR,errmsgs,1); back = 0; efl->lang = NULL; dk_delete(efl); efl = NULL; } } else { /* ERROR NOT ENOUGH MEMORY */ errmsgs[0] = error_messages[3]; dkapp_log_msg(app,DK_LOG_LEVEL_ERROR,errmsgs,1); back = 0; } } } else { /* ERROR EMPTY KEY */ errmsgs[0] = error_messages[4]; dkapp_log_msg(app,DK_LOG_LEVEL_ERROR,errmsgs,1); back = 0; } } else { /* ERROR NO VALUE IN LINE */ errmsgs[0] = error_messages[6]; dkapp_log_msg(app,DK_LOG_LEVEL_ERROR,errmsgs,1); back = 0; } } else { /* ERROR WRONG INPUT LINE, MISSING = */ errmsgs[0] = error_messages[8]; dkapp_log_msg(app,DK_LOG_LEVEL_ERROR,errmsgs,1); back = 0; } } break; } } } else { can_continue = 0; } } dkapp_set_source_lineno(app, 0UL); dkapp_set_source_filename(app, NULL); fclose(c->inputfile); } else { dkapp_err_fopenr(app, c->filename); } return back; } /** Find needed buffer length (length of larget entry). @param c Stc job. @return Needed buffer length. */ static size_t find_buffer_length DK_P1(StcCommand *,c) { size_t back = 0; size_t lgt; char *x; dksto_it_reset(c->li); while((x = (char *)dksto_it_next(c->li)) != NULL) { lgt = strlen(x); if(lgt > back) { back = lgt; } } c->output_language_length = (back + 1); x = dkstr_rchr(c->filename, fn_sep[0]); if(!x) x = c->filename; else x++; back += strlen(x) + 4; back += strlen(c->dirname) + 1; return back; } /** Check whether directory \a s exists, create if necessary. @param s Directory name. @return 1 on success (directory can be used), 0 on error. */ static int check_directory DK_P1(char *,s) { int back = 1; dk_stat_t *st; char *errmsgs[5]; st = dkstat_open(s); if(st) { if(((st->filetype) & (~(DK_FT_SYMLINK))) != DK_FT_DIR) { /* ERROR WRONG FILETYPE */ back = 0; } } else { if(!dksf_mkdir(s, 0755)) { /* ERROR MKDIR FAILED */ back = 0; } } if(!back) { errmsgs[0] = error_messages[12]; errmsgs[1] = s; errmsgs[2] = error_messages[13]; dkapp_log_msg(app,DK_LOG_LEVEL_ERROR,errmsgs,3); } return back; } /** Create directories for language/name/encoding. @param c Stc job. @return 1 on success, 0 on error. */ static int create_directories DK_P1(StcCommand *,c) { int back = 1; char *buffer, *lbuffer; char *x; char *lang, *reg, *enc; if(!check_directory(c->dirname)) { back = 0; } buffer = c->output_filename_buffer; lbuffer = c->language_buffer; if(back) { dksto_it_reset(c->li); while((x = (char *)dksto_it_next(c->li)) != NULL) { strcpy(lbuffer,x); lang = lbuffer; enc = dkstr_chr(lang, '.'); if(enc) { *(enc++) = '\0'; } reg = dkstr_chr(lang, '_'); if(reg) { *(reg++) = '\0'; } if(lang) { strcpy(buffer, c->dirname); strcat(buffer, fn_sep); strcat(buffer, lang); if(!check_directory(buffer)) { back = 0; } if(reg) { strcpy(buffer, c->dirname); strcat(buffer, fn_sep); strcat(buffer, lang); strcat(buffer, fn_sep); strcat(buffer, reg); if(!check_directory(buffer)) { back = 0; } if(enc) { strcpy(buffer, c->dirname); strcat(buffer, fn_sep); strcat(buffer, lang); strcat(buffer, fn_sep); strcat(buffer, reg); strcat(buffer, fn_sep); strcat(buffer, enc); if(!check_directory(buffer)) { back = 0; } } } else { if(enc) { strcpy(buffer, c->dirname); strcat(buffer, fn_sep); strcat(buffer, lang); strcat(buffer, fn_sep); strcat(buffer, enc); if(!check_directory(buffer)) { back = 0; } } } } } } return back; } /** Retrieve file name component from a full path name. @param fullname Full path name. @return File name or NULL. */ static char * get_filename DK_P1(char *,fullname) { char *back = NULL; back = dkstr_rchr(fullname, fn_sep[0]); if(back) back++; else back = fullname; return back; } /** File header for the binary string tables. */ static char file_header[] = { "STRT-2" }; /** Output file name suffix. */ static char stt_suffix[] = { ".stt" }; /** Empty string. */ static char empty_string[] = { "" }; /** File open mode. */ static char str_wb[] = { "wb" }; /** Write entries for the current language. @param c Stc job. @return 1 on success, 0 on error. */ static int write_language DK_P1(StcCommand *,c) { int back = 1; char *filename, *lc, *lt, *currentlang, *lang, *reg, *enc; entry_for_key *efk; dk_stream_t *strm; dk_uword uw; char *errmsgs[5]; /* Prepare the filename */ strcpy(c->output_filename_buffer, c->dirname); strcat(c->output_filename_buffer, fn_sep); filename = c->output_filename_buffer; while(*filename) filename++; strcpy(filename, c->current_language); while(*filename) { if(*filename == '_') *filename = fn_sep[0]; if(*filename == '.') *filename = fn_sep[0]; filename++; } filename = get_filename(c->filename); strcat(c->output_filename_buffer, fn_sep); strcat(c->output_filename_buffer, filename); filename = dksf_get_file_type_dot(c->output_filename_buffer); if(filename) { strcpy(filename, stt_suffix); } else { strcat(c->output_filename_buffer, stt_suffix); } /* Set current pointer for all entries */ dksto_it_reset(c->ei); lc = c->language_comp; lt = c->language_test; currentlang = c->current_language; strcpy(lc, currentlang); lang = lc; enc = dkstr_chr(lang, '.'); if(enc) { *(enc++) = '\0'; } reg = dkstr_chr(lang, '_'); if(reg) { *(reg++) = '\0'; } while((efk = (entry_for_key *)dksto_it_next(c->ei)) != NULL) { efk->current = (entry_for_lang *)dksto_it_find_like((efk->ei),currentlang,1); if(!(efk->current)) { /* lang/reg */ if(lang && reg) { strcpy(lt, lang); strcat(lt, "_"); strcat(lt, reg); efk->current = (entry_for_lang *)dksto_it_find_like((efk->ei),lt,1); } } if(!(efk->current)) { /* lang/enc */ if(lang && enc) { strcpy(lt, lang); strcat(lt, "."); strcat(lt, enc); efk->current = (entry_for_lang *)dksto_it_find_like((efk->ei),lt,1); } } if(!(efk->current)) { /* lang */ if(lang) { strcpy(lt, lang); efk->current = (entry_for_lang *)dksto_it_find_like((efk->ei),lt,1); } } if(!(efk->current)) { /* any */ dksto_it_reset(efk->ei); efk->current = (entry_for_lang *)dksto_it_next(efk->ei); /* WARNING NO MATCHING ENTRY FOUND */ errmsgs[0] = error_messages[0]; errmsgs[1] = efk->key; errmsgs[2] = error_messages[1]; errmsgs[3] = c->current_language; errmsgs[4] = error_messages[2]; dkapp_set_source_lineno(app, efk->lineno); dkapp_set_source_filename(app, c->filename); dkapp_log_msg(app,DK_LOG_LEVEL_WARNING,errmsgs,5); dkapp_set_source_lineno(app, 0UL); dkapp_set_source_filename(app, NULL); } } /* Now write */ if(c->force_plain) { strm = dkapp_stream_openfile(app, c->output_filename_buffer, str_wb); } else { int reason; reason = 0; strm = dkapp_write_file(app, c->output_filename_buffer); } if(strm) { dkstream_write(strm, file_header, sizeof(file_header)); uw = c->vers_major; dkstream_wb_uword(strm, uw); uw = c->vers_minor; dkstream_wb_uword(strm, uw); uw = c->number; dkstream_wb_uword(strm, uw); uw = 0; dkstream_wb_uword(strm, uw); dksto_it_reset(c->ei); while((efk = (entry_for_key *)dksto_it_next(c->ei)) != NULL) { dkstream_wb_string(strm, efk->key); } dksto_it_reset(c->ei); while((efk = (entry_for_key *)dksto_it_next(c->ei)) != NULL) { filename = empty_string; if(efk->current) { if((efk->current)->value) { filename = (efk->current)->value; } } dkstream_wb_string(strm, filename); } dkstream_close(strm); } else { /* ERROR FAILED TO WRITE TO FILE */ errmsgs[0] = error_messages[10]; errmsgs[1] = c->output_filename_buffer; errmsgs[2] = error_messages[11]; dkapp_log_msg(app, DK_LOG_LEVEL_ERROR,errmsgs,3); back = 0; } return back; } /** Write entries for all languages to output. @param c Stc job. @return 1 on success, 0 on errors. */ static int write_all_languages DK_P1(StcCommand *,c) { int back = 1; dk_uword uw; char *efl; uw = 0; dksto_it_reset(c->ei); while(dksto_it_next(c->ei)) { uw++; } c->number = uw; dksto_it_reset(c->li); while((efl = (char *)dksto_it_next(c->li)) != NULL) { c->current_language = efl; if(!write_language(c)) { back = 0; } } return back; } /** Write output file. @param c Stc job. @return 1 on success, 0 on error. */ static int write_output DK_P1(StcCommand *,c) { int back = 0; size_t lgt; char *buffer, *langbuffer, *langtest, *langcomp, *errmsgs[5]; lgt = c->output_filename_length = find_buffer_length(c); buffer = dk_new(char,lgt); if(buffer) { c->output_filename_buffer = buffer; lgt = c->output_language_length; langbuffer = dk_new(char,lgt); langtest = dk_new(char,lgt); langcomp = dk_new(char,lgt); if(langbuffer && langtest && langcomp) { c->language_buffer = langbuffer; c->language_test = langtest; c->language_comp = langcomp; if(create_directories(c)) { back = write_all_languages(c); } c->language_buffer = NULL; c->language_test = NULL; c->language_comp = NULL; } else { errmsgs[0] = error_messages[3]; dkapp_log_msg(app,DK_LOG_LEVEL_ERROR,errmsgs,1); } if(langbuffer) dk_delete(langbuffer); if(langtest) dk_delete(langtest); if(langcomp) dk_delete(langcomp); c->output_filename_buffer = NULL; dk_delete(buffer); } else { errmsgs[0] = error_messages[3]; dkapp_log_msg(app,DK_LOG_LEVEL_ERROR,errmsgs,1); } return back; } /** Encoding suffix for UTF-8. */ static char str_utf8[] = { ".utf-8" }; /** Check whether a language/region/encoding needs handling (data is incomplete). @param c Stc command. @param n Language/region/encoding. @return 1=yes, 0=no. */ static int language_needs_handling DK_P2(StcCommand *,c, char *,n) { int back = 0; char buffer[32]; /* 12 should be sufficient */ char *cptr, *cp1, *cp2; if(n) { if(strlen(n) < sizeof(buffer)) { strcpy(buffer, n); cptr = dkstr_chr(buffer, '_'); if(cptr) { *cptr = '\0'; } cptr = dkstr_chr(buffer, '.'); if(cptr) { *cptr = '\0'; } strcpy(c->language_aut2, c->language_auto); cp1 = dkstr_start(c->language_aut2, NULL); while((!back) && (cp1)) { cp2 = dkstr_next(cp1, NULL); if(dkstr_casecmp(cp1, buffer) == 0) { back = 1; } if(!back) { cp1 = cp2; } } } else { /* ERROR: language/region/encoding string too long */ dkapp_log_msg(app, DK_LOG_LEVEL_ERROR, &(error_messages[21]), 1); } } return back; } /** Create counterpart name for language/region/encoding. Examples: de_DE.UTF-8 -> de_DE, de_DE -> de_DE.UTF-8. @param l Original language/region/encoding. @param b Result buffer. @param sz Size of \a b (bytes). @param enc Current encoding. @return 1 on success, 0 on error. */ static int create_counterpart_name DK_P4(char *,l, char *,b, size_t, sz, int *,enc) { int back = 0; size_t sz_appendix, sz_buffer; if(strlen(l) < sz) { strcpy(b, l); back = 1; sz_appendix = strlen(str_utf8); sz_buffer = strlen(b); *enc = 0; if(sz_buffer > sz_appendix) { if(dkstr_casecmp(&(b[sz_buffer - sz_appendix]),str_utf8) == 0) { *enc = 1; } } if(*enc) { b[sz_buffer - sz_appendix] = '\0'; } else { if((sz_buffer + sz_appendix) < sz) { strcat(b, str_utf8); } else { back = 0; /* ERROR: language/encoding/region string too long */ dkapp_log_msg(app, DK_LOG_LEVEL_ERROR, &(error_messages[21]), 1); } } } return back; } /** Add counter part in other encoding. @param c Stc job. @param efk Entry for key. @param el Entry for language @param nc Counterpart language/region/encoding name. @param se Source encoding. @return 1 on success, 0 on error. */ static int add_counterpart DK_P5(StcCommand *,c, entry_for_key *,efk, entry_for_lang *,el, char *,nc, int,se) { int back = 1; /* function result */ char *newname; /* new name, new string */ char *languagename; /* the language processed */ int ec; /* error code from conversion */ /* add counterpart language to list of languages if necessary */ if((languagename = (char *)dksto_it_find_like(c->li, nc, 1)) == NULL) { newname = dkstr_dup(nc); if(newname) { if(!dksto_add(c->l, (void *)newname)) { back = 0; /* ERROR: Not enough memory */ dk_delete(newname); dkapp_log_msg(app, DK_LOG_LEVEL_ERROR, &(error_messages[3]), 1); } else { languagename = newname; } } else { back = 0; /* ERROR: Not enough memory */ dkapp_log_msg(app, DK_LOG_LEVEL_ERROR, &(error_messages[3]), 1); } } /* create and add new entry */ if((back) && (languagename)) { newname = NULL; ec = 0; if(se) { newname = dkenc_str_utf82bits8(el->value, &ec); } else { newname = dkenc_str_bits82utf8(el->value); if(!newname) { ec = DK_ERR_NOMEM; } } if(newname) { entry_for_lang *newefl; if(ec == DK_ERR_MATH_OOR) { dkapp_log_msg(app, DK_LOG_LEVEL_WARNING, &(error_messages[23]), 1); } newefl = dk_new(entry_for_lang,1); if(newefl) { newefl->lang = languagename; newefl->value = newname; newefl->lineno = el->lineno; if(!dksto_add(efk->e, (void *)newefl)) { newefl->lang = NULL; newefl->value = NULL; dk_delete(newname); dk_delete(newefl); newname = NULL; newefl = NULL; } } else { back = 0; /* ERROR: Not enough memory */ dk_delete(newname); newname = NULL; dkapp_log_msg(app, DK_LOG_LEVEL_ERROR, &(error_messages[3]), 1); } } else { back = 0; switch(ec) { case DK_ERR_INVALID_ARGS: { dkapp_log_msg(app, DK_LOG_LEVEL_ERROR, &(error_messages[22]), 1); } break; case DK_ERR_MATH_OOR: { dkapp_log_msg(app, DK_LOG_LEVEL_ERROR, &(error_messages[23]), 1); } break; default: { dkapp_log_msg(app, DK_LOG_LEVEL_ERROR, &(error_messages[3]), 1); } break; } } } return back; } /** Handle one language for an entry. @param c Stc job. @param efk Entry for key. @param el Entry for language. @return 1 on success, 0 on error. */ static int handle_language_for_entry DK_P3(StcCommand *,c, entry_for_key *,efk, entry_for_lang *,el) { int back = 1; char buffer[32]; int src_encoding; src_encoding = 0; if(create_counterpart_name(el->lang, buffer, sizeof(buffer), &src_encoding)) { if(!dksto_it_find_like(efk->ei,buffer,1)) { if(!add_counterpart(c,efk,el,buffer,src_encoding)) { back = 0; } } else { } } else { back = 0; /* ERROR: language/encoding/region string too long */ dkapp_log_msg(app, DK_LOG_LEVEL_ERROR, &(error_messages[21]), 1); } return back; } /** Automatically complete an entry if necessary and possible. @param c Stc job. @param efk Entry for one key. @return 1 on success, 0 on error. */ static int auto_complete_entry DK_P2(StcCommand *,c, entry_for_key *,efk) { int back = 0; dk_storage_t *e; /* list of entries */ dk_storage_iterator_t *ei; /* iterator for all entries */ entry_for_lang *el; e = dksto_open(0); ei = NULL; if(e) { ei = dksto_it_open(e); if(ei) { back = 1; dksto_set_comp(e, compare_entries_for_lang, 0); dksto_it_reset(efk->ei); while((el = (entry_for_lang *)dksto_it_next(efk->ei)) != NULL) { if(!dksto_add(e, (void *)el)) { back = 0; } } if(back) { dksto_it_reset(ei); while((el = (entry_for_lang *)dksto_it_next(ei)) != NULL) { dkapp_set_source_lineno(app, el->lineno); if(language_needs_handling(c, el->lang)) { if(!handle_language_for_entry(c,efk,el)) { back = 0; } } } } else { /* ERROR: Not enough memory to register language entry */ dkapp_log_msg(app, DK_LOG_LEVEL_ERROR, &(error_messages[3]), 1); } dksto_it_close(ei); } else { /* ERROR: Not enough memory for iterator */ dkapp_log_msg(app, DK_LOG_LEVEL_ERROR, &(error_messages[3]), 1); } dksto_close(e); e = NULL; } else { /* ERROR: Not enough memory for list */ dkapp_log_msg(app, DK_LOG_LEVEL_ERROR, &(error_messages[3]), 1); } return back; } /** Complete missing encodings if necessary and possible. @param c Stc job. @return 1 on success, 0 on error. */ static int auto_complete_encoding DK_P1(StcCommand *,c) { int back = 0; entry_for_key *efk; if((c->e) && (c->ei)) { back = 1; dkapp_set_source_filename(app, c->filename); dksto_it_reset(c->ei); while((efk = (entry_for_key *)dksto_it_next(c->ei)) != NULL) { dkapp_set_source_lineno(app, efk->lineno); if(!auto_complete_entry(c, efk)) { back = 0; } } dkapp_set_source_filename(app, NULL); dkapp_set_source_lineno(app, 0UL); } else { } return back; } /** Run normally. @param filename Input file name. @param dirname Output directory name. @param force_plain Flag: force plain output. */ static void run DK_P3(char *,filename, char *,dirname, int,force_plain) { StcCommand c; c.filename = filename; c.dirname = dirname; c.l = NULL; c.li = NULL; c.e = NULL; c.ei = NULL; c.inputfile = NULL; c.force_plain = force_plain; if(initialize(&c)) { if(read_input(&c)) { if(auto_complete_encoding(&c)) { write_output(&c); } else { } } } cleanup(&c); } /** Version number. */ static char the_version_number[] = { VERSNUMB } ; /** License terms. */ static char *license_terms[] = { "Redistribution and use in source and binary forms, with or without", "modification, are permitted provided that the following conditions are met:", "* Redistributions of source code must retain the above copyright notice, this", " list of conditions and the following disclaimer.", "* Redistributions in binary form must reproduce the above copyright notice,", " this list of conditions and the following disclaimer in the documentation", " and/or other materials provided with the distribution.", "* Neither the name of the Dirk Krause nor the names of other contributors may", " be used to endorse or promote products derived from this software without", " specific prior written permission.", "THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS IS\"", "AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE", "IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE", "ARE DISCLAIMED.", "IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY", "DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES", "(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;", "LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND", "ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT", "(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS", "SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.", NULL }; /** Print version number and license information. */ static void print_version DK_P0() { char **ptr; printf("\n"); printf( "stc 2 - string table compiler (part of the dklibs collection, version %s)\n", the_version_number ); printf("Copyright (C) 2001-2010 Dipl.-Ing. D. Krause\n"); printf("http://dktools.sourceforge.net/stc.html\n\n"); ptr = license_terms; while(*ptr) { printf("%s\n", *(ptr++)); } printf("\nLibraries used:\n\n"); ptr = dklic_get(); while(*ptr) { printf("%s\n", *(ptr++)); } printf("\n"); } /** Default help text if help text file is not found. */ static char *help_strings[] = { (char *)"stc ", (char *)"", (char *)"This program compiles a text-form string table into a set", (char *)"of binary string tables.", (char *)"The binary string tables are stored in and its", (char *)"subdirectories depending on languages, regions and encodings", (char *)"used in the string tables.", (char *)"", NULL }; /** Name of help text file. */ static char str_filename_stc_txt[] = { "stc.txt" }; /** Print help text. */ static void print_help DK_P0() { dkapp_help(app, str_filename_stc_txt, help_strings); } /** String table name. */ static char str_table_name_stc[] = { "stc" }; /** Long options handled by the program. */ static char *long_options[] = { /* 00 */ "p$lain", /* 01 */ "c$onfigure", /* 02 */ "u$nconfigure", /* 03 */ "r$eset", /* 04 */ "sh$ow-configuration", /* 05 */ "v$ersion", /* 06 */ "h$elp", NULL }; /** Convert text to boolean value. @param str Text to convert. @return 1 for true, 0 for false. */ static int decide DK_P1(char *,str) { int back = 0; if(str) { switch(*str) { case '\0': case '+': back = 1; break; case '-': back = 0; break; default: back = dkstr_is_on(str); break; } } return back; } /** Show current configuration. @param app Application @param fp Flag: Force plain output. */ static void show_configuration DK_P2(dk_app_t *,app, int, fp) { dkapp_stdout(app, error_messages[17]); fputc('\n', stdout); dkapp_stdout(app, "-p "); dkapp_stdout(app, error_messages[fp ? 18 : 19]); dkapp_stdout(app, " "); dkapp_stdout(app, error_messages[20]); fputc('\n', stdout); } /** The main() function of the stc program. @param argc Number of command line arguments. @param argv Command line arguments array. @return 0 on success, any other value indicates an error. */ #if DK_HAVE_PROTOTYPES int main(int argc, char *argv[]) #else int main(argc, argv) int argc; char *argv[]; #endif { int xargc, lfd, force_plain; char *thisarg, **xargv, **argptr; int helponly, versiononly, found, do_configure, do_unconfigure, do_showconf; char *infilename, *dirname; dk_fne_t *fne; int a_filename_was_found; #line 1803 "stc.ctr" app = dkapp_open_ext1(argc,argv,packagename,sysconfdir,0,0); do_configure = do_unconfigure = do_showconf = 0; if(app) { force_plain = 0; if(dkapp_get_pref(app, str_pref_plain, inbuffer, sizeof(inbuffer),0)) { if(dkstr_is_on(inbuffer)) { force_plain = 1; } else { force_plain = 0; } } xargc = dkapp_get_argc(app); xargv = dkapp_get_argv(app); dkapp_find_multi(app,error_finder,str_table_name_stc); helponly = versiononly = 0; infilename = dirname = NULL; argptr = xargv; lfd = 0; argptr++; lfd++; while(lfd < xargc) { thisarg = *argptr; if(*thisarg == '-') { thisarg++; switch(*thisarg) { case '-': { char *optarg; thisarg++; optarg = dkstr_chr(thisarg, '='); if(optarg) { *(optarg++) = '\0'; } found = dkstr_array_abbr(long_options, thisarg, '$', 1); switch(found) { case 0: { /* plain */ if(optarg) force_plain = decide(optarg); else force_plain = 1; } break; case 1: { /* configure */ if(optarg) do_configure = decide(optarg); else do_configure = 1; } break; case 2: { /* unconfigure */ if(optarg) do_unconfigure = decide(optarg); else do_unconfigure = 1; if(do_unconfigure) do_configure = 1; } break; case 3: { /* reset */ if(optarg) { if(decide(optarg)) force_plain = 0; } else { force_plain = 0; } } break; case 4: { /* show-configura */ if(optarg) do_showconf = decide(optarg); else do_showconf = 1; } break; case 5: { /* version */ if(optarg) versiononly = decide(optarg); else versiononly = 1; } break; case 6: { /* help */ if(optarg) helponly = decide(optarg); else helponly = 1; } break; } } break; case 'c': { do_configure = decide(++thisarg); } break; case 'u': { do_unconfigure = decide(++thisarg); if(do_unconfigure) do_configure = 1; } break; case 'r': { force_plain = 0; } break; case 'C': { do_showconf = decide(++thisarg); } break; case 'h': { helponly = decide(++thisarg); } break; case 'v': { versiononly = decide(++thisarg); } break; case 'p': { force_plain = decide(++thisarg); } break; default: { } break; } } else { if(infilename) { if(!dirname) { dirname = thisarg; } else { helponly = 1; } } else { infilename = thisarg; } } lfd++; argptr++; } if(!(infilename && dirname)) { if(!(helponly || versiononly)) { helponly = 1; } } if(!(helponly || versiononly || do_configure || do_showconf)) { if(dksf_must_expand_filename(infilename)) { a_filename_was_found = 0; fne = dkfne_open(infilename,1,0); if(fne) { while(dkfne_next(fne)) { a_filename_was_found = 1; run(dkfne_get_fullname(fne), dirname, force_plain); } dkfne_close(fne); if(!a_filename_was_found) { dkapp_err_matchfile(app, infilename); } } else { run(infilename, dirname, force_plain); } } else { run(infilename, dirname, force_plain); } } else { if(do_configure || do_showconf) { if(do_unconfigure) { if(app) dkapp_unconfigure(app); } else { if(do_configure) { char *k; k = str_pref_off; if(force_plain) k = str_pref_on; if(app) dkapp_set_pref(app, str_pref_plain, k); } show_configuration(app, force_plain); } } else { print_version(); if(helponly) { print_help(); } } } dkapp_close(app); } else { exval = 1; } #line 1957 "stc.ctr" exit(exval); return exval; }