1aa1881fe250babb284bf83095f9bf544c97eaf9
[goodguy/cinelerra.git] / 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 stuttered quoted 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, lch = 0;
71   if( (ch=wnext(in)) == '\"' ) { 
72     bool is_nested = in[0] == '\"' && in[1] == '\"';
73     while( (ch=wnext(in)) != 0 ) {
74       if( ch == '\"' && lch != '\\' ) {
75         uint8_t *bp = in;
76         unsigned nch = wnext(in);
77         if( nch != '\"' ) { in = bp;  break; }
78       }
79       wnext(out, lch = 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   unsigned lch = gch(in), sep = 0, rch = 0, ch;
112   if( lch ) {
113     if( is_opnr(lch) ) {
114       for( uint8_t *ip=in; (ch=gch(ip))!=0; rch=ch );
115       if( lch == rch ) { sep = lch;  lch = gch(in); }
116     }
117     while( (ch=gch(in)) != 0 ) {
118       wnext(out, lch);  lch = ch;
119     }
120     if( !sep ) wnext(out, lch);
121   }
122   *out = 0;
123 }
124
125 int brkput = 0;
126
127 // converts c++ string to c string text
128 static void xlat3(const char *cp, uint8_t *out)
129 {
130   wnext(out, '\"');
131   unsigned ch;
132   uint8_t *bp = (uint8_t*)cp;
133   while( (ch=wnext(bp)) != 0 ) {
134     switch( ch ) {
135     case '"':   ch = '\"'; break;
136     case '\a':  ch = 'a';  break;
137     case '\b':  ch = 'b';  break;
138     case '\f':  ch = 'f';  break;
139     case '\n':  ch = 'n';  break;
140     case '\r':  ch = 'r';  break;
141     case '\t':  ch = 't';  break;
142     case '\v':  ch = 'v';  break;
143     default: wnext(out,ch);  continue;
144     }
145     wnext(out,'\\');
146     wnext(out, ch);
147     if( brkput && ch == 'n' && *bp ) {
148       wnext(out, '\"');
149       wnext(out, '\n');
150       wnext(out, '\"');
151     }
152   }
153   wnext(out, '\"');
154   wnext(out, 0);
155 }
156
157 // converts c++ string to csv string text
158 static void xlat4(const char *cp, uint8_t *out)
159 {
160   wnext(out, '\"');
161   unsigned ch;
162   uint8_t *bp = (uint8_t*)cp;
163   while( (ch=wnext(bp)) != 0 ) {
164     switch( ch ) {
165     case '\a':  ch = 'a';  break;
166     case '\b':  ch = 'b';  break;
167     case '\f':  ch = 'f';  break;
168     case '\n':  ch = 'n';  break;
169     case '\r':  ch = 'r';  break;
170     case '\t':  ch = 't';  break;
171     case '\v':  ch = 'v';  break;
172     case '\"': break;
173     default: wnext(out,ch);  continue;
174     }
175     wnext(out,'\\');
176     wnext(out,ch);
177   }
178   wnext(out, '\"');
179   wnext(out, 0);
180 }
181
182 // parses input to c++ string
183 static string xlat(uint8_t *&in)
184 {
185   uint8_t bfr[MX_STR];  bfr[0] = 0;  xlat1(in, bfr);
186   uint8_t str[MX_STR];  str[0] = 0;  xlat2(bfr, str);
187   return string((const char*)str);
188 }
189
190 class tstring : public string {
191 public:
192   bool ok;
193   tstring(const char *sp, bool k) { string::assign(sp); ok = k; }
194 };
195
196 typedef map<string,tstring> Trans;
197 static Trans trans;
198
199 static inline bool prefix_is(uint8_t *bp, const char *cp)
200 {
201   return !strncmp((const char *)bp, cp, strlen(cp));
202 }
203 static inline uint8_t *bgets(uint8_t *bp, int len, FILE *fp)
204 {
205   uint8_t *ret = (uint8_t*)fgets((char*)bp, len, fp);
206   if( ret ) {
207     int len = strlen((char *)bp);
208     if( len > 0 && bp[len-1] == '\n' ) bp[len-1] = 0;
209   }
210   return ret;
211 }
212 static inline int bputs(uint8_t *bp, FILE *fp)
213 {
214   if( !fp ) return 0;
215   fputs((const char*)bp, fp);
216   fputc('\n',fp);
217   int n = 1;
218   while( *bp ) if( *bp++ == '\n' ) ++n;
219   return n;
220 }
221 static inline int bput(uint8_t *bp, FILE *fp)
222 {
223   if( !fp ) return 0;
224   fputs((const char*)bp, fp);
225   return 1;
226 }
227
228 static bool goog = false;
229 static bool nocmts = false;
230
231 static inline bool is_nlin(unsigned ch, uint8_t *bp)
232 {
233   return ch == ' ' && bp[0] == '\\' && bp[1] == ' ' && ( bp[2] == 'n' || bp[2] == 'N' );
234 }
235
236 static inline bool is_ccln(unsigned ch, uint8_t *bp)
237 {
238   return ch == ' ' && bp[0] == ':' && bp[1] == ':' && bp[2] == ' ';
239 }
240
241 static inline bool is_quot(unsigned ch, uint8_t *bp)
242 {
243   return ch == ' ' && bp[0] == '\\' && bp[1] == ' ' && bp[2] == '"';
244 }
245
246 static inline bool is_colon(unsigned ch)
247 {
248   return ch == 0xff1a;
249 }
250
251 static inline bool is_per(unsigned ch)
252 {
253   if( ch == '%' ) return true;
254   if( ch == 0xff05 ) return true;
255   return false;
256 }
257
258 static unsigned fmt_flds = 0;
259
260 enum { fmt_flg=1, fmt_wid=2, fmt_prc=4, fmt_len=8, fmt_cnv=16, };
261
262 static int is_flags(uint8_t *fp)
263 {
264   if( (fmt_flds & fmt_flg) != 0 ) return 0;
265   if( !strchr("#0-+ I", *fp) ) return 0;
266   fmt_flds |= fmt_flg;
267   return 1;
268 }
269
270 static int is_width(uint8_t *fp)
271 {
272   if( (fmt_flds & fmt_wid) != 0 ) return 0;
273   if( *fp != '*' && *fp < '0' && *fp > '9' ) return 0;
274   fmt_flds |= fmt_wid;
275   uint8_t *bp = fp;
276   bool argno = *fp++ == '*';
277   while( *fp >= '0' && *fp <= '9' ) ++fp;
278   if( argno && *fp++ != '$' ) return 1;
279   return fp - bp;
280 }
281
282 static int is_prec(uint8_t *fp)
283 {
284   if( (fmt_flds & fmt_prc) != 0 ) return 0;
285   if( *fp != '.' ) return 0;
286   fmt_flds |= fmt_prc;
287   if( *fp != '*' && *fp < '0' && *fp > '9' ) return 0;
288   uint8_t *bp = fp;
289   bool argno = *fp++ == '*';
290   while( *fp >= '0' && *fp <= '9' ) ++fp;
291   if( argno && *fp++ != '$' ) return 1;
292   return fp - bp;
293 }
294
295 static int is_len(uint8_t *fp)
296 {
297   if( (fmt_flds & fmt_len) != 0 ) return 0;
298   if( !strchr("hlLqjzt", *fp) ) return 0;
299   fmt_flds |= fmt_len;
300   if( fp[0] == 'h' && fp[1] == 'h' ) return 2;
301   if( fp[0] == 'l' && fp[1] == 'l' ) return 2;
302   return 1;
303 }
304
305 static int is_conv(uint8_t *fp)
306 {
307   if( !strchr("diouxXeEfFgGaAscCSpnm", *fp) ) return 0;
308   return 1;
309 }
310
311
312 static inline int fmt_spec(uint8_t *fp)
313 {
314   if( !*fp ) return 0;
315   if( is_per(*fp) ) return 1;
316   fmt_flds = 0;
317   uint8_t *bp = fp;
318   while( !is_conv(fp) ) {
319     int len;
320     if( !(len=is_flags(fp)) && !(len=is_width(fp)) &&
321         !(len=is_prec(fp))  && !(len=is_len(fp)) ) return 0;
322     fp += len;
323   }
324   return fp - bp + 1;
325 }
326
327 static bool chkfmt(int no, uint8_t *ap, uint8_t *bp, uint8_t *cp)
328 {
329   bool ret = true;
330   uint8_t *asp = ap, *bsp = bp;
331   int n = 0;
332   uint8_t *bep = bp;
333   unsigned bpr = 0, bch = wnext(bp);
334   for( ; bch!=0; bch=wnext(bp) ) {
335     if( goog && is_opnr(bch) ) ++n;
336     bep = bp;  bpr = bch;
337   }
338
339   // trim solitary opnrs on ends b
340   if( goog && ( n != 1 || !is_opnr(bpr) ) ) bep = bp;
341   bp = bsp;  bch = wnext(bp);
342   if( goog && ( n == 1 && is_opnr(bch) ) ) bch = wnext(bp);
343
344   unsigned apr = 0, ach = wnext(ap);
345   apr = bpr = 0;
346   while( ach != 0 && bch != 0 ) {
347     // move to % on a
348     while( ach != 0 && !is_per(ach) ) {
349       apr = ach;  ach = wnext(ap);
350     }
351     // move to % on b
352     while( bch != 0 && !is_per(bch) ) {
353       if( goog ) { // google xlat recoginizers
354         if( is_nlin(bch, bp) ) {
355           bch = '\n';  bp += 3;
356         }
357         else if( is_ccln(bch, bp) ) {
358           wnext(cp, bch=':');  bp += 3;
359         }
360         else if( is_quot(bch, bp) ) {
361           bch = '\"';  bp += 3;
362         }
363         else if( is_colon(bch) ) {
364           bch = ':';
365         }
366       }
367       wnext(cp,bch);  bpr = bch;
368       bch = bp >= bep ? 0 : wnext(bp);
369     }
370     if( !ach || !bch ) break;
371     if( !*ap && !*bp ) break;
372     // if % on a and % on b and is fmt_spec
373     if( is_per(ach) && is_per(bch) && (n=fmt_spec(ap)) > 0 ) {
374       if( apr && apr != bpr ) wnext(cp,apr);
375       wnext(cp,ach);  apr = ach;  ach = wnext(ap);
376       // copy format data from a
377       while( ach != 0 && --n >= 0 ) {
378         wnext(cp, ach);  apr = ach;  ach = wnext(ap);
379       }
380       bpr = bch;  bch = bp >= bep ? 0 : wnext(bp);
381       if( apr == '%' && bch == '%' ) {
382         // copy %% format data from b
383         bpr = bch;
384         bch = bp >= bep ? 0 : wnext(bp);
385       }
386       else {
387         // skip format data from b (ignore case)
388         while( bch != 0 && ((bpr ^ apr) & ~('a'-'A')) ) {
389           bpr = bch;
390           bch = bp >= bep ? 0 : wnext(bp);
391         }
392         // hit eol and didn't find end of spec on b
393         if( !bch && ((bpr ^ apr) & ~('a'-'A')) != 0 ) {
394           fprintf(stderr, "line %d: missed spec: %s\n", no, (char*)asp);
395           ret = false;
396         }
397       }
398     }
399     else {
400       fprintf(stderr, "line %d: missed fmt: %s\n", no, (char*)asp);
401       wnext(cp, bch);
402       apr = ach;  ach = wnext(ap);
403       bpr = bch;  bch = bp >= bep ? 0 : wnext(bp);
404       ret = false;
405     }
406   }
407   while( bch != 0 ) {
408     wnext(cp, bch);  bpr = bch;
409     bch = bp >= bep ? 0 : wnext(bp);
410   }
411   if( apr == '\n' && bpr != '\n' ) wnext(cp,'\n');
412   wnext(cp, 0);
413   return ret;
414 }
415
416 void load(FILE *afp, FILE *bfp)
417 {
418   int no = 0;
419   int ins = 0, rep = 0;
420   uint8_t inp[MX_STR];
421
422   while( bgets(inp, sizeof(inp), afp) ) {
423     ++no;
424     uint8_t *bp = inp;
425     string key = xlat(bp);
426     if( bfp ) {
427       if( !bgets(inp, sizeof(inp), bfp) ) {
428         fprintf(stderr,"xlat file ended early\n");
429         exit(1);
430       }
431       bp = inp;
432     }
433     else if( !is_sep(*bp++) ) {
434       fprintf(stderr, "missing sep at line %d: %s", no, inp);
435       exit(1);
436     }
437     string txt = xlat(bp);
438     const char *val = (const char *)bp;
439     uint8_t str[MX_STR];
440     bool ok = chkfmt(no, (uint8_t*)key.c_str(), (uint8_t*)txt.c_str(), str);
441       val = (const char*)str;
442     Trans::iterator it = trans.lower_bound(key);
443     if( it == trans.end() || it->first.compare(key) ) {
444       trans.insert(it, Trans::value_type(key, tstring(val, ok)));
445       ++ins;
446     }
447     else {
448       it->second.assign(val);
449       it->second.ok = ok;
450       ++rep;
451     }
452   }
453   fprintf(stderr,"***     ins %d, rep %d\n", ins, rep);
454 }
455
456 void scan_po(FILE *ifp, FILE *ofp)
457 {
458   int no = 0;
459   uint8_t ibfr[MX_STR], tbfr[MX_STR];
460
461   while( bgets(ibfr, sizeof(ibfr), ifp) ) {
462     if( !prefix_is(ibfr, "msgid ") ) {
463       if( nocmts && ibfr[0] == '#' ) continue;
464       no += bputs(ibfr, ofp);
465       continue;
466     }
467     uint8_t str[MX_STR]; xlat2(&ibfr[6], str);
468     string key((const char*)str);
469     if( !bgets(tbfr, sizeof(tbfr), ifp) ) {
470       fprintf(stderr, "file truncated line %d: %s", no, ibfr);
471       exit(1);
472     }
473     no += bputs(ibfr, ofp);
474    
475     while( tbfr[0] == '"' ) {
476       no += bputs(tbfr, ofp);
477       xlat2(&tbfr[0], str);  key.append((const char*)str);
478       if( !bgets(tbfr, sizeof(tbfr), ifp) ) {
479         fprintf(stderr, "file truncated line %d: %s", no, ibfr);
480         exit(1);
481       }
482     }
483     if( !prefix_is(tbfr, "msgstr ") ) {
484       fprintf(stderr, "file truncated line %d: %s", no, ibfr);
485       exit(1);
486     }
487
488     if( !ofp ) {
489       if( !key.size() ) continue;
490       xlat3(key.c_str(), str);
491       printf("%s\n", (char *)str);
492       continue;
493     }
494
495     Trans::iterator it = trans.lower_bound(key);
496     if( it == trans.end() || it->first.compare(key) ) {
497       fprintf(stderr, "no trans line %d: %s\n", no, ibfr);
498       xlat3(key.c_str(), &tbfr[7]);
499       //no += bputs(tbfr, ofp);
500       no += bputs((uint8_t*)"msgstr \"\"", ofp);
501     }
502     else if( 0 && !it->second.ok ) {
503       fprintf(stderr, "bad fmt line %d: %s\n", no, ibfr);
504       xlat3(it->first.c_str(), &tbfr[7]);
505       no += bputs(tbfr, ofp);
506       xlat3(it->second.c_str(), str);
507       bput((uint8_t*)"#msgstr ", ofp);
508       no += bputs(str, ofp);
509     }
510     else {
511       xlat3(it->second.c_str(), &tbfr[7]);
512       no += bputs(tbfr, ofp);
513     }
514   }
515   if( ifp != stdin ) fclose(ifp);
516 }
517
518 void list_po(FILE *ifp, FILE *ofp, int xeqx = 0, int nnul = 0)
519 {
520   int no = 0;
521   int dup = 0, nul = 0;
522   uint8_t ibfr[MX_STR], tbfr[MX_STR];
523
524   while( bgets(ibfr, sizeof(ibfr), ifp) ) {
525     ++no;
526     if( !prefix_is(ibfr, "msgid ") ) continue;
527     uint8_t str[MX_STR]; xlat2(&ibfr[6], str);
528     string key((const char*)str);
529     if( !bgets(tbfr, sizeof(tbfr), ifp) ) {
530       fprintf(stderr, "file truncated line %d: %s", no, ibfr);
531       exit(1);
532     }
533     ++no;
534    
535     while( tbfr[0] == '"' ) {
536       xlat2(&tbfr[0], str);  key.append((const char*)str);
537       if( !bgets(tbfr, sizeof(tbfr), ifp) ) {
538         fprintf(stderr, "file truncated line %d: %s", no, ibfr);
539         exit(1);
540       }
541       ++no;
542     }
543     if( !prefix_is(tbfr, "msgstr ") ) {
544       fprintf(stderr, "file truncated line %d: %s", no, ibfr);
545       exit(1);
546     }
547
548     xlat2(&tbfr[7], str);
549     string txt((const char*)str);
550    
551     while( bgets(tbfr, sizeof(tbfr), ifp) && tbfr[0] == '"' ) {
552       xlat2(&tbfr[0], str);  txt.append((const char*)str);
553       ++no;
554     }
555     if( nnul && !txt.size() ) {
556       ++nul;
557       if( nnul > 0 ) continue;
558     }
559     else if( xeqx && !key.compare(txt) ) {
560        ++dup;
561        if( xeqx > 0 ) continue;
562     }
563     else if( nnul < 0 || xeqx < 0 ) continue;
564     xlat4(key.c_str(), str);
565     fprintf(ofp, "%s,", (char *)str);
566     xlat4(txt.c_str(), str);
567     fprintf(ofp, "%s\n", (char *)str);
568   }
569   fprintf(stderr, "*** dup %d, nul %d\n", dup, nul);
570 }
571
572 static void usage(const char *av0)
573 {
574   printf("list csv    %s csv < data.csv > data.po\n",av0);
575   printf("list po     %s po < data.po > data.csv\n",av0);
576   printf("list po     %s dups < data.po\n",av0);
577   printf("list po     %s nodups < data.po\n",av0);
578   printf("get strings %s key  < xgettext.po\n",av0);
579   printf("gen xlation %s xlat   xgettext.po xlat.csv\n",av0);
580   printf("gen xlation %s xlat - text,xlat ... < xgettext.po\n",av0);
581   exit(1);
582 }
583
584 int main(int ac, char **av)
585 {
586   if( ac == 1 ) usage(av[0]);
587
588   // if to rework google xlat output
589   if( getenv("GOOG") ) goog = true;
590   if( getenv("NOCMTS") ) nocmts = true;
591
592   if( !strcmp(av[1],"csv") ) {  // test csv
593     load(stdin, 0);
594     for( Trans::iterator it = trans.begin(); it!=trans.end(); ++it ) {
595       uint8_t str1[MX_STR];  xlat3(it->first.c_str(), str1);
596       printf("msgid %s\n", (char *)str1);
597       uint8_t str2[MX_STR];  xlat3(it->second.c_str(), str2);
598       printf("msgstr %s\n\n", (char *)str2);
599     }
600     return 0;
601   }
602
603   if( !strcmp(av[1],"dups") ) {  // test po
604     list_po(stdin, stdout, -1, -1);
605     return 0;
606   }
607
608   if( !strcmp(av[1],"nodups") ) {  // test po
609     list_po(stdin, stdout, 1, 1);
610     return 0;
611   }
612
613   if( !strcmp(av[1],"po") ) {  // test po
614     list_po(stdin, stdout);
615     return 0;
616   }
617
618   if( !strcmp(av[1],"key") ) {
619     scan_po(stdin, 0);
620     return 0;
621   }
622
623   if( ac < 3 ) usage(av[0]);
624
625   FILE *ifp = !strcmp(av[2],"-") ? stdin : fopen(av[2], "r");
626   if( !ifp ) { perror(av[2]);  exit(1); }
627  
628 //  if( ac < 4 ) usage(av[0]);
629
630   if( strcmp(av[1],"xlat") ) {
631     fprintf(stderr,"unkn cmd: %s\n", av[1]);
632     return 1;
633   }
634
635   brkput = 1;
636   for( int i=3; i<ac; ++i ) {  // create trans mapping
637     fprintf(stderr,"*** load %s\n", av[i]);
638     char fn[MX_STR*2];
639     strncpy(fn, av[i], sizeof(fn));
640     int k = 0;
641     FILE *bfp = 0;
642     // look for <filename> or <filename>,<filename>
643     while( k<(int)sizeof(fn) && fn[k]!=0 && fn[k]!=',' ) ++k;
644     if( k<(int)sizeof(fn) && fn[k]==',' ) {
645       fn[k++] = 0;
646       bfp = fopen(&fn[k], "r");
647       if( !bfp ) { perror(&fn[k]);  exit(1); }
648     }
649     FILE *afp = fopen(&fn[0], "r");
650     if( !afp ) { perror(&fn[0]);  exit(1); }
651     load(afp, bfp);
652     fclose(afp);
653     if( bfp ) fclose(bfp);
654   }
655
656   scan_po(ifp, stdout);
657   return 0;
658 }
659