/src/gettext/gettext-tools/src/write-po.c
Line | Count | Source |
1 | | /* GNU gettext - internationalization aids |
2 | | Copyright (C) 1995-2026 Free Software Foundation, Inc. |
3 | | |
4 | | This program is free software: you can redistribute it and/or modify |
5 | | it under the terms of the GNU General Public License as published by |
6 | | the Free Software Foundation; either version 3 of the License, or |
7 | | (at your option) any later version. |
8 | | |
9 | | This program is distributed in the hope that it will be useful, |
10 | | but WITHOUT ANY WARRANTY; without even the implied warranty of |
11 | | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
12 | | GNU General Public License for more details. |
13 | | |
14 | | You should have received a copy of the GNU General Public License |
15 | | along with this program. If not, see <https://www.gnu.org/licenses/>. */ |
16 | | |
17 | | /* Written by Peter Miller, Ulrich Drepper, and Bruno Haible. */ |
18 | | |
19 | | #include <config.h> |
20 | | #include <alloca.h> |
21 | | |
22 | | /* Specification. */ |
23 | | #include "write-po.h" |
24 | | |
25 | | #include <errno.h> |
26 | | #include <limits.h> |
27 | | #include <stdio.h> |
28 | | #include <stdlib.h> |
29 | | #include <string.h> |
30 | | |
31 | | #if HAVE_ICONV |
32 | | # include <iconv.h> |
33 | | #endif |
34 | | |
35 | | #include <textstyle.h> |
36 | | |
37 | | #include "attribute.h" |
38 | | #include "c-ctype.h" |
39 | | #include "po-charset.h" |
40 | | #include "format.h" |
41 | | #include "unilbrk.h" |
42 | | #include "msgl-ascii.h" |
43 | | #include "pos.h" |
44 | | #include "write-catalog.h" |
45 | | #include "xalloc.h" |
46 | | #include "xmalloca.h" |
47 | | #include "c-strstr.h" |
48 | | #include "xvasprintf.h" |
49 | | #include "verify.h" |
50 | | #include "xerror-handler.h" |
51 | | #include "gettext.h" |
52 | | |
53 | | /* Our regular abbreviation. */ |
54 | 0 | #define _(str) gettext (str) |
55 | | |
56 | | |
57 | | /* =================== Putting together a #, flags line. =================== */ |
58 | | |
59 | | |
60 | | /* Convert IS_FORMAT in the context of programming language LANG to a flag |
61 | | string for use in #, flags. */ |
62 | | |
63 | | char * |
64 | | make_format_description_string (enum is_format is_format, const char *lang, |
65 | | bool debug) |
66 | 0 | { |
67 | 0 | char *result; |
68 | |
|
69 | 0 | switch (is_format) |
70 | 0 | { |
71 | | /* Cf. possible_format_p. */ |
72 | 0 | case possible: |
73 | 0 | if (debug) |
74 | 0 | { |
75 | 0 | result = xasprintf ("possible-%s-format", lang); |
76 | 0 | break; |
77 | 0 | } |
78 | 0 | FALLTHROUGH; |
79 | 0 | case yes_according_to_context: |
80 | 0 | case yes: |
81 | 0 | result = xasprintf ("%s-format", lang); |
82 | 0 | break; |
83 | | /* Cf. not_format_p. */ |
84 | 0 | case no: |
85 | 0 | result = xasprintf ("no-%s-format", lang); |
86 | 0 | break; |
87 | 0 | default: |
88 | | /* The others have already been filtered out by significant_format_p. */ |
89 | 0 | abort (); |
90 | 0 | } |
91 | | |
92 | 0 | assume (result != NULL); |
93 | 0 | return result; |
94 | 0 | } |
95 | | |
96 | | |
97 | | /* Return true if IS_FORMAT is worth mentioning in a #, flags list. |
98 | | This is the same as |
99 | | possible_format_p (is_format) || not_format_p (is_format). */ |
100 | | |
101 | | bool |
102 | | significant_format_p (enum is_format is_format) |
103 | 0 | { |
104 | 0 | return is_format != undecided && is_format != impossible; |
105 | 0 | } |
106 | | |
107 | | |
108 | | /* Return true if one of IS_FORMAT is worth mentioning in a #, flags list. */ |
109 | | |
110 | | static bool |
111 | | has_significant_format_p (const enum is_format is_format[NFORMATS]) |
112 | 0 | { |
113 | 0 | for (size_t i = 0; i < NFORMATS; i++) |
114 | 0 | if (significant_format_p (is_format[i])) |
115 | 0 | return true; |
116 | 0 | return false; |
117 | 0 | } |
118 | | |
119 | | |
120 | | /* Convert a RANGE to a freshly allocated string for use in #, flags. */ |
121 | | |
122 | | char * |
123 | | make_range_description_string (struct argument_range range) |
124 | 0 | { |
125 | 0 | char *result = xasprintf ("range: %d..%d", range.min, range.max); |
126 | 0 | assume (result != NULL); |
127 | 0 | return result; |
128 | 0 | } |
129 | | |
130 | | |
131 | | /* Convert a wrapping flag DO_WRAP to a string for use in #, flags. */ |
132 | | |
133 | | static const char * |
134 | | make_c_width_description_string (enum is_wrap do_wrap) |
135 | 0 | { |
136 | 0 | const char *result = NULL; |
137 | |
|
138 | 0 | switch (do_wrap) |
139 | 0 | { |
140 | 0 | case yes: |
141 | 0 | result = "wrap"; |
142 | 0 | break; |
143 | 0 | case no: |
144 | 0 | result = "no-wrap"; |
145 | 0 | break; |
146 | 0 | default: |
147 | 0 | abort (); |
148 | 0 | } |
149 | | |
150 | 0 | return result; |
151 | 0 | } |
152 | | |
153 | | |
154 | | /* ========================== Styling primitives. ========================== */ |
155 | | |
156 | | |
157 | | /* When compiled in src, enable styling support. |
158 | | When compiled in libgettextpo, don't enable styling support. */ |
159 | | #ifdef GETTEXTDATADIR |
160 | | |
161 | | /* All ostream_t instances are in fact styled_ostream_t instances. */ |
162 | | #define is_stylable(stream) true |
163 | | |
164 | | /* Start a run of text belonging to a given CSS class. */ |
165 | | static inline void |
166 | | begin_css_class (ostream_t stream, const char *classname) |
167 | | { |
168 | | styled_ostream_begin_use_class ((styled_ostream_t) stream, classname); |
169 | | } |
170 | | |
171 | | /* End a run of text belonging to a given CSS class. */ |
172 | | static inline void |
173 | | end_css_class (ostream_t stream, const char *classname) |
174 | | { |
175 | | styled_ostream_end_use_class ((styled_ostream_t) stream, classname); |
176 | | } |
177 | | |
178 | | #else |
179 | | |
180 | | #define is_stylable(stream) false |
181 | 0 | #define begin_css_class(stream,classname) (void)(classname) |
182 | 0 | #define end_css_class(stream,classname) (void)(classname) |
183 | | |
184 | | #endif |
185 | | |
186 | | /* CSS classes at message level. */ |
187 | | static const char class_header[] = "header"; |
188 | | static const char class_translated[] = "translated"; |
189 | | static const char class_untranslated[] = "untranslated"; |
190 | | static const char class_fuzzy[] = "fuzzy"; |
191 | | static const char class_obsolete[] = "obsolete"; |
192 | | |
193 | | /* CSS classes describing the parts of a message. */ |
194 | | static const char class_comment[] = "comment"; |
195 | | static const char class_translator_comment[] = "translator-comment"; |
196 | | static const char class_extracted_comment[] = "extracted-comment"; |
197 | | static const char class_reference_comment[] = "reference-comment"; |
198 | | static const char class_reference[] = "reference"; |
199 | | static const char class_flag_comment[] = "flag-comment"; |
200 | | static const char class_flag[] = "flag"; |
201 | | static const char class_fuzzy_flag[] = "fuzzy-flag"; |
202 | | static const char class_previous_comment[] = "previous-comment"; |
203 | | static const char class_previous[] = "previous"; |
204 | | static const char class_msgid[] = "msgid"; |
205 | | static const char class_msgstr[] = "msgstr"; |
206 | | static const char class_keyword[] = "keyword"; |
207 | | static const char class_string[] = "string"; |
208 | | |
209 | | /* CSS classes for the contents of strings. */ |
210 | | static const char class_text[] = "text"; |
211 | | static const char class_escape_sequence[] = "escape-sequence"; |
212 | | static const char class_format_directive[] = "format-directive"; |
213 | | static const char class_invalid_format_directive[] = "invalid-format-directive"; |
214 | | #if 0 |
215 | | static const char class_added[] = "added"; |
216 | | static const char class_changed[] = "changed"; |
217 | | static const char class_removed[] = "removed"; |
218 | | #endif |
219 | | |
220 | | /* Per-character attributes. */ |
221 | | enum |
222 | | { |
223 | | ATTR_ESCAPE_SEQUENCE = 1 << 0, |
224 | | /* The following two are exclusive. */ |
225 | | ATTR_FORMAT_DIRECTIVE = 1 << 1, |
226 | | ATTR_INVALID_FORMAT_DIRECTIVE = 1 << 2 |
227 | | }; |
228 | | |
229 | | |
230 | | /* ================ Output parts of a message, as comments. ================ */ |
231 | | |
232 | | |
233 | | /* Output mp->comment as a set of comment lines. */ |
234 | | |
235 | | static bool print_comment = true; |
236 | | |
237 | | void |
238 | | message_print_style_comment (bool flag) |
239 | 0 | { |
240 | 0 | print_comment = flag; |
241 | 0 | } |
242 | | |
243 | | void |
244 | | message_print_comment (const message_ty *mp, ostream_t stream) |
245 | 0 | { |
246 | 0 | if (print_comment && mp->comment != NULL) |
247 | 0 | { |
248 | 0 | begin_css_class (stream, class_translator_comment); |
249 | |
|
250 | 0 | for (size_t j = 0; j < mp->comment->nitems; ++j) |
251 | 0 | { |
252 | 0 | const char *s = mp->comment->item[j]; |
253 | 0 | do |
254 | 0 | { |
255 | 0 | ostream_write_str (stream, "#"); |
256 | 0 | if (*s != '\0') |
257 | 0 | ostream_write_str (stream, " "); |
258 | 0 | const char *e = strchr (s, '\n'); |
259 | 0 | if (e == NULL) |
260 | 0 | { |
261 | 0 | ostream_write_str (stream, s); |
262 | 0 | s = NULL; |
263 | 0 | } |
264 | 0 | else |
265 | 0 | { |
266 | 0 | ostream_write_mem (stream, s, e - s); |
267 | 0 | s = e + 1; |
268 | 0 | } |
269 | 0 | ostream_write_str (stream, "\n"); |
270 | 0 | } |
271 | 0 | while (s != NULL); |
272 | 0 | } |
273 | |
|
274 | 0 | end_css_class (stream, class_translator_comment); |
275 | 0 | } |
276 | 0 | } |
277 | | |
278 | | |
279 | | /* Output mp->comment_dot as a set of comment lines. */ |
280 | | |
281 | | void |
282 | | message_print_comment_dot (const message_ty *mp, ostream_t stream) |
283 | 0 | { |
284 | 0 | if (mp->comment_dot != NULL) |
285 | 0 | { |
286 | 0 | begin_css_class (stream, class_extracted_comment); |
287 | |
|
288 | 0 | for (size_t j = 0; j < mp->comment_dot->nitems; ++j) |
289 | 0 | { |
290 | 0 | const char *s = mp->comment_dot->item[j]; |
291 | 0 | ostream_write_str (stream, "#."); |
292 | 0 | if (*s != '\0') |
293 | 0 | ostream_write_str (stream, " "); |
294 | 0 | ostream_write_str (stream, s); |
295 | 0 | ostream_write_str (stream, "\n"); |
296 | 0 | } |
297 | |
|
298 | 0 | end_css_class (stream, class_extracted_comment); |
299 | 0 | } |
300 | 0 | } |
301 | | |
302 | | |
303 | | /* Output mp->filepos as a set of comment lines. */ |
304 | | |
305 | | static enum filepos_comment_type filepos_comment_type = filepos_comment_full; |
306 | | |
307 | | void |
308 | | message_print_comment_filepos (const message_ty *mp, ostream_t stream, |
309 | | const char *charset, bool uniforum, |
310 | | size_t page_width) |
311 | 0 | { |
312 | 0 | if (filepos_comment_type != filepos_comment_none |
313 | 0 | && mp->filepos_count != 0) |
314 | 0 | { |
315 | 0 | begin_css_class (stream, class_reference_comment); |
316 | |
|
317 | 0 | lex_pos_ty *filepos; |
318 | 0 | size_t filepos_count; |
319 | 0 | if (filepos_comment_type == filepos_comment_file) |
320 | 0 | { |
321 | 0 | filepos_count = 0; |
322 | 0 | filepos = XNMALLOC (mp->filepos_count, lex_pos_ty); |
323 | |
|
324 | 0 | for (size_t i = 0; i < mp->filepos_count; ++i) |
325 | 0 | { |
326 | 0 | lex_pos_ty *pp = &mp->filepos[i]; |
327 | |
|
328 | 0 | size_t j; |
329 | 0 | for (j = 0; j < filepos_count; j++) |
330 | 0 | if (strcmp (filepos[j].file_name, pp->file_name) == 0) |
331 | 0 | break; |
332 | |
|
333 | 0 | if (j == filepos_count) |
334 | 0 | { |
335 | 0 | filepos[filepos_count].file_name = pp->file_name; |
336 | 0 | filepos[filepos_count].line_number = (size_t)-1; |
337 | 0 | filepos_count++; |
338 | 0 | } |
339 | 0 | } |
340 | 0 | } |
341 | 0 | else |
342 | 0 | { |
343 | 0 | filepos = mp->filepos; |
344 | 0 | filepos_count = mp->filepos_count; |
345 | 0 | } |
346 | |
|
347 | 0 | if (uniforum) |
348 | 0 | { |
349 | 0 | for (size_t j = 0; j < filepos_count; ++j) |
350 | 0 | { |
351 | 0 | lex_pos_ty *pp = &filepos[j]; |
352 | |
|
353 | 0 | const char *cp = pp->file_name; |
354 | 0 | while (cp[0] == '.' && cp[1] == '/') |
355 | 0 | cp += 2; |
356 | |
|
357 | 0 | ostream_write_str (stream, "# "); |
358 | 0 | begin_css_class (stream, class_reference); |
359 | | /* There are two Sun formats to choose from: SunOS and |
360 | | Solaris. Use the Solaris form here. */ |
361 | 0 | char *str = xasprintf ("File: %s, line: %ld", |
362 | 0 | cp, (long) pp->line_number); |
363 | 0 | assume (str != NULL); |
364 | 0 | ostream_write_str (stream, str); |
365 | 0 | end_css_class (stream, class_reference); |
366 | 0 | ostream_write_str (stream, "\n"); |
367 | 0 | free (str); |
368 | 0 | } |
369 | 0 | } |
370 | 0 | else |
371 | 0 | { |
372 | 0 | const char *canon_charset = po_charset_canonicalize (charset); |
373 | |
|
374 | 0 | ostream_write_str (stream, "#:"); |
375 | 0 | size_t column = 2; |
376 | 0 | for (size_t j = 0; j < filepos_count; ++j) |
377 | 0 | { |
378 | 0 | lex_pos_ty *pp = &filepos[j]; |
379 | |
|
380 | 0 | const char *cp = pp->file_name; |
381 | 0 | while (cp[0] == '.' && cp[1] == '/') |
382 | 0 | cp += 2; |
383 | |
|
384 | 0 | char buffer[22]; |
385 | 0 | if (filepos_comment_type == filepos_comment_file |
386 | | /* Some xgettext input formats, like RST, lack line |
387 | | numbers. */ |
388 | 0 | || pp->line_number == (size_t)(-1)) |
389 | 0 | buffer[0] = '\0'; |
390 | 0 | else |
391 | 0 | sprintf (buffer, ":%ld", (long) pp->line_number); |
392 | | |
393 | | /* File names are usually entirely ASCII. Therefore strlen is |
394 | | sufficient to determine their printed width. */ |
395 | 0 | size_t width = strlen (cp) + strlen (buffer) + 1; |
396 | 0 | if (column > 2 && column + width > page_width) |
397 | 0 | { |
398 | 0 | ostream_write_str (stream, "\n#:"); |
399 | 0 | column = 2; |
400 | 0 | } |
401 | 0 | ostream_write_str (stream, " "); |
402 | 0 | begin_css_class (stream, class_reference); |
403 | 0 | if (pos_filename_has_spaces (pp)) |
404 | 0 | { |
405 | | /* Enclose the file name within U+2068 and U+2069 characters, |
406 | | so that it can be parsed unambiguously. */ |
407 | 0 | if (canon_charset == po_charset_utf8) |
408 | 0 | { |
409 | 0 | ostream_write_str (stream, "\xE2\x81\xA8"); /* U+2068 */ |
410 | 0 | ostream_write_str (stream, cp); |
411 | 0 | ostream_write_str (stream, "\xE2\x81\xA9"); /* U+2069 */ |
412 | 0 | } |
413 | 0 | else if (canon_charset != NULL |
414 | 0 | && strcmp (canon_charset, "GB18030") == 0) |
415 | 0 | { |
416 | 0 | ostream_write_str (stream, "\x81\x36\xAC\x34"); /* U+2068 */ |
417 | 0 | ostream_write_str (stream, cp); |
418 | 0 | ostream_write_str (stream, "\x81\x36\xAC\x35"); /* U+2069 */ |
419 | 0 | } |
420 | 0 | else |
421 | 0 | abort (); |
422 | 0 | } |
423 | 0 | else |
424 | 0 | ostream_write_str (stream, cp); |
425 | 0 | ostream_write_str (stream, buffer); |
426 | 0 | end_css_class (stream, class_reference); |
427 | 0 | column += width; |
428 | 0 | } |
429 | 0 | ostream_write_str (stream, "\n"); |
430 | 0 | } |
431 | | |
432 | 0 | if (filepos != mp->filepos) |
433 | 0 | free (filepos); |
434 | |
|
435 | 0 | end_css_class (stream, class_reference_comment); |
436 | 0 | } |
437 | 0 | } |
438 | | |
439 | | |
440 | | /* Output mp->is_fuzzy, mp->is_format, mp->range, mp->do_wrap as a comment |
441 | | line. */ |
442 | | |
443 | | void |
444 | | message_print_comment_flags (const message_ty *mp, ostream_t stream, bool debug) |
445 | 0 | { |
446 | 0 | if ((mp->is_fuzzy && mp->msgstr[0] != '\0') |
447 | 0 | || has_significant_format_p (mp->is_format) |
448 | 0 | || has_range_p (mp->range) |
449 | 0 | || mp->do_wrap == no) |
450 | 0 | { |
451 | 0 | bool first_flag = true; |
452 | |
|
453 | 0 | begin_css_class (stream, class_flag_comment); |
454 | |
|
455 | 0 | ostream_write_str (stream, "#,"); |
456 | | |
457 | | /* We don't print the fuzzy flag if the msgstr is empty. This |
458 | | might be introduced by the user but we want to normalize the |
459 | | output. */ |
460 | 0 | if (mp->is_fuzzy && mp->msgstr[0] != '\0') |
461 | 0 | { |
462 | 0 | ostream_write_str (stream, " "); |
463 | 0 | begin_css_class (stream, class_flag); |
464 | 0 | begin_css_class (stream, class_fuzzy_flag); |
465 | 0 | ostream_write_str (stream, "fuzzy"); |
466 | 0 | end_css_class (stream, class_fuzzy_flag); |
467 | 0 | end_css_class (stream, class_flag); |
468 | 0 | first_flag = false; |
469 | 0 | } |
470 | |
|
471 | 0 | for (size_t i = 0; i < NFORMATS; i++) |
472 | 0 | if (significant_format_p (mp->is_format[i])) |
473 | 0 | { |
474 | 0 | if (!first_flag) |
475 | 0 | ostream_write_str (stream, ","); |
476 | |
|
477 | 0 | ostream_write_str (stream, " "); |
478 | 0 | begin_css_class (stream, class_flag); |
479 | 0 | char *string = |
480 | 0 | make_format_description_string (mp->is_format[i], |
481 | 0 | format_language[i], debug); |
482 | 0 | ostream_write_str (stream, string); |
483 | 0 | free (string); |
484 | 0 | end_css_class (stream, class_flag); |
485 | 0 | first_flag = false; |
486 | 0 | } |
487 | |
|
488 | 0 | if (has_range_p (mp->range)) |
489 | 0 | { |
490 | 0 | if (!first_flag) |
491 | 0 | ostream_write_str (stream, ","); |
492 | |
|
493 | 0 | ostream_write_str (stream, " "); |
494 | 0 | begin_css_class (stream, class_flag); |
495 | 0 | char *string = make_range_description_string (mp->range); |
496 | 0 | ostream_write_str (stream, string); |
497 | 0 | free (string); |
498 | 0 | end_css_class (stream, class_flag); |
499 | 0 | first_flag = false; |
500 | 0 | } |
501 | |
|
502 | 0 | if (mp->do_wrap == no) |
503 | 0 | { |
504 | 0 | if (!first_flag) |
505 | 0 | ostream_write_str (stream, ","); |
506 | |
|
507 | 0 | ostream_write_str (stream, " "); |
508 | 0 | begin_css_class (stream, class_flag); |
509 | 0 | ostream_write_str (stream, |
510 | 0 | make_c_width_description_string (mp->do_wrap)); |
511 | 0 | end_css_class (stream, class_flag); |
512 | 0 | first_flag = false; |
513 | 0 | } |
514 | |
|
515 | 0 | ostream_write_str (stream, "\n"); |
516 | |
|
517 | 0 | end_css_class (stream, class_flag_comment); |
518 | 0 | } |
519 | 0 | } |
520 | | |
521 | | |
522 | | /* ========= Some parameters for use by 'msgdomain_list_print_po'. ========= */ |
523 | | |
524 | | |
525 | | /* This variable controls the extent to which the page width applies. |
526 | | True means it applies to message strings and file reference lines. |
527 | | False means it applies to file reference lines only. */ |
528 | | static bool wrap_strings = true; |
529 | | |
530 | | void |
531 | | message_page_width_ignore () |
532 | 0 | { |
533 | 0 | wrap_strings = false; |
534 | 0 | } |
535 | | |
536 | | |
537 | | /* These three variables control the output style of the message_print |
538 | | function. Interface functions for them are to be used. */ |
539 | | static bool indent = false; |
540 | | static bool uniforum = false; |
541 | | static bool escape = false; |
542 | | |
543 | | void |
544 | | message_print_style_indent () |
545 | 0 | { |
546 | 0 | indent = true; |
547 | 0 | } |
548 | | |
549 | | void |
550 | | message_print_style_uniforum () |
551 | 0 | { |
552 | 0 | uniforum = true; |
553 | 0 | } |
554 | | |
555 | | void |
556 | | message_print_style_escape (bool flag) |
557 | 0 | { |
558 | 0 | escape = flag; |
559 | 0 | } |
560 | | |
561 | | void |
562 | | message_print_style_filepos (enum filepos_comment_type type) |
563 | 0 | { |
564 | 0 | filepos_comment_type = type; |
565 | 0 | } |
566 | | |
567 | | |
568 | | /* --add-location argument handling. Return an error indicator. */ |
569 | | bool |
570 | | handle_filepos_comment_option (const char *option) |
571 | 0 | { |
572 | 0 | if (option != NULL) |
573 | 0 | { |
574 | 0 | if (strcmp (option, "never") == 0 || strcmp (option, "no") == 0) |
575 | 0 | message_print_style_filepos (filepos_comment_none); |
576 | 0 | else if (strcmp (option, "full") == 0 || strcmp (option, "yes") == 0) |
577 | 0 | message_print_style_filepos (filepos_comment_full); |
578 | 0 | else if (strcmp (option, "file") == 0) |
579 | 0 | message_print_style_filepos (filepos_comment_file); |
580 | 0 | else |
581 | 0 | { |
582 | 0 | fprintf (stderr, "invalid --add-location argument: %s\n", option); |
583 | 0 | return true; |
584 | 0 | } |
585 | 0 | } |
586 | 0 | else |
587 | | /* --add-location is equivalent to --add-location=full. */ |
588 | 0 | message_print_style_filepos (filepos_comment_full); |
589 | 0 | return false; |
590 | 0 | } |
591 | | |
592 | | |
593 | | /* =============== msgdomain_list_print_po() and subroutines. =============== */ |
594 | | |
595 | | |
596 | | /* A version of memcpy optimized for the case n <= 1. */ |
597 | | static inline void |
598 | | memcpy_small (void *dst, const void *src, size_t n) |
599 | 0 | { |
600 | 0 | if (n > 0) |
601 | 0 | { |
602 | 0 | char *q = (char *) dst; |
603 | 0 | const char *p = (const char *) src; |
604 | |
|
605 | 0 | *q = *p; |
606 | 0 | if (--n > 0) |
607 | 0 | do *++q = *++p; while (--n > 0); |
608 | 0 | } |
609 | 0 | } |
610 | | |
611 | | |
612 | | /* A version of memset optimized for the case n <= 1. */ |
613 | | /* Avoid false GCC warning "‘__builtin_memset’ specified bound |
614 | | 18446744073709551614 exceeds maximum object size 9223372036854775807." |
615 | | Cf. <https://gcc.gnu.org/bugzilla/show_bug.cgi?id=109995>. */ |
616 | | #if __GNUC__ >= 7 |
617 | | # pragma GCC diagnostic ignored "-Wstringop-overflow" |
618 | | #endif |
619 | | static inline void |
620 | | memset_small (void *dst, char c, size_t n) |
621 | 0 | { |
622 | 0 | if (n > 0) |
623 | 0 | { |
624 | 0 | char *p = (char *) dst; |
625 | |
|
626 | 0 | *p = c; |
627 | 0 | if (--n > 0) |
628 | 0 | do *++p = c; while (--n > 0); |
629 | 0 | } |
630 | 0 | } |
631 | | |
632 | | |
633 | | static void |
634 | | wrap (const message_ty *mp, ostream_t stream, |
635 | | const char *line_prefix, int extra_indent, const char *css_class, |
636 | | const char *name, const char *value, |
637 | | enum is_wrap do_wrap, size_t page_width, |
638 | | const char *charset, xerror_handler_ty xeh) |
639 | 0 | { |
640 | 0 | const char *canon_charset = po_charset_canonicalize (charset); |
641 | |
|
642 | 0 | bool weird_cjk; |
643 | 0 | #if HAVE_ICONV |
644 | | /* The old Solaris/openwin msgfmt and GNU msgfmt <= 0.10.35 don't know |
645 | | about multibyte encodings, and require a spurious backslash after |
646 | | every multibyte character whose last byte is 0x5C. Some programs, |
647 | | like vim, distribute PO files in this broken format. It is important |
648 | | for such programs that GNU msgmerge continues to support this old |
649 | | PO file format when the Makefile requests it. */ |
650 | 0 | iconv_t conv; |
651 | 0 | { |
652 | 0 | const char *envval = getenv ("OLD_PO_FILE_OUTPUT"); |
653 | 0 | if (envval != NULL && *envval != '\0') |
654 | | /* Write a PO file in old format, with extraneous backslashes. */ |
655 | 0 | conv = (iconv_t)(-1); |
656 | 0 | else |
657 | 0 | if (canon_charset == NULL) |
658 | | /* Invalid PO file encoding. */ |
659 | 0 | conv = (iconv_t)(-1); |
660 | 0 | else |
661 | | /* Use iconv() to parse multibyte characters. */ |
662 | 0 | conv = iconv_open ("UTF-8", canon_charset); |
663 | 0 | } |
664 | |
|
665 | 0 | if (conv != (iconv_t)(-1)) |
666 | 0 | weird_cjk = false; |
667 | 0 | else |
668 | 0 | #endif |
669 | 0 | if (canon_charset == NULL) |
670 | 0 | weird_cjk = false; |
671 | 0 | else |
672 | 0 | weird_cjk = po_is_charset_weird_cjk (canon_charset); |
673 | |
|
674 | 0 | if (canon_charset == NULL) |
675 | 0 | canon_charset = po_charset_ascii; |
676 | | |
677 | | /* Determine the extent of format string directives. */ |
678 | 0 | char *fmtdir = NULL; |
679 | 0 | char *fmtdirattr = NULL; |
680 | 0 | if (value[0] != '\0') |
681 | 0 | { |
682 | 0 | bool is_msgstr = |
683 | 0 | (strlen (name) >= 6 && memcmp (name, "msgstr", 6) == 0); |
684 | | /* or equivalent: = (css_class == class_msgstr) */ |
685 | |
|
686 | 0 | for (size_t i = 0; i < NFORMATS; i++) |
687 | 0 | if (possible_format_p (mp->is_format[i])) |
688 | 0 | { |
689 | 0 | size_t len = strlen (value); |
690 | 0 | struct formatstring_parser *parser = formatstring_parsers[i]; |
691 | |
|
692 | 0 | fmtdir = XCALLOC (len, char); |
693 | |
|
694 | 0 | char *invalid_reason = NULL; |
695 | 0 | void *descr = parser->parse (value, is_msgstr, fmtdir, &invalid_reason); |
696 | 0 | if (descr != NULL) |
697 | 0 | parser->free (descr); |
698 | | |
699 | | /* Locate the FMTDIR_* bits and transform the array to an array |
700 | | of attributes. */ |
701 | 0 | fmtdirattr = XCALLOC (len, char); |
702 | 0 | const char *fd_end = fmtdir + len; |
703 | 0 | const char *fdp; |
704 | 0 | char *fdap; |
705 | 0 | for (fdp = fmtdir, fdap = fmtdirattr; fdp < fd_end; fdp++, fdap++) |
706 | 0 | if (*fdp & FMTDIR_START) |
707 | 0 | { |
708 | 0 | const char *fdq; |
709 | 0 | for (fdq = fdp; fdq < fd_end; fdq++) |
710 | 0 | if (*fdq & (FMTDIR_END | FMTDIR_ERROR)) |
711 | 0 | break; |
712 | 0 | if (!(fdq < fd_end)) |
713 | | /* The ->parse method has determined the start of a |
714 | | formatstring directive but not stored a bit indicating |
715 | | its end. It is a bug in the ->parse method. */ |
716 | 0 | abort (); |
717 | 0 | if (*fdq & FMTDIR_ERROR) |
718 | 0 | memset (fdap, ATTR_INVALID_FORMAT_DIRECTIVE, fdq - fdp + 1); |
719 | 0 | else |
720 | 0 | memset (fdap, ATTR_FORMAT_DIRECTIVE, fdq - fdp + 1); |
721 | 0 | fdap += fdq - fdp; |
722 | 0 | fdp = fdq; |
723 | 0 | } |
724 | 0 | else |
725 | 0 | *fdap = 0; |
726 | | |
727 | 0 | break; |
728 | 0 | } |
729 | 0 | } |
730 | | |
731 | | /* Loop over the '\n' delimited portions of value. */ |
732 | 0 | const char *s = value; |
733 | 0 | bool first_line = true; |
734 | 0 | do |
735 | 0 | { |
736 | | /* The usual escapes, as defined by the ANSI C Standard. */ |
737 | 0 | # define is_escape(c) \ |
738 | 0 | ((c) == '\a' || (c) == '\b' || (c) == '\f' || (c) == '\n' \ |
739 | 0 | || (c) == '\r' || (c) == '\t' || (c) == '\v') |
740 | |
|
741 | 0 | const char *es; |
742 | 0 | for (es = s; *es != '\0'; ) |
743 | 0 | if (*es++ == '\n') |
744 | 0 | break; |
745 | | |
746 | | /* Expand escape sequences in each portion. */ |
747 | 0 | const char *ep; |
748 | 0 | size_t portion_len; |
749 | 0 | for (ep = s, portion_len = 0; ep < es; ep++) |
750 | 0 | { |
751 | 0 | char c = *ep; |
752 | 0 | if (is_escape (c)) |
753 | 0 | portion_len += 2; |
754 | 0 | else if (escape && !c_isprint ((unsigned char) c)) |
755 | 0 | portion_len += 4; |
756 | 0 | else if (c == '\\' || c == '"') |
757 | 0 | portion_len += 2; |
758 | 0 | else |
759 | 0 | { |
760 | 0 | #if HAVE_ICONV |
761 | 0 | if (conv != (iconv_t)(-1)) |
762 | 0 | { |
763 | | /* Skip over a complete multi-byte character. Don't |
764 | | interpret the second byte of a multi-byte character as |
765 | | ASCII. This is needed for the BIG5, BIG5-HKSCS, GBK, |
766 | | GB18030, SHIFT_JIS, JOHAB encodings. */ |
767 | 0 | char scratchbuf[64]; |
768 | 0 | const char *inptr = ep; |
769 | 0 | size_t insize; |
770 | 0 | char *outptr = &scratchbuf[0]; |
771 | 0 | size_t outsize = sizeof (scratchbuf); |
772 | 0 | size_t res; |
773 | |
|
774 | 0 | res = (size_t)(-1); |
775 | 0 | for (insize = 1; inptr + insize <= es; insize++) |
776 | 0 | { |
777 | 0 | res = iconv (conv, |
778 | 0 | (ICONV_CONST char **) &inptr, &insize, |
779 | 0 | &outptr, &outsize); |
780 | 0 | if (!(res == (size_t)(-1) && errno == EINVAL)) |
781 | 0 | break; |
782 | | /* We expect that no input bytes have been consumed |
783 | | so far. */ |
784 | 0 | if (inptr != ep) |
785 | 0 | abort (); |
786 | 0 | } |
787 | 0 | if (res == (size_t)(-1)) |
788 | 0 | { |
789 | 0 | if (errno == EILSEQ) |
790 | 0 | { |
791 | 0 | xeh->xerror (CAT_SEVERITY_ERROR, mp, NULL, 0, 0, |
792 | 0 | false, |
793 | 0 | _("invalid multibyte sequence")); |
794 | 0 | continue; |
795 | 0 | } |
796 | 0 | else if (errno == EINVAL) |
797 | 0 | { |
798 | | /* This could happen if an incomplete |
799 | | multibyte sequence at the end of input |
800 | | bytes. */ |
801 | 0 | xeh->xerror (CAT_SEVERITY_ERROR, mp, NULL, 0, 0, |
802 | 0 | false, |
803 | 0 | _("incomplete multibyte sequence")); |
804 | 0 | continue; |
805 | 0 | } |
806 | 0 | else |
807 | 0 | abort (); |
808 | 0 | } |
809 | 0 | insize = inptr - ep; |
810 | 0 | portion_len += insize; |
811 | 0 | ep += insize - 1; |
812 | 0 | } |
813 | 0 | else |
814 | 0 | #endif |
815 | 0 | { |
816 | 0 | if (weird_cjk |
817 | | /* Special handling of encodings with CJK structure. */ |
818 | 0 | && ep + 2 <= es |
819 | 0 | && (unsigned char) ep[0] >= 0x80 |
820 | 0 | && (unsigned char) ep[1] >= 0x30) |
821 | 0 | { |
822 | 0 | portion_len += 2; |
823 | 0 | ep += 1; |
824 | 0 | } |
825 | 0 | else |
826 | 0 | portion_len += 1; |
827 | 0 | } |
828 | 0 | } |
829 | 0 | } |
830 | | |
831 | 0 | char *portion = XNMALLOC (portion_len, char); |
832 | 0 | char *overrides = XNMALLOC (portion_len, char); |
833 | 0 | char *attributes = XNMALLOC (portion_len, char); |
834 | 0 | char *pp; |
835 | 0 | char *op; |
836 | 0 | char *ap; |
837 | 0 | for (ep = s, pp = portion, op = overrides, ap = attributes; ep < es; ep++) |
838 | 0 | { |
839 | 0 | char c = *ep; |
840 | 0 | char attr = (fmtdirattr != NULL ? fmtdirattr[ep - value] : 0); |
841 | 0 | char brk = UC_BREAK_UNDEFINED; |
842 | | /* Don't break inside format directives. */ |
843 | 0 | if (attr == ATTR_FORMAT_DIRECTIVE |
844 | 0 | && (fmtdir[ep - value] & FMTDIR_START) == 0) |
845 | 0 | brk = UC_BREAK_PROHIBITED; |
846 | 0 | if (is_escape (c)) |
847 | 0 | { |
848 | 0 | switch (c) |
849 | 0 | { |
850 | 0 | case '\a': c = 'a'; break; |
851 | 0 | case '\b': c = 'b'; break; |
852 | 0 | case '\f': c = 'f'; break; |
853 | 0 | case '\n': c = 'n'; break; |
854 | 0 | case '\r': c = 'r'; break; |
855 | 0 | case '\t': c = 't'; break; |
856 | 0 | case '\v': c = 'v'; break; |
857 | 0 | default: abort (); |
858 | 0 | } |
859 | 0 | *pp++ = '\\'; |
860 | 0 | *pp++ = c; |
861 | 0 | *op++ = brk; |
862 | 0 | *op++ = UC_BREAK_PROHIBITED; |
863 | 0 | *ap++ = attr | ATTR_ESCAPE_SEQUENCE; |
864 | 0 | *ap++ = attr | ATTR_ESCAPE_SEQUENCE; |
865 | | /* We warn about any use of escape sequences beside |
866 | | '\n' and '\t'. */ |
867 | 0 | if (c != 'n' && c != 't') |
868 | 0 | { |
869 | 0 | char *error_message = |
870 | 0 | xasprintf (_("internationalized messages should not contain the '\\%c' escape sequence"), |
871 | 0 | c); |
872 | 0 | xeh->xerror (CAT_SEVERITY_WARNING, mp, NULL, 0, 0, false, |
873 | 0 | error_message); |
874 | 0 | free (error_message); |
875 | 0 | } |
876 | 0 | } |
877 | 0 | else if (escape && !c_isprint ((unsigned char) c)) |
878 | 0 | { |
879 | 0 | *pp++ = '\\'; |
880 | 0 | *pp++ = '0' + (((unsigned char) c >> 6) & 7); |
881 | 0 | *pp++ = '0' + (((unsigned char) c >> 3) & 7); |
882 | 0 | *pp++ = '0' + ((unsigned char) c & 7); |
883 | 0 | *op++ = brk; |
884 | 0 | *op++ = UC_BREAK_PROHIBITED; |
885 | 0 | *op++ = UC_BREAK_PROHIBITED; |
886 | 0 | *op++ = UC_BREAK_PROHIBITED; |
887 | 0 | *ap++ = attr | ATTR_ESCAPE_SEQUENCE; |
888 | 0 | *ap++ = attr | ATTR_ESCAPE_SEQUENCE; |
889 | 0 | *ap++ = attr | ATTR_ESCAPE_SEQUENCE; |
890 | 0 | *ap++ = attr | ATTR_ESCAPE_SEQUENCE; |
891 | 0 | } |
892 | 0 | else if (c == '\\' || c == '"') |
893 | 0 | { |
894 | 0 | *pp++ = '\\'; |
895 | 0 | *pp++ = c; |
896 | 0 | *op++ = brk; |
897 | 0 | *op++ = UC_BREAK_PROHIBITED; |
898 | 0 | *ap++ = attr | ATTR_ESCAPE_SEQUENCE; |
899 | 0 | *ap++ = attr | ATTR_ESCAPE_SEQUENCE; |
900 | 0 | } |
901 | 0 | else |
902 | 0 | { |
903 | 0 | #if HAVE_ICONV |
904 | 0 | if (conv != (iconv_t)(-1)) |
905 | 0 | { |
906 | | /* Copy a complete multi-byte character. Don't |
907 | | interpret the second byte of a multi-byte character as |
908 | | ASCII. This is needed for the BIG5, BIG5-HKSCS, GBK, |
909 | | GB18030, SHIFT_JIS, JOHAB encodings. */ |
910 | 0 | char scratchbuf[64]; |
911 | 0 | const char *inptr = ep; |
912 | 0 | size_t insize; |
913 | 0 | char *outptr = &scratchbuf[0]; |
914 | 0 | size_t outsize = sizeof (scratchbuf); |
915 | 0 | size_t res; |
916 | |
|
917 | 0 | res = (size_t)(-1); |
918 | 0 | for (insize = 1; inptr + insize <= es; insize++) |
919 | 0 | { |
920 | 0 | res = iconv (conv, |
921 | 0 | (ICONV_CONST char **) &inptr, &insize, |
922 | 0 | &outptr, &outsize); |
923 | 0 | if (!(res == (size_t)(-1) && errno == EINVAL)) |
924 | 0 | break; |
925 | | /* We expect that no input bytes have been consumed |
926 | | so far. */ |
927 | 0 | if (inptr != ep) |
928 | 0 | abort (); |
929 | 0 | } |
930 | 0 | if (res == (size_t)(-1)) |
931 | 0 | { |
932 | 0 | if (errno == EILSEQ) |
933 | 0 | { |
934 | 0 | xeh->xerror (CAT_SEVERITY_ERROR, mp, NULL, 0, 0, |
935 | 0 | false, _("invalid multibyte sequence")); |
936 | 0 | continue; |
937 | 0 | } |
938 | 0 | else |
939 | 0 | abort (); |
940 | 0 | } |
941 | 0 | insize = inptr - ep; |
942 | 0 | memcpy_small (pp, ep, insize); |
943 | 0 | pp += insize; |
944 | 0 | *op = brk; |
945 | 0 | memset_small (op + 1, UC_BREAK_PROHIBITED, insize - 1); |
946 | 0 | op += insize; |
947 | 0 | memset_small (ap, attr, insize); |
948 | 0 | ap += insize; |
949 | 0 | ep += insize - 1; |
950 | 0 | } |
951 | 0 | else |
952 | 0 | #endif |
953 | 0 | { |
954 | 0 | if (weird_cjk |
955 | | /* Special handling of encodings with CJK structure. */ |
956 | 0 | && ep + 2 <= es |
957 | 0 | && (unsigned char) c >= 0x80 |
958 | 0 | && (unsigned char) ep[1] >= 0x30) |
959 | 0 | { |
960 | 0 | *pp++ = c; |
961 | 0 | ep += 1; |
962 | 0 | *pp++ = *ep; |
963 | 0 | *op++ = brk; |
964 | 0 | *op++ = UC_BREAK_PROHIBITED; |
965 | 0 | *ap++ = attr; |
966 | 0 | *ap++ = attr; |
967 | 0 | } |
968 | 0 | else |
969 | 0 | { |
970 | 0 | *pp++ = c; |
971 | 0 | *op++ = brk; |
972 | 0 | *ap++ = attr; |
973 | 0 | } |
974 | 0 | } |
975 | 0 | } |
976 | 0 | } |
977 | | |
978 | | /* Don't break immediately before the "\n" at the end. */ |
979 | 0 | if (es > s && es[-1] == '\n') |
980 | 0 | overrides[portion_len - 2] = UC_BREAK_PROHIBITED; |
981 | |
|
982 | 0 | char *linebreaks = XNMALLOC (portion_len, char); |
983 | | |
984 | | /* Subsequent lines after a break are all indented. |
985 | | See INDENT-S. */ |
986 | 0 | int startcol_after_break = (line_prefix ? strlen (line_prefix) : 0); |
987 | 0 | if (indent) |
988 | 0 | startcol_after_break = (startcol_after_break + extra_indent + 8) & ~7; |
989 | 0 | startcol_after_break++; |
990 | | |
991 | | /* The line width. Allow room for the closing quote character. */ |
992 | 0 | int width = (wrap_strings && do_wrap != no ? page_width : INT_MAX) - 1; |
993 | | /* Adjust for indentation of subsequent lines. */ |
994 | 0 | width -= startcol_after_break; |
995 | |
|
996 | 0 | recompute: ; |
997 | | /* The line starts with different things depending on whether it |
998 | | is the first line, and if we are using the indented style. |
999 | | See INDENT-F. */ |
1000 | 0 | int startcol = (line_prefix ? strlen (line_prefix) : 0); |
1001 | 0 | if (first_line) |
1002 | 0 | { |
1003 | 0 | startcol += strlen (name); |
1004 | 0 | if (indent) |
1005 | 0 | startcol = (startcol + extra_indent + 8) & ~7; |
1006 | 0 | else |
1007 | 0 | startcol++; |
1008 | 0 | } |
1009 | 0 | else |
1010 | 0 | { |
1011 | 0 | if (indent) |
1012 | 0 | startcol = (startcol + extra_indent + 8) & ~7; |
1013 | 0 | } |
1014 | | /* Allow room for the opening quote character. */ |
1015 | 0 | startcol++; |
1016 | | /* Adjust for indentation of subsequent lines. */ |
1017 | 0 | startcol -= startcol_after_break; |
1018 | | |
1019 | | /* Do line breaking on the portion. */ |
1020 | 0 | ulc_width_linebreaks (portion, portion_len, width, startcol, 0, |
1021 | 0 | overrides, canon_charset, linebreaks); |
1022 | | |
1023 | | /* If this is the first line, and we are not using the indented |
1024 | | style, and the line would wrap, then use an empty first line |
1025 | | and restart. */ |
1026 | 0 | if (first_line && !indent |
1027 | 0 | && portion_len > 0 |
1028 | 0 | && (*es != '\0' |
1029 | 0 | || startcol > width |
1030 | 0 | || memchr (linebreaks, UC_BREAK_POSSIBLE, portion_len) != NULL)) |
1031 | 0 | { |
1032 | 0 | if (line_prefix != NULL) |
1033 | 0 | ostream_write_str (stream, line_prefix); |
1034 | 0 | begin_css_class (stream, css_class); |
1035 | 0 | begin_css_class (stream, class_keyword); |
1036 | 0 | ostream_write_str (stream, name); |
1037 | 0 | end_css_class (stream, class_keyword); |
1038 | 0 | ostream_write_str (stream, " "); |
1039 | 0 | begin_css_class (stream, class_string); |
1040 | 0 | ostream_write_str (stream, "\"\""); |
1041 | 0 | end_css_class (stream, class_string); |
1042 | 0 | end_css_class (stream, css_class); |
1043 | 0 | ostream_write_str (stream, "\n"); |
1044 | 0 | first_line = false; |
1045 | | /* Recompute startcol and linebreaks. */ |
1046 | 0 | goto recompute; |
1047 | 0 | } |
1048 | | |
1049 | | /* Print the beginning of the line. This will depend on whether |
1050 | | this is the first line, and if the indented style is being |
1051 | | used. INDENT-F. */ |
1052 | 0 | { |
1053 | 0 | int currcol = 0; |
1054 | |
|
1055 | 0 | if (line_prefix != NULL) |
1056 | 0 | { |
1057 | 0 | ostream_write_str (stream, line_prefix); |
1058 | 0 | currcol = strlen (line_prefix); |
1059 | 0 | } |
1060 | 0 | begin_css_class (stream, css_class); |
1061 | 0 | if (first_line) |
1062 | 0 | { |
1063 | 0 | begin_css_class (stream, class_keyword); |
1064 | 0 | ostream_write_str (stream, name); |
1065 | 0 | currcol += strlen (name); |
1066 | 0 | end_css_class (stream, class_keyword); |
1067 | 0 | if (indent) |
1068 | 0 | { |
1069 | 0 | if (extra_indent > 0) |
1070 | 0 | ostream_write_mem (stream, " ", extra_indent); |
1071 | 0 | currcol += extra_indent; |
1072 | 0 | ostream_write_mem (stream, " ", 8 - (currcol & 7)); |
1073 | 0 | currcol = (currcol + 8) & ~7; |
1074 | 0 | } |
1075 | 0 | else |
1076 | 0 | { |
1077 | 0 | ostream_write_str (stream, " "); |
1078 | 0 | currcol++; |
1079 | 0 | } |
1080 | 0 | first_line = false; |
1081 | 0 | } |
1082 | 0 | else |
1083 | 0 | { |
1084 | 0 | if (indent) |
1085 | 0 | { |
1086 | 0 | if (extra_indent > 0) |
1087 | 0 | ostream_write_mem (stream, " ", extra_indent); |
1088 | 0 | currcol += extra_indent; |
1089 | 0 | ostream_write_mem (stream, " ", 8 - (currcol & 7)); |
1090 | 0 | currcol = (currcol + 8) & ~7; |
1091 | 0 | } |
1092 | 0 | } |
1093 | 0 | } |
1094 | | |
1095 | | /* Print the portion itself, with linebreaks where necessary. */ |
1096 | 0 | { |
1097 | 0 | begin_css_class (stream, class_string); |
1098 | 0 | ostream_write_str (stream, "\""); |
1099 | 0 | begin_css_class (stream, class_text); |
1100 | |
|
1101 | 0 | char currattr = 0; |
1102 | |
|
1103 | 0 | for (size_t i = 0; i < portion_len; i++) |
1104 | 0 | { |
1105 | 0 | if (linebreaks[i] == UC_BREAK_POSSIBLE) |
1106 | 0 | { |
1107 | | /* Change currattr so that it becomes 0. */ |
1108 | 0 | if (currattr & ATTR_ESCAPE_SEQUENCE) |
1109 | 0 | { |
1110 | 0 | end_css_class (stream, class_escape_sequence); |
1111 | 0 | currattr &= ~ATTR_ESCAPE_SEQUENCE; |
1112 | 0 | } |
1113 | 0 | if (currattr & ATTR_FORMAT_DIRECTIVE) |
1114 | 0 | { |
1115 | 0 | end_css_class (stream, class_format_directive); |
1116 | 0 | currattr &= ~ATTR_FORMAT_DIRECTIVE; |
1117 | 0 | } |
1118 | 0 | else if (currattr & ATTR_INVALID_FORMAT_DIRECTIVE) |
1119 | 0 | { |
1120 | 0 | end_css_class (stream, class_invalid_format_directive); |
1121 | 0 | currattr &= ~ATTR_INVALID_FORMAT_DIRECTIVE; |
1122 | 0 | } |
1123 | 0 | if (!(currattr == 0)) |
1124 | 0 | abort (); |
1125 | | |
1126 | 0 | end_css_class (stream, class_text); |
1127 | 0 | ostream_write_str (stream, "\""); |
1128 | 0 | end_css_class (stream, class_string); |
1129 | 0 | end_css_class (stream, css_class); |
1130 | 0 | ostream_write_str (stream, "\n"); |
1131 | 0 | int currcol = 0; |
1132 | | /* INDENT-S. */ |
1133 | 0 | if (line_prefix != NULL) |
1134 | 0 | { |
1135 | 0 | ostream_write_str (stream, line_prefix); |
1136 | 0 | currcol = strlen (line_prefix); |
1137 | 0 | } |
1138 | 0 | begin_css_class (stream, css_class); |
1139 | 0 | if (indent) |
1140 | 0 | { |
1141 | 0 | ostream_write_mem (stream, " ", 8 - (currcol & 7)); |
1142 | 0 | currcol = (currcol + 8) & ~7; |
1143 | 0 | } |
1144 | 0 | begin_css_class (stream, class_string); |
1145 | 0 | ostream_write_str (stream, "\""); |
1146 | 0 | begin_css_class (stream, class_text); |
1147 | 0 | } |
1148 | | /* Change currattr so that it matches attributes[i]. */ |
1149 | 0 | if (attributes[i] != currattr) |
1150 | 0 | { |
1151 | | /* class_escape_sequence occurs inside class_format_directive |
1152 | | and class_invalid_format_directive, so clear it first. */ |
1153 | 0 | if (currattr & ATTR_ESCAPE_SEQUENCE) |
1154 | 0 | { |
1155 | 0 | end_css_class (stream, class_escape_sequence); |
1156 | 0 | currattr &= ~ATTR_ESCAPE_SEQUENCE; |
1157 | 0 | } |
1158 | 0 | if (~attributes[i] & currattr & ATTR_FORMAT_DIRECTIVE) |
1159 | 0 | { |
1160 | 0 | end_css_class (stream, class_format_directive); |
1161 | 0 | currattr &= ~ATTR_FORMAT_DIRECTIVE; |
1162 | 0 | } |
1163 | 0 | else if (~attributes[i] & currattr & ATTR_INVALID_FORMAT_DIRECTIVE) |
1164 | 0 | { |
1165 | 0 | end_css_class (stream, class_invalid_format_directive); |
1166 | 0 | currattr &= ~ATTR_INVALID_FORMAT_DIRECTIVE; |
1167 | 0 | } |
1168 | 0 | if (attributes[i] & ~currattr & ATTR_FORMAT_DIRECTIVE) |
1169 | 0 | { |
1170 | 0 | begin_css_class (stream, class_format_directive); |
1171 | 0 | currattr |= ATTR_FORMAT_DIRECTIVE; |
1172 | 0 | } |
1173 | 0 | else if (attributes[i] & ~currattr & ATTR_INVALID_FORMAT_DIRECTIVE) |
1174 | 0 | { |
1175 | 0 | begin_css_class (stream, class_invalid_format_directive); |
1176 | 0 | currattr |= ATTR_INVALID_FORMAT_DIRECTIVE; |
1177 | 0 | } |
1178 | | /* class_escape_sequence occurs inside class_format_directive |
1179 | | and class_invalid_format_directive, so set it last. */ |
1180 | 0 | if (attributes[i] & ~currattr & ATTR_ESCAPE_SEQUENCE) |
1181 | 0 | { |
1182 | 0 | begin_css_class (stream, class_escape_sequence); |
1183 | 0 | currattr |= ATTR_ESCAPE_SEQUENCE; |
1184 | 0 | } |
1185 | 0 | } |
1186 | 0 | ostream_write_mem (stream, &portion[i], 1); |
1187 | 0 | } |
1188 | | |
1189 | | /* Change currattr so that it becomes 0. */ |
1190 | 0 | if (currattr & ATTR_ESCAPE_SEQUENCE) |
1191 | 0 | { |
1192 | 0 | end_css_class (stream, class_escape_sequence); |
1193 | 0 | currattr &= ~ATTR_ESCAPE_SEQUENCE; |
1194 | 0 | } |
1195 | 0 | if (currattr & ATTR_FORMAT_DIRECTIVE) |
1196 | 0 | { |
1197 | 0 | end_css_class (stream, class_format_directive); |
1198 | 0 | currattr &= ~ATTR_FORMAT_DIRECTIVE; |
1199 | 0 | } |
1200 | 0 | else if (currattr & ATTR_INVALID_FORMAT_DIRECTIVE) |
1201 | 0 | { |
1202 | 0 | end_css_class (stream, class_invalid_format_directive); |
1203 | 0 | currattr &= ~ATTR_INVALID_FORMAT_DIRECTIVE; |
1204 | 0 | } |
1205 | 0 | if (!(currattr == 0)) |
1206 | 0 | abort (); |
1207 | | |
1208 | 0 | end_css_class (stream, class_text); |
1209 | 0 | ostream_write_str (stream, "\""); |
1210 | 0 | end_css_class (stream, class_string); |
1211 | 0 | end_css_class (stream, css_class); |
1212 | 0 | ostream_write_str (stream, "\n"); |
1213 | 0 | } |
1214 | | |
1215 | 0 | free (linebreaks); |
1216 | 0 | free (attributes); |
1217 | 0 | free (overrides); |
1218 | 0 | free (portion); |
1219 | |
|
1220 | 0 | s = es; |
1221 | 0 | # undef is_escape |
1222 | 0 | } |
1223 | 0 | while (*s); |
1224 | | |
1225 | 0 | if (fmtdirattr != NULL) |
1226 | 0 | free (fmtdirattr); |
1227 | 0 | if (fmtdir != NULL) |
1228 | 0 | free (fmtdir); |
1229 | |
|
1230 | 0 | #if HAVE_ICONV |
1231 | 0 | if (conv != (iconv_t)(-1)) |
1232 | 0 | iconv_close (conv); |
1233 | 0 | #endif |
1234 | 0 | } |
1235 | | |
1236 | | |
1237 | | static void |
1238 | | print_blank_line (ostream_t stream) |
1239 | 0 | { |
1240 | 0 | if (uniforum) |
1241 | 0 | { |
1242 | 0 | begin_css_class (stream, class_comment); |
1243 | 0 | ostream_write_str (stream, "#\n"); |
1244 | 0 | end_css_class (stream, class_comment); |
1245 | 0 | } |
1246 | 0 | else |
1247 | 0 | ostream_write_str (stream, "\n"); |
1248 | 0 | } |
1249 | | |
1250 | | |
1251 | | static void |
1252 | | message_print (const message_ty *mp, ostream_t stream, |
1253 | | const char *charset, size_t page_width, bool blank_line, |
1254 | | xerror_handler_ty xeh, bool debug) |
1255 | 0 | { |
1256 | | /* Separate messages with a blank line. Uniforum doesn't like blank |
1257 | | lines, so use an empty comment (unless there already is one). */ |
1258 | 0 | if (blank_line && (!uniforum |
1259 | 0 | || mp->comment == NULL |
1260 | 0 | || mp->comment->nitems == 0 |
1261 | 0 | || mp->comment->item[0][0] != '\0')) |
1262 | 0 | print_blank_line (stream); |
1263 | |
|
1264 | 0 | if (is_header (mp)) |
1265 | 0 | begin_css_class (stream, class_header); |
1266 | 0 | else if (mp->msgstr[0] == '\0') |
1267 | 0 | begin_css_class (stream, class_untranslated); |
1268 | 0 | else if (mp->is_fuzzy) |
1269 | 0 | begin_css_class (stream, class_fuzzy); |
1270 | 0 | else |
1271 | 0 | begin_css_class (stream, class_translated); |
1272 | |
|
1273 | 0 | begin_css_class (stream, class_comment); |
1274 | | |
1275 | | /* Print translator comment if available. */ |
1276 | 0 | message_print_comment (mp, stream); |
1277 | | |
1278 | | /* Print xgettext extracted comments. */ |
1279 | 0 | message_print_comment_dot (mp, stream); |
1280 | | |
1281 | | /* Print the file position comments. This will help a human who is |
1282 | | trying to navigate the sources. There is no problem of getting |
1283 | | repeated positions, because duplicates are checked for. */ |
1284 | 0 | message_print_comment_filepos (mp, stream, charset, uniforum, page_width); |
1285 | | |
1286 | | /* Print flag information in special comment. */ |
1287 | 0 | message_print_comment_flags (mp, stream, debug); |
1288 | | |
1289 | | /* Print the previous msgid. This helps the translator when the msgid has |
1290 | | only slightly changed. */ |
1291 | 0 | begin_css_class (stream, class_previous_comment); |
1292 | 0 | if (mp->prev_msgctxt != NULL) |
1293 | 0 | wrap (mp, stream, "#| ", 0, class_previous, "msgctxt", mp->prev_msgctxt, |
1294 | 0 | mp->do_wrap, page_width, charset, xeh); |
1295 | 0 | if (mp->prev_msgid != NULL) |
1296 | 0 | wrap (mp, stream, "#| ", 0, class_previous, "msgid", mp->prev_msgid, |
1297 | 0 | mp->do_wrap, page_width, charset, xeh); |
1298 | 0 | if (mp->prev_msgid_plural != NULL) |
1299 | 0 | wrap (mp, stream, "#| ", 0, class_previous, "msgid_plural", |
1300 | 0 | mp->prev_msgid_plural, mp->do_wrap, page_width, charset, xeh); |
1301 | 0 | end_css_class (stream, class_previous_comment); |
1302 | 0 | int extra_indent = (mp->prev_msgctxt != NULL || mp->prev_msgid != NULL |
1303 | 0 | || mp->prev_msgid_plural != NULL |
1304 | 0 | ? 3 |
1305 | 0 | : 0); |
1306 | |
|
1307 | 0 | end_css_class (stream, class_comment); |
1308 | | |
1309 | | /* Print each of the message components. Wrap them nicely so they |
1310 | | are as readable as possible. If there is no recorded msgstr for |
1311 | | this domain, emit an empty string. */ |
1312 | 0 | if (mp->msgctxt != NULL && !is_ascii_string (mp->msgctxt) |
1313 | 0 | && po_charset_canonicalize (charset) != po_charset_utf8) |
1314 | 0 | { |
1315 | 0 | char *warning_message = |
1316 | 0 | xasprintf (_("\ |
1317 | 0 | The following msgctxt contains non-ASCII characters.\n\ |
1318 | 0 | This will cause problems to translators who use a character encoding\n\ |
1319 | 0 | different from yours. Consider using a pure ASCII msgctxt instead.\n\ |
1320 | 0 | %s\n"), mp->msgctxt); |
1321 | 0 | xeh->xerror (CAT_SEVERITY_WARNING, mp, NULL, 0, 0, true, warning_message); |
1322 | 0 | free (warning_message); |
1323 | 0 | } |
1324 | 0 | if (!is_ascii_string (mp->msgid) |
1325 | 0 | && po_charset_canonicalize (charset) != po_charset_utf8) |
1326 | 0 | { |
1327 | 0 | char *warning_message = |
1328 | 0 | xasprintf (_("\ |
1329 | 0 | The following msgid contains non-ASCII characters.\n\ |
1330 | 0 | This will cause problems to translators who use a character encoding\n\ |
1331 | 0 | different from yours. Consider using a pure ASCII msgid instead.\n\ |
1332 | 0 | %s\n"), mp->msgid); |
1333 | 0 | xeh->xerror (CAT_SEVERITY_WARNING, mp, NULL, 0, 0, true, warning_message); |
1334 | 0 | free (warning_message); |
1335 | 0 | } |
1336 | 0 | if (mp->msgctxt != NULL) |
1337 | 0 | wrap (mp, stream, NULL, extra_indent, class_msgid, "msgctxt", mp->msgctxt, |
1338 | 0 | mp->do_wrap, page_width, charset, xeh); |
1339 | 0 | wrap (mp, stream, NULL, extra_indent, class_msgid, "msgid", mp->msgid, |
1340 | 0 | mp->do_wrap, page_width, charset, xeh); |
1341 | 0 | if (mp->msgid_plural != NULL) |
1342 | 0 | wrap (mp, stream, NULL, extra_indent, class_msgid, "msgid_plural", |
1343 | 0 | mp->msgid_plural, mp->do_wrap, page_width, charset, xeh); |
1344 | |
|
1345 | 0 | if (mp->msgid_plural == NULL) |
1346 | 0 | wrap (mp, stream, NULL, extra_indent, class_msgstr, "msgstr", mp->msgstr, |
1347 | 0 | mp->do_wrap, page_width, charset, xeh); |
1348 | 0 | else |
1349 | 0 | { |
1350 | 0 | unsigned int i; |
1351 | 0 | const char *p; |
1352 | 0 | for (p = mp->msgstr, i = 0; |
1353 | 0 | p < mp->msgstr + mp->msgstr_len; |
1354 | 0 | p += strlen (p) + 1, i++) |
1355 | 0 | { |
1356 | 0 | char prefix_buf[20]; |
1357 | 0 | sprintf (prefix_buf, "msgstr[%u]", i); |
1358 | 0 | wrap (mp, stream, NULL, extra_indent, class_msgstr, prefix_buf, p, |
1359 | 0 | mp->do_wrap, page_width, charset, xeh); |
1360 | 0 | } |
1361 | 0 | } |
1362 | |
|
1363 | 0 | if (is_header (mp)) |
1364 | 0 | end_css_class (stream, class_header); |
1365 | 0 | else if (mp->msgstr[0] == '\0') |
1366 | 0 | end_css_class (stream, class_untranslated); |
1367 | 0 | else if (mp->is_fuzzy) |
1368 | 0 | end_css_class (stream, class_fuzzy); |
1369 | 0 | else |
1370 | 0 | end_css_class (stream, class_translated); |
1371 | 0 | } |
1372 | | |
1373 | | |
1374 | | static void |
1375 | | message_print_obsolete (const message_ty *mp, ostream_t stream, |
1376 | | const char *charset, size_t page_width, bool blank_line, |
1377 | | xerror_handler_ty xeh, bool debug) |
1378 | 0 | { |
1379 | | /* If msgstr is the empty string we print nothing. */ |
1380 | 0 | if (mp->msgstr[0] == '\0') |
1381 | 0 | return; |
1382 | | |
1383 | | /* Separate messages with a blank line. Uniforum doesn't like blank |
1384 | | lines, so use an empty comment (unless there already is one). */ |
1385 | 0 | if (blank_line) |
1386 | 0 | print_blank_line (stream); |
1387 | |
|
1388 | 0 | begin_css_class (stream, class_obsolete); |
1389 | |
|
1390 | 0 | begin_css_class (stream, class_comment); |
1391 | | |
1392 | | /* Print translator comment if available. */ |
1393 | 0 | message_print_comment (mp, stream); |
1394 | | |
1395 | | /* Print xgettext extracted comments (normally empty). */ |
1396 | 0 | message_print_comment_dot (mp, stream); |
1397 | | |
1398 | | /* Print the file position comments (normally empty). */ |
1399 | 0 | message_print_comment_filepos (mp, stream, charset, uniforum, page_width); |
1400 | | |
1401 | | /* Print flag information in special comment. |
1402 | | Preserve only |
1403 | | - the fuzzy flag, because it is important for the translator when the |
1404 | | message becomes active again, |
1405 | | - the no-wrap flag, because we use mp->do_wrap below for the wrapping, |
1406 | | therefore further processing through 'msgcat' needs to use the same |
1407 | | value of do_wrap, |
1408 | | - the *-format flags, because the wrapping depends on these flags (see |
1409 | | 'Don't break inside format directives' comment), therefore further |
1410 | | processing through 'msgcat' needs to use the same values of is_format. |
1411 | | This is a trimmed-down variant of message_print_comment_flags. */ |
1412 | 0 | if (mp->is_fuzzy |
1413 | 0 | || has_significant_format_p (mp->is_format) |
1414 | 0 | || mp->do_wrap == no) |
1415 | 0 | { |
1416 | 0 | bool first_flag = true; |
1417 | |
|
1418 | 0 | ostream_write_str (stream, "#,"); |
1419 | |
|
1420 | 0 | if (mp->is_fuzzy) |
1421 | 0 | { |
1422 | 0 | ostream_write_str (stream, " fuzzy"); |
1423 | 0 | first_flag = false; |
1424 | 0 | } |
1425 | |
|
1426 | 0 | for (size_t i = 0; i < NFORMATS; i++) |
1427 | 0 | if (significant_format_p (mp->is_format[i])) |
1428 | 0 | { |
1429 | 0 | if (!first_flag) |
1430 | 0 | ostream_write_str (stream, ","); |
1431 | |
|
1432 | 0 | ostream_write_str (stream, " "); |
1433 | 0 | char *string = |
1434 | 0 | make_format_description_string (mp->is_format[i], |
1435 | 0 | format_language[i], debug); |
1436 | 0 | ostream_write_str (stream, string); |
1437 | 0 | free (string); |
1438 | 0 | first_flag = false; |
1439 | 0 | } |
1440 | |
|
1441 | 0 | if (mp->do_wrap == no) |
1442 | 0 | { |
1443 | 0 | if (!first_flag) |
1444 | 0 | ostream_write_str (stream, ","); |
1445 | |
|
1446 | 0 | ostream_write_str (stream, " "); |
1447 | 0 | ostream_write_str (stream, |
1448 | 0 | make_c_width_description_string (mp->do_wrap)); |
1449 | 0 | first_flag = false; |
1450 | 0 | } |
1451 | |
|
1452 | 0 | ostream_write_str (stream, "\n"); |
1453 | 0 | } |
1454 | | |
1455 | | /* Print the previous msgid. This helps the translator when the msgid has |
1456 | | only slightly changed. */ |
1457 | 0 | begin_css_class (stream, class_previous_comment); |
1458 | 0 | if (mp->prev_msgctxt != NULL) |
1459 | 0 | wrap (mp, stream, "#~| ", 0, class_previous, "msgctxt", mp->prev_msgctxt, |
1460 | 0 | mp->do_wrap, page_width, charset, xeh); |
1461 | 0 | if (mp->prev_msgid != NULL) |
1462 | 0 | wrap (mp, stream, "#~| ", 0, class_previous, "msgid", mp->prev_msgid, |
1463 | 0 | mp->do_wrap, page_width, charset, xeh); |
1464 | 0 | if (mp->prev_msgid_plural != NULL) |
1465 | 0 | wrap (mp, stream, "#~| ", 0, class_previous, "msgid_plural", |
1466 | 0 | mp->prev_msgid_plural, mp->do_wrap, page_width, charset, xeh); |
1467 | 0 | end_css_class (stream, class_previous_comment); |
1468 | 0 | int extra_indent = (mp->prev_msgctxt != NULL || mp->prev_msgid != NULL |
1469 | 0 | || mp->prev_msgid_plural != NULL |
1470 | 0 | ? 1 |
1471 | 0 | : 0); |
1472 | |
|
1473 | 0 | end_css_class (stream, class_comment); |
1474 | | |
1475 | | /* Print each of the message components. Wrap them nicely so they |
1476 | | are as readable as possible. */ |
1477 | 0 | if (mp->msgctxt != NULL && !is_ascii_string (mp->msgctxt) |
1478 | 0 | && po_charset_canonicalize (charset) != po_charset_utf8) |
1479 | 0 | { |
1480 | 0 | char *warning_message = |
1481 | 0 | xasprintf (_("\ |
1482 | 0 | The following msgctxt contains non-ASCII characters.\n\ |
1483 | 0 | This will cause problems to translators who use a character encoding\n\ |
1484 | 0 | different from yours. Consider using a pure ASCII msgctxt instead.\n\ |
1485 | 0 | %s\n"), mp->msgctxt); |
1486 | 0 | xeh->xerror (CAT_SEVERITY_WARNING, mp, NULL, 0, 0, true, warning_message); |
1487 | 0 | free (warning_message); |
1488 | 0 | } |
1489 | 0 | if (!is_ascii_string (mp->msgid) |
1490 | 0 | && po_charset_canonicalize (charset) != po_charset_utf8) |
1491 | 0 | { |
1492 | 0 | char *warning_message = |
1493 | 0 | xasprintf (_("\ |
1494 | 0 | The following msgid contains non-ASCII characters.\n\ |
1495 | 0 | This will cause problems to translators who use a character encoding\n\ |
1496 | 0 | different from yours. Consider using a pure ASCII msgid instead.\n\ |
1497 | 0 | %s\n"), mp->msgid); |
1498 | 0 | xeh->xerror (CAT_SEVERITY_WARNING, mp, NULL, 0, 0, true, warning_message); |
1499 | 0 | free (warning_message); |
1500 | 0 | } |
1501 | 0 | if (mp->msgctxt != NULL) |
1502 | 0 | wrap (mp, stream, "#~ ", extra_indent, class_msgid, "msgctxt", mp->msgctxt, |
1503 | 0 | mp->do_wrap, page_width, charset, xeh); |
1504 | 0 | wrap (mp, stream, "#~ ", extra_indent, class_msgid, "msgid", mp->msgid, |
1505 | 0 | mp->do_wrap, page_width, charset, xeh); |
1506 | 0 | if (mp->msgid_plural != NULL) |
1507 | 0 | wrap (mp, stream, "#~ ", extra_indent, class_msgid, "msgid_plural", |
1508 | 0 | mp->msgid_plural, mp->do_wrap, page_width, charset, xeh); |
1509 | |
|
1510 | 0 | if (mp->msgid_plural == NULL) |
1511 | 0 | wrap (mp, stream, "#~ ", extra_indent, class_msgstr, "msgstr", mp->msgstr, |
1512 | 0 | mp->do_wrap, page_width, charset, xeh); |
1513 | 0 | else |
1514 | 0 | { |
1515 | 0 | unsigned int i; |
1516 | 0 | const char *p; |
1517 | 0 | for (p = mp->msgstr, i = 0; |
1518 | 0 | p < mp->msgstr + mp->msgstr_len; |
1519 | 0 | p += strlen (p) + 1, i++) |
1520 | 0 | { |
1521 | 0 | char prefix_buf[20]; |
1522 | 0 | sprintf (prefix_buf, "msgstr[%u]", i); |
1523 | 0 | wrap (mp, stream, "#~ ", extra_indent, class_msgstr, prefix_buf, p, |
1524 | 0 | mp->do_wrap, page_width, charset, xeh); |
1525 | 0 | } |
1526 | 0 | } |
1527 | |
|
1528 | 0 | end_css_class (stream, class_obsolete); |
1529 | 0 | } |
1530 | | |
1531 | | |
1532 | | static void |
1533 | | msgdomain_list_print_po (msgdomain_list_ty *mdlp, ostream_t stream, |
1534 | | size_t page_width, xerror_handler_ty xeh, bool debug) |
1535 | 0 | { |
1536 | | /* Write out the messages for each domain. */ |
1537 | 0 | bool blank_line = false; |
1538 | 0 | for (size_t k = 0; k < mdlp->nitems; k++) |
1539 | 0 | { |
1540 | | /* If the first domain is the default, don't bother emitting |
1541 | | the domain name, because it is the default. */ |
1542 | 0 | if (!(k == 0 |
1543 | 0 | && strcmp (mdlp->item[k]->domain, MESSAGE_DOMAIN_DEFAULT) == 0)) |
1544 | 0 | { |
1545 | 0 | if (blank_line) |
1546 | 0 | print_blank_line (stream); |
1547 | 0 | begin_css_class (stream, class_keyword); |
1548 | 0 | ostream_write_str (stream, "domain"); |
1549 | 0 | end_css_class (stream, class_keyword); |
1550 | 0 | ostream_write_str (stream, " "); |
1551 | 0 | begin_css_class (stream, class_string); |
1552 | 0 | ostream_write_str (stream, "\""); |
1553 | 0 | begin_css_class (stream, class_text); |
1554 | 0 | ostream_write_str (stream, mdlp->item[k]->domain); |
1555 | 0 | end_css_class (stream, class_text); |
1556 | 0 | ostream_write_str (stream, "\""); |
1557 | 0 | end_css_class (stream, class_string); |
1558 | 0 | ostream_write_str (stream, "\n"); |
1559 | 0 | blank_line = true; |
1560 | 0 | } |
1561 | |
|
1562 | 0 | message_list_ty *mlp = mdlp->item[k]->messages; |
1563 | | |
1564 | | /* Search the header entry. */ |
1565 | 0 | const char *header = NULL; |
1566 | 0 | for (size_t j = 0; j < mlp->nitems; ++j) |
1567 | 0 | if (is_header (mlp->item[j]) && !mlp->item[j]->obsolete) |
1568 | 0 | { |
1569 | 0 | header = mlp->item[j]->msgstr; |
1570 | 0 | break; |
1571 | 0 | } |
1572 | | |
1573 | | /* Extract the charset name. */ |
1574 | 0 | const char *charset = "ASCII"; |
1575 | 0 | char *allocated_charset = NULL; |
1576 | 0 | if (header != NULL) |
1577 | 0 | { |
1578 | 0 | const char *charsetstr = c_strstr (header, "charset="); |
1579 | |
|
1580 | 0 | if (charsetstr != NULL) |
1581 | 0 | { |
1582 | 0 | charsetstr += strlen ("charset="); |
1583 | 0 | size_t len = strcspn (charsetstr, " \t\n"); |
1584 | |
|
1585 | 0 | allocated_charset = (char *) xmalloca (len + 1); |
1586 | 0 | memcpy (allocated_charset, charsetstr, len); |
1587 | 0 | allocated_charset[len] = '\0'; |
1588 | |
|
1589 | 0 | charset = allocated_charset; |
1590 | | |
1591 | | /* Treat the dummy default value as if it were absent. */ |
1592 | 0 | if (strcmp (charset, "CHARSET") == 0) |
1593 | 0 | charset = "ASCII"; |
1594 | 0 | } |
1595 | 0 | } |
1596 | | |
1597 | | /* Write out each of the messages for this domain. */ |
1598 | 0 | for (size_t j = 0; j < mlp->nitems; ++j) |
1599 | 0 | if (!mlp->item[j]->obsolete) |
1600 | 0 | { |
1601 | 0 | message_print (mlp->item[j], stream, charset, page_width, |
1602 | 0 | blank_line, xeh, debug); |
1603 | 0 | blank_line = true; |
1604 | 0 | } |
1605 | | |
1606 | | /* Write out each of the obsolete messages for this domain. */ |
1607 | 0 | for (size_t j = 0; j < mlp->nitems; ++j) |
1608 | 0 | if (mlp->item[j]->obsolete) |
1609 | 0 | { |
1610 | 0 | message_print_obsolete (mlp->item[j], stream, charset, page_width, |
1611 | 0 | blank_line, xeh, debug); |
1612 | 0 | blank_line = true; |
1613 | 0 | } |
1614 | |
|
1615 | 0 | if (allocated_charset != NULL) |
1616 | 0 | freea (allocated_charset); |
1617 | 0 | } |
1618 | 0 | } |
1619 | | |
1620 | | |
1621 | | /* Describes a PO file in .po syntax. */ |
1622 | | const struct catalog_output_format output_format_po = |
1623 | | { |
1624 | | msgdomain_list_print_po, /* print */ |
1625 | | false, /* requires_utf8 */ |
1626 | | true, /* requires_utf8_for_filenames_with_spaces */ |
1627 | | true, /* supports_color */ |
1628 | | true, /* supports_multiple_domains */ |
1629 | | true, /* supports_contexts */ |
1630 | | true, /* supports_plurals */ |
1631 | | true, /* sorts_obsoletes_to_end */ |
1632 | | false, /* alternative_is_po */ |
1633 | | false /* alternative_is_java_class */ |
1634 | | }; |