67701bb9875ee155e9fc67bf3a1a95adb36f16c2
[goodguy/history.git] / cinelerra-5.1 / po / xlat.C
1 #include <stdio.h>
2 #include <stdint.h>
3 #include <stdlib.h>
4 #include <string.h>
5 #include <unistd.h>
6
7 #include <string>
8 #include <map>
9
10 #define MX_STR 8192
11
12 // test csv    ./a.out csv < data.csv
13 // test po     ./a.out  po < data.po
14 // get strings ./a.out key  < xgettext.po
15 // gen xlation ./a.out xlat < xgettext.po xlat.csv
16 // gen xlation ./a.out xlat < xgettext.po text,xlat ...
17 // gen xupdate ./a.out xlat < xgettext.po xlat.csv newer.csv ... newest.csv
18
19 unsigned int wnext(uint8_t *&bp)
20 {
21   unsigned int ch = *bp++;
22   if( ch >= 0x80 ) {
23     static const unsigned char byts[] = {
24       1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 4, 5,
25     };
26     int i = ch - 0xc0;
27     int n = i<0 ? 0 : byts[i/4];
28     for( i=n; --i>=0 && *bp>=0x80; ch+=*bp++ ) ch <<= 6;
29     static const unsigned int ofs[6] = {
30       0x00000000U, 0x00003080U, 0x000E2080U,
31       0x03C82080U, 0xFA082080U, 0x82082080U
32     };
33     ch = i<0 ?  ch-ofs[n] : '?';
34   }
35   return ch;
36 }
37
38 int wnext(uint8_t *&bp, unsigned int ch)
39 {
40   if( ch < 0x00000080 ) { *bp++ = ch;  return 1; }
41   int n = ch < 0x00000800 ? 2 : ch < 0x00010000 ? 3 :
42           ch < 0x00200000 ? 4 : ch < 0x04000000 ? 5 : 6;
43   int m = (0xff00 >> n), i = n-1;
44   *bp++ = (ch>>(6*i)) | m;
45   while( --i >= 0 ) *bp++ = ((ch>>(6*i)) & 0x3f) | 0x80;
46   return n;
47 }
48
49 using namespace std;
50
51 //csv = comma seperated value file
52 #define SEP ','
53 static bool is_sep(int ch) { return ch == SEP; }
54
55 static bool is_opnr(int ch)
56 {
57   if( ch ==  '\"'  ) return true;
58   if( ch == 0xab ) return true;
59   if( ch == 0xbb ) return true;
60   if( ch == 0x300c ) return true;
61   if( ch == 0x300d ) return true;
62   return 0;
63 }
64
65 // converts libreoffice csv to string (with quotes attached)
66 //  quote marks only
67 static void xlat1(uint8_t *&in, uint8_t *out)
68 {
69   uint8_t *ibp = in, *obp = out;
70   unsigned ch;
71   if( (ch=wnext(in)) == '\"' ) { 
72     bool is_nested = in[0] == '\"' && in[1] == '\"';
73     while( (ch=wnext(in)) != 0 ) {
74       if( ch == '\"' ) {
75         uint8_t *bp = in;
76         unsigned nch = wnext(in);
77         if( nch != '\"' ) { in = bp;  break; }
78       }
79       wnext(out, ch);
80     }
81     if( is_nested && ch == '"' ) {
82       while( out > obp && *(out-1) == ' ' ) --out;
83     }
84   }
85   if( ch != '"' ) {
86     in = ibp;  out = obp;
87     while( (ch=wnext(in)) && !is_sep(ch) ) wnext(out,ch);
88   }
89   *out = 0;
90 }
91
92 static inline unsigned gch(uint8_t *&in) {
93   unsigned ch = wnext(in);
94   if( ch == '\\' ) {
95     switch( (ch=*in++) ) {
96     case 'a':  ch = '\a';  break;
97     case 'b':  ch = '\b';  break;
98     case 'f':  ch = '\f';  break;
99     case 'n':  ch = '\n';  break;
100     case 'r':  ch = '\r';  break;
101     case 't':  ch = '\t';  break;
102     case 'v':  ch = '\v';  break;
103     }
104   }
105   return ch;
106 }
107
108 // converts string (with opn/cls attached) to c string
109 static void xlat2(uint8_t *in, uint8_t *out)
110 {
111   uint8_t *obp = out;
112   unsigned lch = 0, ch = gch(in);
113   if( ch ) {
114     if( !is_opnr(ch) ) wnext(out, ch);
115     while( (ch=gch(in)) != 0 ) {
116       lch = ch;  obp = out;
117       wnext(out, ch);
118     }
119     if( lch && is_opnr(lch) ) out = obp;
120   }
121   *out = 0;
122 }
123
124 // converts c++ string to c string text
125 static void xlat3(const char *cp, uint8_t *out)
126 {
127   wnext(out, '\"');
128   unsigned ch;
129   uint8_t *bp = (uint8_t*)cp;
130   while( (ch=wnext(bp)) != 0 ) {
131     switch( ch ) {
132     case '"':   ch = '\"'; break;
133     case '\a':  ch = 'a';  break;
134     case '\b':  ch = 'b';  break;
135     case '\f':  ch = 'f';  break;
136     case '\n':  ch = 'n';  break;
137     case '\r':  ch = 'r';  break;
138     case '\t':  ch = 't';  break;
139     case '\v':  ch = 'v';  break;
140     default: wnext(out,ch);  continue;
141     }
142     wnext(out,'\\');
143     wnext(out, ch);
144   }
145   wnext(out, '\"');
146   wnext(out, 0);
147 }
148
149 // converts c++ string to csv string text
150 static void xlat4(const char *cp, uint8_t *out)
151 {
152   wnext(out, '\"');
153   unsigned ch;
154   uint8_t *bp = (uint8_t*)cp;
155   while( (ch=wnext(bp)) != 0 ) {
156     switch( ch ) {
157     case '\a':  ch = 'a';  break;
158     case '\b':  ch = 'b';  break;
159     case '\f':  ch = 'f';  break;
160     case '\n':  ch = 'n';  break;
161     case '\r':  ch = 'r';  break;
162     case '\t':  ch = 't';  break;
163     case '\v':  ch = 'v';  break;
164     case '\"': wnext(out,ch); // fall thru
165     default: wnext(out,ch);  continue;
166     }
167     wnext(out,'\\');
168     wnext(out,ch);
169   }
170   wnext(out, '\"');
171   wnext(out, 0);
172 }
173
174 // parses input to c++ string
175 static string xlat(uint8_t *&in)
176 {
177   uint8_t bfr[MX_STR];  bfr[0] = 0;  xlat1(in, bfr);
178   uint8_t str[MX_STR];  str[0] = 0;  xlat2(bfr, str);
179   return string((const char*)str);
180 }
181
182 class tstring : public string {
183 public:
184   bool ok;
185   tstring(const char *sp, bool k) { string::assign(sp); ok = k; }
186 };
187
188 typedef map<string,tstring> Trans;
189 static Trans trans;
190
191 static inline bool prefix_is(uint8_t *bp, const char *cp)
192 {
193   return !strncmp((const char *)bp, cp, strlen(cp));
194 }
195 static inline uint8_t *bgets(uint8_t *bp, int len, FILE *fp)
196 {
197   uint8_t *ret = (uint8_t*)fgets((char*)bp, len, fp);
198   if( ret ) {
199     int len = strlen((char *)bp);
200     if( len > 0 && bp[len-1] == '\n' ) bp[len-1] = 0;
201   }
202   return ret;
203 }
204 static inline int bputs(uint8_t *bp, FILE *fp)
205 {
206   if( !fp ) return 0;
207   fputs((const char*)bp, fp);
208   fputc('\n',fp);
209   return 1;
210 }
211 static inline int bput(uint8_t *bp, FILE *fp)
212 {
213   if( !fp ) return 0;
214   fputs((const char*)bp, fp);
215   return 1;
216 }
217
218
219 static inline bool is_nlin(unsigned ch, uint8_t *bp)
220 {
221   return ch == ' ' && bp[0] == '\\' && bp[1] == ' ' && ( bp[2] == 'n' || bp[2] == 'N' );
222 }
223
224 static inline bool is_ccln(unsigned ch, uint8_t *bp)
225 {
226   return ch == ' ' && bp[0] == ':' && bp[1] == ':' && bp[2] == ' ';
227 }
228
229 static inline bool is_quot(unsigned ch, uint8_t *bp)
230 {
231   return ch == ' ' && bp[0] == '\\' && bp[1] == ' ' && bp[2] == '"';
232 }
233
234 static inline bool is_colon(unsigned ch)
235 {
236   return ch == 0xff1a;
237 }
238
239 static inline bool is_per(unsigned ch)
240 {
241   if( ch == '%' ) return true;
242   if( ch == 0xff05 ) return true;
243   return false;
244 }
245
246 static unsigned fmt_flds = 0;
247
248 enum { fmt_flg=1, fmt_wid=2, fmt_prc=4, fmt_len=8, fmt_cnv=16, };
249
250 static int is_flags(uint8_t *fp)
251 {
252   if( (fmt_flds & fmt_flg) != 0 ) return 0;
253   if( !strchr("#0-+ I", *fp) ) return 0;
254   fmt_flds |= fmt_flg;
255   return 1;
256 }
257
258 static int is_width(uint8_t *fp)
259 {
260   if( (fmt_flds & fmt_wid) != 0 ) return 0;
261   if( *fp != '*' && *fp < '0' && *fp > '9' ) return 0;
262   fmt_flds |= fmt_wid;
263   uint8_t *bp = fp;
264   bool argno = *fp++ == '*';
265   while( *fp >= '0' && *fp <= '9' ) ++fp;
266   if( argno && *fp++ != '$' ) return 1;
267   return fp - bp;
268 }
269
270 static int is_prec(uint8_t *fp)
271 {
272   if( (fmt_flds & fmt_prc) != 0 ) return 0;
273   if( *fp != '.' ) return 0;
274   fmt_flds |= fmt_prc;
275   if( *fp != '*' && *fp < '0' && *fp > '9' ) return 0;
276   uint8_t *bp = fp;
277   bool argno = *fp++ == '*';
278   while( *fp >= '0' && *fp <= '9' ) ++fp;
279   if( argno && *fp++ != '$' ) return 1;
280   return fp - bp;
281 }
282
283 static int is_len(uint8_t *fp)
284 {
285   if( (fmt_flds & fmt_len) != 0 ) return 0;
286   if( !strchr("hlLqjzt", *fp) ) return 0;
287   fmt_flds |= fmt_len;
288   if( fp[0] == 'h' && fp[1] == 'h' ) return 2;
289   if( fp[0] == 'l' && fp[1] == 'l' ) return 2;
290   return 1;
291 }
292
293 static int is_conv(uint8_t *fp)
294 {
295   if( !strchr("diouxXeEfFgGaAscCSpnm", *fp) ) return 0;
296   return 1;
297 }
298
299
300 static inline int fmt_spec(uint8_t *fp)
301 {
302   if( !*fp ) return 0;
303   if( is_per(*fp) ) return 1;
304   fmt_flds = 0;
305   uint8_t *bp = fp;
306   while( !is_conv(fp) ) {
307     int len;
308     if( !(len=is_flags(fp)) && !(len=is_width(fp)) &&
309         !(len=is_prec(fp))  && !(len=is_len(fp)) ) return 0;
310     fp += len;
311   }
312   return fp - bp + 1;
313 }
314
315 static bool chkfmt(int no, uint8_t *ap, uint8_t *bp, uint8_t *cp)
316 {
317   bool ret = true;
318   uint8_t *asp = ap, *bsp = bp;
319   int n = 0;
320   uint8_t *bep = bp;
321   unsigned bpr = 0, bch = wnext(bp);
322   for( ; bch!=0; bch=wnext(bp) ) {
323     if( is_opnr(bch) ) ++n;
324     bep = bp;  bpr = bch;
325   }
326   // trim solitary opnrs on ends b
327   if( n != 1 || !is_opnr(bpr) ) bep = bp;
328   bp = bsp;  bch = wnext(bp);
329   if( n == 1 && is_opnr(bch) ) bch = wnext(bp);
330
331   unsigned apr = 0, ach = wnext(ap);
332   apr = bpr = 0;
333   while( ach != 0 && bch != 0 ) {
334     // move to % on a
335     while( ach != 0 && !is_per(ach) ) {
336       apr = ach;  ach = wnext(ap);
337     }
338     // move to % on b
339     while( bch != 0 && !is_per(bch) ) {
340       if( is_nlin(bch, bp) ) {
341         bch = '\n';  bp += 3;
342       }
343       else if( is_ccln(bch, bp) ) {
344         wnext(cp, bch=':');  bp += 3;
345       }
346       else if( is_quot(bch, bp) ) {
347         bch = '\"';  bp += 3;
348       }
349       else if( is_colon(bch) ) {
350         bch = ':';
351       }
352       wnext(cp,bch);  bpr = bch;
353       bch = bp >= bep ? 0 : wnext(bp);
354     }
355     if( !ach || !bch ) break;
356     // if % on a and % on b and is fmt_spec
357     if( is_per(ach) && is_per(bch) && (n=fmt_spec(ap)) > 0 ) {
358       if( apr != bpr ) wnext(cp,apr);
359       wnext(cp,ach);  apr = ach;  ach = wnext(ap);
360       // copy format data from a
361       while( ach != 0 && --n >= 0 ) {
362         wnext(cp, ach);  apr = ach;  ach = wnext(ap);
363       }
364       bpr = bch;  bch = bp >= bep ? 0 : wnext(bp);
365       // skip format data from b (ignore case)
366       while( bch != 0 && ((bpr ^ apr) & ~('a'-'A')) ) {
367         bpr = bch;
368         bch = bp >= bep ? 0 : wnext(bp);
369       }
370       // hit eol and didn't find end of spec on b
371       if( !bch && !((bpr ^ apr) & ~('a'-'A')) ) {
372         fprintf(stderr, "line %d: missed spec: %s\n", no, (char*)asp);
373         ret = false;
374       }
375     }
376     else {
377       fprintf(stderr, "line %d: missed fmt: %s\n", no, (char*)asp);
378       wnext(cp, bch);
379       apr = ach;  ach = wnext(ap);
380       bpr = bch;  bch = bp >= bep ? 0 : wnext(bp);
381       ret = false;
382     }
383   }
384   while( bch != 0 ) {
385     wnext(cp, bch);  bpr = bch;
386     bch = bp >= bep ? 0 : wnext(bp);
387   }
388   if( apr == '\n' && bpr != '\n' ) wnext(cp,'\n');
389   wnext(cp, 0);
390   return ret;
391 }
392
393 void load(FILE *afp, FILE *bfp)
394 {
395   int no = 0;
396   int ins = 0, rep = 0;
397   uint8_t inp[MX_STR];
398
399   while( bgets(inp, sizeof(inp), afp) ) {
400     ++no;
401     uint8_t *bp = inp;
402     string key = xlat(bp);
403     if( bfp ) {
404       if( !bgets(inp, sizeof(inp), bfp) ) {
405         fprintf(stderr,"xlat file ended early\n");
406         exit(1);
407       }
408       bp = inp;
409     }
410     else if( !is_sep(*bp++) ) {
411       fprintf(stderr, "missing sep at line %d: %s", no, inp);
412       exit(1);
413     }
414     string txt = xlat(bp);
415     const char *val = (const char *)bp;
416     uint8_t str[MX_STR];
417     bool ok = chkfmt(no, (uint8_t*)key.c_str(), (uint8_t*)txt.c_str(), str);
418       val = (const char*)str;
419     Trans::iterator it = trans.lower_bound(key);
420     if( it == trans.end() || it->first.compare(key) ) {
421       trans.insert(it, Trans::value_type(key, tstring(val, ok)));
422       ++ins;
423     }
424     else {
425       it->second.assign(val);
426       it->second.ok = ok;
427       ++rep;
428     }
429   }
430   fprintf(stderr,"***     ins %d, rep %d\n", ins, rep);
431 }
432
433 void scan_po(FILE *ifp, FILE *ofp)
434 {
435   int no = 0;
436   uint8_t ibfr[MX_STR], tbfr[MX_STR];
437
438   while( bgets(ibfr, sizeof(ibfr), ifp) ) {
439     if( !prefix_is(ibfr, "msgid ") ) {
440       bputs(ibfr, ofp);  ++no;
441       continue;
442     }
443     uint8_t str[MX_STR]; xlat2(&ibfr[6], str);
444     string key((const char*)str);
445     if( !bgets(tbfr, sizeof(tbfr), ifp) ) {
446       fprintf(stderr, "file truncated line %d: %s", no, ibfr);
447       exit(1);
448     }
449     bputs(ibfr, ofp);  ++no;
450    
451     while( tbfr[0] == '"' ) {
452       bputs(tbfr, ofp);  ++no;
453       xlat2(&tbfr[0], str);  key.append((const char*)str);
454       if( !bgets(tbfr, sizeof(tbfr), ifp) ) {
455         fprintf(stderr, "file truncated line %d: %s", no, ibfr);
456         exit(1);
457       }
458     }
459     if( !prefix_is(tbfr, "msgstr ") ) {
460       fprintf(stderr, "file truncated line %d: %s", no, ibfr);
461       exit(1);
462     }
463
464     if( !ofp ) {
465       if( !key.size() ) continue;
466       xlat3(key.c_str(), str);
467       printf("%s\n", (char *)str);
468       continue;
469     }
470
471     Trans::iterator it = trans.lower_bound(key);
472     if( it == trans.end() || it->first.compare(key) ) {
473       fprintf(stderr, "no trans line %d: %s\n", no, ibfr);
474       xlat3(key.c_str(), &tbfr[7]);
475       bputs(tbfr, ofp);  ++no;
476       bputs((uint8_t*)"#msgstr \"\"", ofp); ++no;
477     }
478     else if( !it->second.ok ) {
479       fprintf(stderr, "bad fmt line %d: %s\n", no, ibfr);
480       xlat3(it->first.c_str(), &tbfr[7]);
481       bputs(tbfr, ofp);  ++no;
482       xlat3(it->second.c_str(), str);
483       bput((uint8_t*)"#msgstr ", ofp);
484       bputs(str, ofp);  ++no;
485     }
486     else {
487       xlat3(it->second.c_str(), &tbfr[7]);
488       bputs(tbfr, ofp);  ++no;
489     }
490   }
491   if( ifp != stdin ) fclose(ifp);
492 }
493
494 void list_po(FILE *ifp, FILE *ofp)
495 {
496   int no = 0;
497   int dup = 0, nul = 0;
498   uint8_t ibfr[MX_STR], tbfr[MX_STR];
499
500   while( bgets(ibfr, sizeof(ibfr), ifp) ) {
501     ++no;
502     if( !prefix_is(ibfr, "msgid ") ) continue;
503     uint8_t str[MX_STR]; xlat2(&ibfr[6], str);
504     string key((const char*)str);
505     if( !bgets(tbfr, sizeof(tbfr), ifp) ) {
506       fprintf(stderr, "file truncated line %d: %s", no, ibfr);
507       exit(1);
508     }
509     ++no;
510    
511     while( tbfr[0] == '"' ) {
512       xlat2(&tbfr[0], str);  key.append((const char*)str);
513       if( !bgets(tbfr, sizeof(tbfr), ifp) ) {
514         fprintf(stderr, "file truncated line %d: %s", no, ibfr);
515         exit(1);
516       }
517       ++no;
518     }
519     if( !prefix_is(tbfr, "msgstr ") ) {
520       fprintf(stderr, "file truncated line %d: %s", no, ibfr);
521       exit(1);
522     }
523
524     xlat2(&tbfr[7], str);
525     string txt((const char*)str);
526    
527     while( bgets(tbfr, sizeof(tbfr), ifp) && tbfr[0] == '"' ) {
528       xlat2(&tbfr[0], str);  txt.append((const char*)str);
529       ++no;
530     }
531     if( !txt.size() ) { ++nul; continue; }
532     if( !key.compare(txt) ) { ++dup; continue; }
533     xlat4(key.c_str(), str);
534     fprintf(ofp, "%s,", (char *)str);
535     xlat4(txt.c_str(), str);
536     fprintf(ofp, "%s\n", (char *)str);
537   }
538   fprintf(stderr, "*** dup %d, nul %d\n", dup, nul);
539 }
540
541 static void usage(const char *av0)
542 {
543   printf("test csv    %s  csv < data.csv\n",av0);
544   printf("test po     %s   po < data.po\n",av0);
545   printf("get strings %s  key < xgettext.po\n",av0);
546   printf("gen xlation %s xlat < xgettext.po xlat.csv\n",av0);
547   printf("gen xlation %s xlat < xgettext.po text,xlat ...\n",av0);
548   exit(1);
549 }
550
551 int main(int ac, char **av)
552 {
553   if( ac == 1 ) usage(av[0]);
554
555   if( !strcmp(av[1],"csv") ) {  // test csv
556     load(stdin, 0);
557     for( Trans::iterator it = trans.begin(); it!=trans.end(); ++it ) {
558       uint8_t str[MX_STR];  xlat3(it->second.c_str(), str);
559       printf("key = \"%s\", val = %s\n", it->first.c_str(), (char *)str);
560     }
561     return 0;
562   }
563
564   if( !strcmp(av[1],"po") ) {  // test po
565     list_po(stdin, stdout);
566     return 0;
567   }
568
569   if( !strcmp(av[1],"key") ) {
570     scan_po(stdin, 0);
571     return 0;
572   }
573
574   if( ac < 3 ) usage(av[0]);
575
576   FILE *ifp = !strcmp(av[2],"-") ? stdin : fopen(av[2], "r");
577   if( !ifp ) { perror(av[2]);  exit(1); }
578  
579 //  if( ac < 4 ) usage(av[0]);
580
581   if( strcmp(av[1],"xlat") ) {
582     fprintf(stderr,"unkn cmd: %s\n", av[1]);
583     return 1;
584   }
585
586   for( int i=3; i<ac; ++i ) {  // create trans mapping
587     fprintf(stderr,"*** load %s\n", av[i]);
588     char fn[MX_STR*2];
589     strncpy(fn, av[i], sizeof(fn));
590     int k = 0;
591     FILE *bfp = 0;
592     // look for <filename> or <filename>,<filename>
593     while( k<(int)sizeof(fn) && fn[k]!=0 && fn[k]!=',' ) ++k;
594     if( k<(int)sizeof(fn) && fn[k]==',' ) {
595       fn[k++] = 0;
596       bfp = fopen(&fn[k], "r");
597       if( !bfp ) { perror(&fn[k]);  exit(1); }
598     }
599     FILE *afp = fopen(&fn[0], "r");
600     if( !afp ) { perror(&fn[0]);  exit(1); }
601     load(afp, bfp);
602     fclose(afp);
603     if( bfp ) fclose(bfp);
604   }
605
606   scan_po(ifp, stdout);
607   return 0;
608 }
609