Imported Upstream version 0.19.7
[platform/upstream/gettext.git] / gettext-tools / src / write-qt.c
1 /* Writing Qt .qm files.
2    Copyright (C) 2003, 2005-2007, 2009, 2015 Free Software Foundation,
3    Inc.
4    Written by Bruno Haible <bruno@clisp.org>, 2003.
5
6    This program is free software: you can redistribute it and/or modify
7    it under the terms of the GNU General Public License as published by
8    the Free Software Foundation; either version 3 of the License, or
9    (at your option) any later version.
10
11    This program is distributed in the hope that it will be useful,
12    but WITHOUT ANY WARRANTY; without even the implied warranty of
13    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14    GNU General Public License for more details.
15
16    You should have received a copy of the GNU General Public License
17    along with this program.  If not, see <http://www.gnu.org/licenses/>.  */
18
19 #ifdef HAVE_CONFIG_H
20 # include <config.h>
21 #endif
22
23 /* Specification.  */
24 #include "write-qt.h"
25
26 #include <assert.h>
27 #include <errno.h>
28 #include <stdbool.h>
29 #include <stdio.h>
30 #include <stdlib.h>
31 #include <string.h>
32
33 #include "error.h"
34 #include "xerror.h"
35 #include "message.h"
36 #include "po-charset.h"
37 #include "msgl-iconv.h"
38 #include "hash-string.h"
39 #include "unistr.h"
40 #include "xalloc.h"
41 #include "obstack.h"
42 #include "hash.h"
43 #include "binary-io.h"
44 #include "fwriteerror.h"
45 #include "gettext.h"
46
47 #define _(str) gettext (str)
48
49 /* Qt .qm files are read by the QTranslator::load() function and written
50    by the Qt QTranslator::save() function.
51
52    The Qt tool 'msg2qm' uses the latter function and can convert PO files
53    to .qm files. But since 'msg2qm' is marked as an "old" tool in Qt 3.0.5's
54    i18n.html documentation and therefore likely to disappear, we provide the
55    same functionality here.
56
57    The format of .qm files, as reverse engineered from the functions
58      QTranslator::save(const QString& filename, SaveMode mode)
59      QTranslator::squeeze(SaveMode mode)
60      QTranslatorMessage::write(QDataStream& stream, bool strip, Prefix prefix)
61      elfHash(const char* name)
62    in qt-3.0.5, is as follows:
63
64      It's a binary data format. Elements are u8 (byte), u16, u32. They are
65      written in big-endian order.
66
67      The file starts with a magic string of 16 bytes:
68        3C B8 64 18 CA EF 9C 95 CD 21 1C BF 60 A1 BD DD
69
70      Then come three sections. Each of the three sections is optional. Each
71      has this structure:
72        struct {
73          u8 section_type; // 0x42 = hashes, 0x69 = messages, 0x2f = contexts
74          u32 length; // number of bytes of the data
75          u8 data[length];
76        };
77
78      In the first section, the hashes section, the data has the following
79      structure:
80        It's a sorted array of
81          struct {
82            u32 hashcode; // elfHash of the concatenation of msgid and
83                          // disambiguating-comment
84            u32 offset; // offset within the data[] of the messages section
85          };
86        It's sorted in ascending order by hashcode as primary sorting criteria
87        and - when the hashcodes are the same - by offset as secondary criteria.
88
89      In the second section, the messages section, the data has the following
90      structure:
91        It's a sequence of records, each representing a message, in no
92        particular order. Each record is a sequence of subsections, each
93        introduced by a particular subsection tag. The possible subsection tags
94        are (and they usually occur in this order):
95          - 03: Translation. Followed by the msgstr in UCS-2 or UTF-16 format:
96                struct {
97                  u32 length;
98                  u16 chars[length/2];
99                };
100          - 08: Disambiguating-comment. Followed by the NUL-terminated,
101                ISO-8859-1 encoded, disambiguating-comment string:
102                struct {
103                  u32 length;    // number of bytes including the NUL at the end
104                  u8 chars[length];
105                };
106          - 06: SourceText, i.e. msgid. Followed by the NUL-terminated,
107                ISO-8859-1 encoded, msgid:
108                struct {
109                  u32 length;    // number of bytes including the NUL at the end
110                  u8 chars[length];
111                };
112          - 02: SourceText16, i.e. msgid. Encoded as UCS-2, but must actually
113                be ISO-8859-1.
114                struct {
115                  u32 length;
116                  u16 chars[length/2];
117                };
118                This subsection tag is obsoleted by SourceText.
119          - 07: Context. Followed by the NUL-terminated, ISO-8859-1 encoded,
120                context string (usually a C++ class name or empty):
121                struct {
122                  u32 length;    // number of bytes including the NUL at the end
123                  u8 chars[length];
124                };
125          - 04: Context16. Encoded as UCS-2, but must actually be ISO-8859-1.
126                struct {
127                  u32 length;
128                  u16 chars[length/2];
129                };
130                This subsection tag is obsoleted by Context.
131          - 05: Hash. Followed by
132                struct {
133                  u32 hashcode; // elfHash of the concatenation of msgid and
134                                // disambiguating-comment
135                };
136          - 01: End. Designates the end of the record. No further data.
137        Usually the following subsections are written, but some of them are
138        optional:
139          - 03: Translation.
140          - 08: Disambiguating-comment (optional).
141          - 06: SourceText (optional).
142          - 07: Context (optional).
143          - 05: Hash.
144          - 01: End.
145        A subsection can be omitted if the value to be output is the same as
146        for the previous record.
147
148      The third section, the contexts section, contains the set of all occurring
149      context strings. This section is optional; it is used to speed up the
150      search. The data is a hash table with the following structure:
151        struct {
152          u16 table_size;
153          u16 buckets[table_size];
154          u8 pool[...];
155        };
156      pool[...] contains:
157        u16 zero;
158        for i = 0, ..., table_size:
159          if there are context strings with elfHash(context)%table_size == i:
160            for all context strings with elfHash(context)%table_size == i:
161              len := min(length(context),255); // truncated to length 255
162              struct {
163                u8 len;
164                u8 chars[len];
165              };
166            struct {
167              u8 zero[1]; // signals the end of this bucket
168              u8 padding[0 or 1]; // padding for even number of bytes
169            };
170      buckets[i] is 0 for an empty bucket, or the offset in pool[] where
171      the context strings for this bucket start, divided by 2.
172      This context section must not be used
173        - if the empty context is used, or
174        - if a context of length > 255 is used, or
175        - if the context pool's size would be > 2^17.
176
177      The elfHash function is the same as our hash_string function, except that
178      at the end it maps a hash code of 0x00000000 to 0x00000001.
179
180    When we convert from PO file format, all disambiguating-comments and
181    contexts are empty, and therefore the contexts section can be omitted.  */
182
183
184 /* Write a u8 (a single byte) to the output stream.  */
185 static inline void
186 write_u8 (FILE *output_file, unsigned char value)
187 {
188   putc (value, output_file);
189 }
190
191 /* Write a u16 (two bytes) to the output stream.  */
192 static inline void
193 write_u16 (FILE *output_file, unsigned short value)
194 {
195   unsigned char data[2];
196
197   data[0] = (value >> 8) & 0xff;
198   data[1] = value & 0xff;
199
200   fwrite (data, 2, 1, output_file);
201 }
202
203 /* Write a u32 (four bytes) to the output stream.  */
204 static inline void
205 write_u32 (FILE *output_file, unsigned int value)
206 {
207   unsigned char data[4];
208
209   data[0] = (value >> 24) & 0xff;
210   data[1] = (value >> 16) & 0xff;
211   data[2] = (value >> 8) & 0xff;
212   data[3] = value & 0xff;
213
214   fwrite (data, 4, 1, output_file);
215 }
216
217
218 #define obstack_chunk_alloc xmalloc
219 #define obstack_chunk_free free
220
221 /* Add a u8 (a single byte) to an obstack.  */
222 static void
223 append_u8 (struct obstack *mempool, unsigned char value)
224 {
225   unsigned char data[1];
226
227   data[0] = value;
228
229   obstack_grow (mempool, data, 1);
230 }
231
232 /* Add a u16 (two bytes) to an obstack.  */
233 static void
234 append_u16 (struct obstack *mempool, unsigned short value)
235 {
236   unsigned char data[2];
237
238   data[0] = (value >> 8) & 0xff;
239   data[1] = value & 0xff;
240
241   obstack_grow (mempool, data, 2);
242 }
243
244 /* Add a u32 (four bytes) to an obstack.  */
245 static void
246 append_u32 (struct obstack *mempool, unsigned int value)
247 {
248   unsigned char data[4];
249
250   data[0] = (value >> 24) & 0xff;
251   data[1] = (value >> 16) & 0xff;
252   data[2] = (value >> 8) & 0xff;
253   data[3] = value & 0xff;
254
255   obstack_grow (mempool, data, 4);
256 }
257
258 /* Add an ISO-8859-1 encoded string to an obstack.  */
259 static void
260 append_base_string (struct obstack *mempool, const char *string)
261 {
262   size_t length = strlen (string) + 1;
263   append_u32 (mempool, length);
264   obstack_grow (mempool, string, length);
265 }
266
267 /* Add an UTF-16 encoded string to an obstack.  */
268 static void
269 append_unicode_string (struct obstack *mempool, const unsigned short *string,
270                        size_t length)
271 {
272   append_u32 (mempool, length * 2);
273   for (; length > 0; string++, length--)
274     append_u16 (mempool, *string);
275 }
276
277 /* Retrieve a 4-byte integer from memory.  */
278 static inline unsigned int
279 peek_u32 (const unsigned char *p)
280 {
281   return (p[0] << 24) | (p[1] << 16) | (p[2] << 8) | p[3];
282 }
283
284 /* Convert an UTF-8 string to ISO-8859-1, without error checking.  */
285 static char *
286 conv_to_iso_8859_1 (const char *string)
287 {
288   size_t length = strlen (string);
289   const char *str = string;
290   const char *str_limit = string + length;
291   /* Conversion to ISO-8859-1 can only reduce the number of bytes.  */
292   char *result = XNMALLOC (length + 1, char);
293   char *q = result;
294
295   while (str < str_limit)
296     {
297       ucs4_t uc;
298       str += u8_mbtouc (&uc, (const unsigned char *) str, str_limit - str);
299       /* It has already been verified that the string fits in ISO-8859-1.  */
300       if (!(uc < 0x100))
301         abort ();
302       /* Store as ISO-8859-1.  */
303       *q++ = (unsigned char) uc;
304     }
305   *q = '\0';
306   assert (q - result <= length);
307
308   return result;
309 }
310
311 /* Convert an UTF-8 string to UTF-16, returning its size (number of UTF-16
312    codepoints) in *SIZEP.  */
313 static unsigned short *
314 conv_to_utf16 (const char *string, size_t *sizep)
315 {
316   size_t length = strlen (string);
317   const char *str = string;
318   const char *str_limit = string + length;
319   /* Conversion to UTF-16 can at most double the number of bytes.  */
320   unsigned short *result = XNMALLOC (length, unsigned short);
321   unsigned short *q = result;
322
323   while (str < str_limit)
324     {
325       ucs4_t uc;
326       str += u8_mbtouc (&uc, (const unsigned char *) str, str_limit - str);
327       if (uc < 0x10000)
328         /* UCS-2 character.  */
329         *q++ = (unsigned short) uc;
330       else
331         {
332           /* UTF-16 surrogate.  */
333           *q++ = 0xd800 + ((uc - 0x10000) >> 10);
334           *q++ = 0xdc00 + ((uc - 0x10000) & 0x3ff);
335         }
336     }
337   assert (q - result <= 2 * length);
338
339   *sizep = q - result;
340   return result;
341 }
342
343 /* Return the Qt hash code of a string.  */
344 static unsigned int
345 string_hashcode (const char *str)
346 {
347   unsigned int h;
348
349   h = hash_string (str);
350   if (h == 0)
351     h = 1;
352   return h;
353 }
354
355 /* Compare two entries of the hashes section.  */
356 static int
357 cmp_hashes (const void *va, const void *vb)
358 {
359   const unsigned char *a = (const unsigned char *) va;
360   const unsigned char *b = (const unsigned char *) vb;
361   unsigned int a_hashcode = peek_u32 (a);
362   unsigned int b_hashcode = peek_u32 (b);
363
364   if (a_hashcode != b_hashcode)
365     return (a_hashcode >= b_hashcode ? 1 : -1);
366   else
367     {
368       unsigned int a_offset = peek_u32 (a + 4);
369       unsigned int b_offset = peek_u32 (b + 4);
370
371       if (a_offset != b_offset)
372         return (a_offset >= b_offset ? 1 : -1);
373       else
374         return 0;
375     }
376 }
377
378
379 /* Write a section to the output stream.  */
380 static void
381 write_section (FILE *output_file, unsigned char tag, void *data, size_t size)
382 {
383   /* A section can be omitted if it is empty.  */
384   if (size > 0)
385     {
386       write_u8 (output_file, tag);
387       write_u32 (output_file, size);
388       fwrite (data, size, 1, output_file);
389     }
390 }
391
392
393 /* Write an entire .qm file.  */
394 static void
395 write_qm (FILE *output_file, message_list_ty *mlp)
396 {
397   static unsigned char magic[16] =
398     {
399       0x3C, 0xB8, 0x64, 0x18, 0xCA, 0xEF, 0x9C, 0x95,
400       0xCD, 0x21, 0x1C, 0xBF, 0x60, 0xA1, 0xBD, 0xDD
401     };
402   struct obstack hashes_pool;
403   struct obstack messages_pool;
404   size_t j;
405
406   obstack_init (&hashes_pool);
407   obstack_init (&messages_pool);
408
409   /* Prepare the hashes section and the messages section.  */
410   for (j = 0; j < mlp->nitems; j++)
411     {
412       message_ty *mp = mlp->item[j];
413
414       /* No need to emit the header entry, it's not needed at runtime.  */
415       if (!is_header (mp))
416         {
417           char *msgctxt_as_iso_8859_1 =
418             conv_to_iso_8859_1 (mp->msgctxt != NULL ? mp->msgctxt : "");
419           char *msgid_as_iso_8859_1 = conv_to_iso_8859_1 (mp->msgid);
420           size_t msgstr_len;
421           unsigned short *msgstr_as_utf16 =
422             conv_to_utf16 (mp->msgstr, &msgstr_len);
423           unsigned int hashcode = string_hashcode (msgid_as_iso_8859_1);
424           unsigned int offset = obstack_object_size (&messages_pool);
425
426           /* Add a record to the hashes section.  */
427           append_u32 (&hashes_pool, hashcode);
428           append_u32 (&hashes_pool, offset);
429
430           /* Add a record to the messages section.  */
431
432           append_u8 (&messages_pool, 0x03);
433           append_unicode_string (&messages_pool, msgstr_as_utf16, msgstr_len);
434
435           append_u8 (&messages_pool, 0x08);
436           append_base_string (&messages_pool, "");
437
438           append_u8 (&messages_pool, 0x06);
439           append_base_string (&messages_pool, msgid_as_iso_8859_1);
440
441           append_u8 (&messages_pool, 0x07);
442           append_base_string (&messages_pool, msgctxt_as_iso_8859_1);
443
444           append_u8 (&messages_pool, 0x05);
445           append_u32 (&messages_pool, hashcode);
446
447           append_u8 (&messages_pool, 0x01);
448
449           free (msgstr_as_utf16);
450           free (msgid_as_iso_8859_1);
451           free (msgctxt_as_iso_8859_1);
452         }
453     }
454
455   /* Sort the hashes section.  */
456   {
457     size_t nstrings = obstack_object_size (&hashes_pool) / 8;
458     if (nstrings > 0)
459       qsort (obstack_base (&hashes_pool), nstrings, 8, cmp_hashes);
460   }
461
462   /* Write the magic number.  */
463   fwrite (magic, sizeof (magic), 1, output_file);
464
465   /* Write the hashes section.  */
466   write_section (output_file, 0x42, obstack_base (&hashes_pool),
467                  obstack_object_size (&hashes_pool));
468
469   /* Write the messages section.  */
470   write_section (output_file, 0x69, obstack_base (&messages_pool),
471                  obstack_object_size (&messages_pool));
472
473   /* Decide whether to write a contexts section.  */
474   {
475     bool can_write_contexts = true;
476
477     for (j = 0; j < mlp->nitems; j++)
478       {
479         message_ty *mp = mlp->item[j];
480
481         if (!is_header (mp))
482           if (mp->msgctxt == NULL || mp->msgctxt[0] == '\0'
483               || strlen (mp->msgctxt) > 255)
484             {
485               can_write_contexts = false;
486               break;
487             }
488       }
489
490     if (can_write_contexts)
491       {
492         hash_table all_contexts;
493         size_t num_contexts;
494         unsigned long table_size;
495
496         /* Collect the contexts, removing duplicates.  */
497         hash_init (&all_contexts, 10);
498         for (j = 0; j < mlp->nitems; j++)
499           {
500             message_ty *mp = mlp->item[j];
501
502             if (!is_header (mp))
503               hash_insert_entry (&all_contexts,
504                                  mp->msgctxt, strlen (mp->msgctxt) + 1,
505                                  NULL);
506           }
507
508         /* Compute the number of different contexts.  */
509         num_contexts = all_contexts.size;
510
511         /* Compute a suitable hash table size.  */
512         table_size = next_prime (num_contexts * 1.7);
513         if (table_size >= 0x10000)
514           table_size = 65521;
515
516         /* Put the contexts into a hash table of size table_size.  */
517         {
518           struct list_cell { const char *context; struct list_cell *next; };
519           struct list_cell *list_memory =
520             XNMALLOC (table_size, struct list_cell);
521           struct list_cell *freelist;
522           struct bucket { struct list_cell *head; struct list_cell **tail; };
523           struct bucket *buckets = XNMALLOC (table_size, struct bucket);
524           size_t i;
525
526           freelist = list_memory;
527
528           for (i = 0; i < table_size; i++)
529             {
530               buckets[i].head = NULL;
531               buckets[i].tail = &buckets[i].head;
532             }
533
534           {
535             void *iter;
536             const void *key;
537             size_t keylen;
538             void *null;
539
540             iter = NULL;
541             while (hash_iterate (&all_contexts, &iter, &key, &keylen, &null)
542                    == 0)
543               {
544                 const char *context = (const char *)key;
545                 i = string_hashcode (context) % table_size;
546                 freelist->context = context;
547                 freelist->next = NULL;
548                 *buckets[i].tail = freelist;
549                 buckets[i].tail = &freelist->next;
550                 freelist++;
551               }
552           }
553
554           /* Determine the total context pool size.  */
555           {
556             size_t pool_size;
557
558             pool_size = 2;
559             for (i = 0; i < table_size; i++)
560               if (buckets[i].head != NULL)
561                 {
562                   const struct list_cell *p;
563
564                   for (p = buckets[i].head; p != NULL; p = p->next)
565                     pool_size += 1 + strlen (p->context);
566                   pool_size++;
567                   if ((pool_size % 2) != 0)
568                     pool_size++;
569                 }
570             if (pool_size <= 0x20000)
571               {
572                 /* Prepare the contexts section.  */
573                 struct obstack contexts_pool;
574                 size_t pool_offset;
575
576                 obstack_init (&contexts_pool);
577
578                 append_u16 (&contexts_pool, table_size);
579                 pool_offset = 2;
580                 for (i = 0; i < table_size; i++)
581                   if (buckets[i].head != NULL)
582                     {
583                       const struct list_cell *p;
584
585                       append_u16 (&contexts_pool, pool_offset / 2);
586                       for (p = buckets[i].head; p != NULL; p = p->next)
587                         pool_offset += 1 + strlen (p->context);
588                       pool_offset++;
589                       if ((pool_offset % 2) != 0)
590                         pool_offset++;
591                     }
592                   else
593                     append_u16 (&contexts_pool, 0);
594                 if (!(pool_offset == pool_size))
595                   abort ();
596
597                 append_u16 (&contexts_pool, 0);
598                 pool_offset = 2;
599                 for (i = 0; i < table_size; i++)
600                   if (buckets[i].head != NULL)
601                     {
602                       const struct list_cell *p;
603
604                       for (p = buckets[i].head; p != NULL; p = p->next)
605                         {
606                           append_u8 (&contexts_pool, strlen (p->context));
607                           obstack_grow (&contexts_pool,
608                                         p->context, strlen (p->context));
609                           pool_offset += 1 + strlen (p->context);
610                         }
611                       append_u8 (&contexts_pool, 0);
612                       pool_offset++;
613                       if ((pool_offset % 2) != 0)
614                         {
615                           append_u8 (&contexts_pool, 0);
616                           pool_offset++;
617                         }
618                     }
619                 if (!(pool_offset == pool_size))
620                   abort ();
621
622                 if (!(obstack_object_size (&contexts_pool)
623                       == 2 + 2 * table_size + pool_size))
624                   abort ();
625
626                 /* Write the contexts section.  */
627                 write_section (output_file, 0x2f, obstack_base (&contexts_pool),
628                                obstack_object_size (&contexts_pool));
629
630                 obstack_free (&contexts_pool, NULL);
631               }
632           }
633
634           free (buckets);
635           free (list_memory);
636         }
637
638         hash_destroy (&all_contexts);
639       }
640   }
641
642   obstack_free (&messages_pool, NULL);
643   obstack_free (&hashes_pool, NULL);
644 }
645
646
647 int
648 msgdomain_write_qt (message_list_ty *mlp, const char *canon_encoding,
649                     const char *domain_name, const char *file_name)
650 {
651   FILE *output_file;
652
653   /* If no entry for this domain don't even create the file.  */
654   if (mlp->nitems != 0)
655     {
656       /* Determine whether mlp has plural entries.  */
657       {
658         bool has_plural;
659         size_t j;
660
661         has_plural = false;
662         for (j = 0; j < mlp->nitems; j++)
663           if (mlp->item[j]->msgid_plural != NULL)
664             has_plural = true;
665         if (has_plural)
666           {
667             multiline_error (xstrdup (""),
668                              xstrdup (_("\
669 message catalog has plural form translations\n\
670 but the Qt message catalog format doesn't support plural handling\n")));
671             return 1;
672           }
673       }
674
675       /* Convert the messages to Unicode.  */
676       iconv_message_list (mlp, canon_encoding, po_charset_utf8, NULL);
677
678       /* Determine whether mlp has non-ISO-8859-1 msgctxt entries.  */
679       {
680         size_t j;
681
682         for (j = 0; j < mlp->nitems; j++)
683           {
684             const char *string = mlp->item[j]->msgctxt;
685
686             if (string != NULL)
687               {
688                 /* An UTF-8 encoded string fits in ISO-8859-1 if and only if
689                    all its bytes are < 0xc4.  */
690                 for (; *string; string++)
691                   if ((unsigned char) *string >= 0xc4)
692                     {
693                       multiline_error (xstrdup (""),
694                                        xstrdup (_("\
695 message catalog has msgctxt strings containing characters outside ISO-8859-1\n\
696 but the Qt message catalog format supports Unicode only in the translated\n\
697 strings, not in the context strings\n")));
698                       return 1;
699                     }
700               }
701           }
702       }
703
704       /* Determine whether mlp has non-ISO-8859-1 msgid entries.  */
705       {
706         size_t j;
707
708         for (j = 0; j < mlp->nitems; j++)
709           {
710             const char *string = mlp->item[j]->msgid;
711
712             /* An UTF-8 encoded string fits in ISO-8859-1 if and only if all
713                its bytes are < 0xc4.  */
714             for (; *string; string++)
715               if ((unsigned char) *string >= 0xc4)
716                 {
717                   multiline_error (xstrdup (""),
718                                    xstrdup (_("\
719 message catalog has msgid strings containing characters outside ISO-8859-1\n\
720 but the Qt message catalog format supports Unicode only in the translated\n\
721 strings, not in the untranslated strings\n")));
722                   return 1;
723                 }
724           }
725       }
726
727       if (strcmp (domain_name, "-") == 0)
728         {
729           output_file = stdout;
730           SET_BINARY (fileno (output_file));
731         }
732       else
733         {
734           output_file = fopen (file_name, "wb");
735           if (output_file == NULL)
736             {
737               error (0, errno, _("error while opening \"%s\" for writing"),
738                      file_name);
739               return 1;
740             }
741         }
742
743       if (output_file != NULL)
744         {
745           write_qm (output_file, mlp);
746
747           /* Make sure nothing went wrong.  */
748           if (fwriteerror (output_file))
749             error (EXIT_FAILURE, errno, _("error while writing \"%s\" file"),
750                    file_name);
751         }
752     }
753
754   return 0;
755 }