po.c (33176B)
1 /* This program is licensed under the terms of GNU GPL v3 or (at your option) 2 * any later version. Copyright (C) 2021-2024 Страхиња Радић. 3 * See the file LICENSE for exact copyright and license details. */ 4 5 #include <libgen.h> 6 #include <stdio.h> 7 #include <stdlib.h> 8 #include <string.h> 9 #include <sys/stat.h> 10 #include <time.h> 11 12 #define PO_C_GLOBALS 13 14 #include "version.h" 15 #include "util.h" 16 #include "po.h" 17 #include "draw.h" 18 #include "config.h" 19 20 extern const char* errors[]; 21 extern const char* flag_strings[]; 22 23 int print_error(const int code, const char* msg, ...); 24 25 char* 26 format_flags(char* buffer, const size_t max, const struct PoEntry* entry, 27 const int draw_box) 28 { 29 snprintf(buffer, max, "%s%c%c%c%c%s", draw_box ? "[ " : "", 30 entry->flags & FL_C_FORMAT ? 'C' : ' ', 31 entry->flags & FL_FUZZY ? 'F' : ' ', 32 entry->msgstr_count > 1 ? 'P' : ' ', 33 entry->flags & FL_NO_C_FORMAT ? 'N' : ' ', 34 draw_box ? " ]" : ""); 35 return buffer; 36 } 37 38 void 39 init_po_entry(struct PoEntry* entry) 40 { 41 entry->flags = FL_NONE; 42 entry->obsolete = 0; 43 entry->warning = 0; 44 entry->msgid = NULL; 45 entry->msgid_size = 0; 46 entry->msgid_len = 0; 47 entry->msgid_plural = NULL; 48 entry->msgid_plural_size = 0; 49 entry->msgid_plural_len = 0; 50 entry->plural_forms = NULL; 51 entry->msgstr = NULL; 52 entry->msgstr_count = 0; 53 entry->msgstr_size = NULL; 54 entry->msgstr_len = NULL; 55 entry->comments = NULL; 56 entry->comment_lines = 0; 57 entry->trans_comments = NULL; 58 entry->trans_comment_lines = 0; 59 entry->ref_comments = NULL; 60 entry->ref_comment_lines = 0; 61 } 62 63 void 64 free_po_entry(struct PoEntry* entry) 65 { 66 if (!entry) 67 return; 68 69 free(entry->msgid); 70 if (entry->msgstr) 71 { 72 uint32_t** msgstr = entry->msgstr; 73 while (msgstr < entry->msgstr + entry->msgstr_count) 74 free(*msgstr++); 75 free(entry->msgstr); 76 } 77 free(entry->msgstr_size); 78 free(entry->msgstr_len); 79 free(entry->msgid_plural); 80 free(entry->plural_forms); 81 82 if (entry->comments) 83 { 84 uint32_t** comment = entry->comments; 85 while (comment < entry->comments + entry->comment_lines) 86 free(*comment++); 87 free(entry->comments); 88 } 89 90 if (entry->trans_comments) 91 { 92 uint32_t** trans_comment = entry->trans_comments; 93 while (trans_comment 94 < entry->trans_comments + entry->trans_comment_lines) 95 free(*trans_comment++); 96 free(entry->trans_comments); 97 } 98 99 if (entry->ref_comments) 100 { 101 char** comment = entry->ref_comments; 102 while (comment < entry->ref_comments + entry->ref_comment_lines) 103 free(*comment++); 104 free(entry->ref_comments); 105 } 106 } 107 108 struct PoEntry* 109 set_flags(struct PoEntry* entry, const Flags flags) 110 { 111 if (!entry) 112 return NULL; 113 entry->flags = flags; 114 return entry; 115 } 116 117 struct PoEntry* 118 set_msgid(struct PoEntry* entry, const char* msgid, int append) 119 { 120 uint32_t umsgid[MAXMSGLINE]; 121 size_t msgid_size = 0; 122 if (!entry) 123 return NULL; 124 msgid_size = u8_string_to_unicode(umsgid, msgid, MAXMSGLINE); 125 return u32_set_msgid(entry, umsgid, msgid_size, append); 126 } 127 128 struct PoEntry* 129 set_msgid_plural(struct PoEntry* entry, const char* msgid_plural, int append) 130 { 131 uint32_t umsgid_plural[MAXMSGLINE]; 132 size_t msgid_plural_size = 0; 133 if (!entry) 134 return NULL; 135 msgid_plural_size 136 = u8_string_to_unicode(umsgid_plural, msgid_plural, MAXMSGLINE); 137 return u32_set_msgid_plural(entry, umsgid_plural, msgid_plural_size, 138 append); 139 } 140 141 struct PoEntry* 142 set_plural_forms(struct PoEntry* entry, const char* plural_forms, int append) 143 { 144 uint32_t uplural_forms[MAXMSGLINE]; 145 if (!entry) 146 return NULL; 147 u8_string_to_unicode(uplural_forms, plural_forms, MAXMSGLINE); 148 return u32_set_plural_forms(entry, uplural_forms, append); 149 } 150 151 struct PoEntry* 152 set_msgstr(struct PoEntry* entry, const char* msgstr, int msgstr_index, 153 int append, struct DrawState* state) 154 { 155 uint32_t umsgstr[MAXMSGLINE]; 156 if (!entry) 157 return NULL; 158 u8_string_to_unicode(umsgstr, msgstr, MAXMSGLINE); 159 return u32_set_msgstr(entry, umsgstr, msgstr_index, append, state); 160 } 161 162 struct PoEntry* 163 u32_set_msgid(struct PoEntry* entry, const uint32_t* msgid, size_t msgid_size, 164 int append) 165 { 166 size_t msgid_len = 0; 167 if (!entry) 168 return NULL; 169 if (msgid) 170 msgid_len = u32_strlen(msgid); 171 if (!entry->msgid) 172 { 173 entry->msgid_size = MSGDELTA; 174 entry->msgid_len = 0; 175 entry->msgid = calloc(entry->msgid_size, sizeof(uint32_t)); 176 } 177 else if (entry->msgid_len + msgid_len >= entry->msgid_size) 178 { 179 entry->msgid_size += MSGDELTA; 180 entry->msgid = realloc(entry->msgid, 181 entry->msgid_size * sizeof(uint32_t)); 182 } 183 entry->msgid_len += msgid_len; 184 if (!entry->msgid) 185 return NULL; 186 if (append) 187 u32_strncat(entry->msgid, msgid, entry->msgid_size); 188 else 189 u32_strncpy(entry->msgid, msgid, entry->msgid_size); 190 return entry; 191 } 192 193 struct PoEntry* 194 u32_set_msgid_plural(struct PoEntry* entry, const uint32_t* msgid_plural, 195 size_t msgid_plural_size, int append) 196 { 197 size_t msgid_plural_len = 0; 198 if (!entry) 199 return NULL; 200 if (msgid_plural) 201 msgid_plural_len = u32_strlen(msgid_plural); 202 if (!entry->msgid_plural) 203 { 204 entry->msgid_plural_size = MSGDELTA; 205 entry->msgid_plural_len = 0; 206 entry->msgid_plural 207 = calloc(entry->msgid_plural_size, sizeof(uint32_t)); 208 } 209 else if (entry->msgid_plural_size + msgid_plural_len 210 >= entry->msgid_plural_size) 211 { 212 entry->msgid_plural_size += MSGDELTA; 213 entry->msgid_plural = realloc(entry->msgid_plural, 214 entry->msgid_plural_size * sizeof(uint32_t)); 215 } 216 entry->msgid_plural_len += msgid_plural_len; 217 if (!entry->msgid_plural) 218 return NULL; 219 if (append) 220 u32_strncat(entry->msgid_plural, msgid_plural, 221 entry->msgid_plural_size); 222 else 223 u32_strncpy(entry->msgid_plural, msgid_plural, 224 entry->msgid_plural_size); 225 return entry; 226 } 227 228 struct PoEntry* 229 u32_set_plural_forms(struct PoEntry* entry, const uint32_t* plural_forms, 230 int append) 231 { 232 if (!entry) 233 return NULL; 234 if (!entry->plural_forms) 235 { 236 entry->plural_forms = calloc(MAXMSGLINE, sizeof(uint32_t)); 237 if (!entry->plural_forms) 238 return NULL; 239 } 240 if (append) 241 u32_strncat(entry->plural_forms, plural_forms, MAXMSGLINE); 242 else 243 u32_strncpy(entry->plural_forms, plural_forms, MAXMSGLINE); 244 return entry; 245 } 246 247 struct PoEntry* 248 u32_set_msgstr(struct PoEntry* entry, const uint32_t* msgstr, int msgstr_index, 249 int append, struct DrawState* state) 250 { 251 size_t msgstr_len = 0; 252 if (!entry) 253 return NULL; 254 if (msgstr) 255 msgstr_len = u32_strlen(msgstr); 256 if (entry->msgstr_count == 0 || msgstr_index + 1 > entry->msgstr_count) 257 { 258 uint32_t** newmsgstr = NULL; 259 size_t* newmsgstr_size = NULL; 260 size_t* newmsgstr_len = NULL; 261 entry->msgstr_count = msgstr_index + 1; 262 if (!entry->msgstr) 263 { 264 newmsgstr = calloc(entry->msgstr_count, 265 sizeof(uint32_t*)); 266 newmsgstr_size 267 = calloc(entry->msgstr_count, sizeof(size_t)); 268 newmsgstr_len 269 = calloc(entry->msgstr_count, sizeof(size_t)); 270 /* when the .po file is not a standard text file (no LF 271 * at end) */ 272 if (state->untranslated_count > 0) 273 state->untranslated_count--; 274 } 275 else 276 { 277 newmsgstr = realloc(entry->msgstr, 278 entry->msgstr_count * sizeof(uint32_t*)); 279 newmsgstr_size = realloc(entry->msgstr_size, 280 entry->msgstr_count * sizeof(size_t)); 281 newmsgstr_len = realloc(entry->msgstr_len, 282 entry->msgstr_count * sizeof(size_t)); 283 } 284 if (!newmsgstr || !newmsgstr_size || !newmsgstr_len) 285 { 286 free(newmsgstr); 287 free(newmsgstr_len); 288 free(newmsgstr_size); 289 return NULL; 290 } 291 *(newmsgstr + msgstr_index) = NULL; 292 entry->msgstr = newmsgstr; 293 entry->msgstr_size = newmsgstr_size; 294 entry->msgstr_len = newmsgstr_len; 295 } 296 if (!*(entry->msgstr + msgstr_index)) 297 { 298 *(entry->msgstr_size + msgstr_index) = MSGDELTA; 299 *(entry->msgstr_len + msgstr_index) = 0; 300 *(entry->msgstr + msgstr_index) 301 = calloc(*(entry->msgstr_size + msgstr_index), 302 sizeof(uint32_t)); 303 } 304 else if (*(entry->msgstr_len + msgstr_index) + msgstr_len 305 >= *(entry->msgstr_size + msgstr_index)) 306 { 307 *(entry->msgstr_size + msgstr_index) += MSGDELTA; 308 *(entry->msgstr + msgstr_index) 309 = realloc(*(entry->msgstr + msgstr_index), 310 *(entry->msgstr_size + msgstr_index) 311 * sizeof(uint32_t)); 312 } 313 *(entry->msgstr_len + msgstr_index) += msgstr_len; 314 if (!*(entry->msgstr + msgstr_index)) 315 return NULL; 316 if (append) 317 u32_strncat(*(entry->msgstr + msgstr_index), msgstr, 318 *(entry->msgstr_size + msgstr_index)); 319 else 320 u32_strncpy(*(entry->msgstr + msgstr_index), msgstr, 321 *(entry->msgstr_size + msgstr_index)); 322 return entry; 323 } 324 325 struct PoEntry* 326 u32_add_comment(struct PoEntry* entry, const uint32_t* comment) 327 { 328 uint32_t** comments = NULL; 329 uint32_t* new_line = NULL; 330 size_t len = 0; 331 332 if (!entry) 333 return NULL; 334 335 entry->comment_lines++; 336 if (!entry->comments) 337 comments = calloc(entry->comment_lines, sizeof(uint32_t*)); 338 else 339 comments = realloc(entry->comments, 340 entry->comment_lines * sizeof(uint32_t*)); 341 if (!comments) 342 return NULL; 343 entry->comments = comments; 344 345 len = u32_strlen(comment) + 1; 346 new_line = calloc(len, sizeof(uint32_t)); 347 if (!new_line) 348 return NULL; 349 entry->comments[entry->comment_lines - 1] = new_line; 350 u32_strncpy(new_line, comment, len); 351 return entry; 352 } 353 354 struct PoEntry* 355 u32_add_trans_comment(struct PoEntry* entry, const uint32_t* trans_comment) 356 { 357 uint32_t** trans_comments = NULL; 358 uint32_t* new_line = NULL; 359 size_t len = 0; 360 361 if (!entry) 362 return NULL; 363 364 entry->trans_comment_lines++; 365 if (!entry->trans_comments) 366 trans_comments 367 = calloc(entry->trans_comment_lines, sizeof(uint32_t*)); 368 else 369 trans_comments = realloc(entry->trans_comments, 370 entry->trans_comment_lines * sizeof(uint32_t*)); 371 if (!trans_comments) 372 return NULL; 373 entry->trans_comments = trans_comments; 374 375 len = u32_strlen(trans_comment) + 1; 376 new_line = calloc(len, sizeof(uint32_t)); 377 if (!new_line) 378 return NULL; 379 entry->trans_comments[entry->trans_comment_lines - 1] = new_line; 380 u32_strncpy(new_line, trans_comment, len); 381 return entry; 382 } 383 384 struct PoEntry* 385 add_ref_comment(struct PoEntry* entry, const char* comment) 386 { 387 char** ref_comments = NULL; 388 char* new_line = NULL; 389 size_t len = 0; 390 391 if (!entry) 392 return NULL; 393 394 entry->ref_comment_lines++; 395 396 if (!entry->ref_comments) 397 ref_comments = calloc(entry->ref_comment_lines, sizeof(char*)); 398 else 399 ref_comments = realloc(entry->ref_comments, 400 entry->ref_comment_lines * sizeof(char*)); 401 if (!ref_comments) 402 return NULL; 403 entry->ref_comments = ref_comments; 404 405 len = strlen(comment) + 1; 406 new_line = calloc(len, 1); 407 if (!new_line) 408 return NULL; 409 entry->ref_comments[entry->ref_comment_lines - 1] = new_line; 410 strncpy(new_line, comment, len); 411 return entry; 412 } 413 414 int 415 load_file(struct DrawState* dstate, long* lineno, long* col) 416 { 417 struct PoEntry* newchunk = calloc(ALLOC_DELTA, sizeof(struct PoEntry)); 418 struct PoEntry* entry = NULL; 419 struct PoEntry* current = NULL; 420 ParseState pstate = PS_NONE; 421 #ifndef PLURAL_STRING 422 int has_plural_msgid = 0; 423 #endif 424 FILE* input = NULL; 425 char input_line[MAXBUFLINE]; 426 /* lines to tolerate the lack of msgid */ 427 int msgid_counter = PO_DETECTION_LINES; 428 int msgstr_index = -1; 429 size_t non_obsolete_sofar = 0; 430 431 dstate->msgid_size = 0; 432 if (!newchunk) 433 return LOAD_ERR_CANT_ALLOC; 434 435 dstate->msgid_count = 0; 436 dstate->msgid_size = ALLOC_DELTA; 437 dstate->entries = newchunk; 438 entry = dstate->entries; 439 dstate->untranslated_count++; 440 441 if (!(input = fopen(dstate->filename, "rt"))) 442 return LOAD_ERR_CANT_OPEN_FILE; 443 while (!feof(input)) 444 { 445 if (!fgets(input_line, MAXBUFLINE, input)) 446 continue; 447 448 char* eol = strchr(input_line, '\n'); 449 if (eol) 450 *eol = 0; 451 452 int result; 453 result = parse_po_line(input_line, dstate, &entry, &pstate, col, 454 &msgstr_index); 455 456 #ifndef PLURAL_STRING 457 if (entry && entry->msgid_plural) 458 has_plural_msgid = 1; 459 #endif 460 461 if (result == PARSE_ERR_NONE) 462 { 463 if (pstate & PS_MSGID) 464 msgid_counter = -1; 465 else if (msgid_counter != -1) 466 msgid_counter--; 467 468 if (msgid_counter == 0) 469 return LOAD_ERR_NOT_PO_FILE; 470 } 471 else if (result == PARSE_ERR_SYNTAX) 472 snprintf(dstate->error, MAXBUFLINE, 473 "%s:%ld:%ld: Syntax error", dstate->filename, 474 *lineno, *col); 475 else 476 { 477 free(dstate->entries); 478 dstate->entries = NULL; 479 return LOAD_PARSE_ERR(result); 480 } 481 (*lineno)++; 482 } 483 fclose(input); 484 485 /* when the .po file is not a standard text file (no LF at end) */ 486 if (dstate->msgid_count > 1) 487 { 488 if (entry) 489 { 490 if (entry->msgid) 491 /* regular entry */ 492 dstate->msgid_count++; 493 else if (entry->comment_lines > 0) 494 /* obsolete entry */ 495 entry->obsolete = 1; 496 } 497 #ifndef PLURAL_STRING 498 if (has_plural_msgid && !dstate->entries->plural_forms) 499 STRLCPY(errors[ERR_NO_PLURAL_FORMS], dstate->error, 500 MAXBUFLINE); 501 #endif 502 } 503 504 /* file has < PO_DETECTION_LINES */ 505 if (msgid_counter > 0) 506 return LOAD_ERR_NOT_PO_FILE; 507 508 dstate->first_shown_msgid = dstate->msgid_count > 0 ? 1 : 0; 509 update_statistics(dstate->entries, dstate, entry); 510 dstate->real_msgid_count = dstate->msgid_count + dstate->obsolete_count; 511 512 current = dstate->entries; 513 dstate->obsolete_at_end_count = 0; 514 non_obsolete_sofar = 0; 515 while (current != dstate->entries + dstate->real_msgid_count) 516 { 517 if (!current->obsolete) 518 non_obsolete_sofar++; 519 else if (non_obsolete_sofar == dstate->msgid_count) 520 dstate->obsolete_at_end_count++; 521 if (!current->obsolete && current->msgid && current->msgstr) 522 update_warning(current->msgid, *current->msgstr, 523 ¤t->warning); 524 current++; 525 } 526 527 return LOAD_ERR_NONE; 528 } 529 530 int 531 save_file(const struct PoEntry* entries, const size_t real_msgid_count, 532 const char* filename, const int nplurals) 533 { 534 const struct PoEntry* current = entries; 535 FILE* output = NULL; 536 #ifdef CREATE_BACKUPS 537 char* backup_filename = NULL; 538 #endif 539 struct stat st; 540 int result = SAVE_ERR_NONE; 541 char* real_filename = NULL; 542 size_t rfn_len = 0; 543 Flags flags; 544 545 if (!(real_filename = calloc(MAXPATH, 1))) 546 return SAVE_ERR_CANT_ALLOC; 547 #ifdef CREATE_BACKUPS 548 if (!(backup_filename = calloc(MAXPATH, 1))) 549 { 550 free(real_filename); 551 return SAVE_ERR_CANT_ALLOC; 552 } 553 #endif 554 555 #ifdef CREATE_BACKUPS 556 if (lstat(filename, &st) != -1) 557 { 558 if (S_ISLNK(st.st_mode)) 559 { 560 rfn_len = readlink(filename, real_filename, MAXPATH); 561 if (rfn_len == -1) 562 { 563 result = SAVE_ERR_CANT_READLINK; 564 goto save_loop_entries_done; 565 } 566 real_filename[rfn_len] = 0; 567 if (stat(real_filename, &st) == -1) 568 { 569 result = SAVE_ERR_CANT_STAT_FILE; 570 goto save_loop_entries_done; 571 } 572 } 573 STRLCPY(filename, backup_filename, MAXPATH); 574 STRLCAT(backup_suffix, backup_filename, MAXPATH); 575 if (rename(rfn_len > 0 ? real_filename : filename, 576 backup_filename) 577 == -1) 578 { 579 result = SAVE_ERR_CANT_MOVE_FILE; 580 goto save_loop_entries_done; 581 } 582 } 583 else 584 { 585 result = SAVE_ERR_CANT_STAT_FILE; 586 goto save_loop_entries_done; 587 } 588 #endif 589 if (!(output = fopen(rfn_len > 0 ? real_filename : filename, "wt"))) 590 { 591 result = SAVE_ERR_CANT_OPEN_FILE; 592 goto save_loop_entries_done; 593 } 594 595 #ifdef CREATE_BACKUPS 596 if (rfn_len > 0) 597 { 598 if (unlink(filename) == -1) 599 { 600 result = SAVE_ERR_CANT_UNLINK; 601 goto save_loop_entries_done; 602 } 603 if (symlink(real_filename, filename) == -1) 604 { 605 result = SAVE_ERR_CANT_SYMLINK; 606 goto save_loop_entries_done; 607 } 608 } 609 chmod(rfn_len > 0 ? real_filename : filename, st.st_mode); 610 #endif 611 612 save_loop_entries_start: 613 if (current >= entries + real_msgid_count) 614 goto save_loop_entries_done; 615 616 if (current->obsolete) 617 goto save_flags; 618 619 save_comments: 620 if (current->comment_lines > 0) 621 { 622 char* u8_comment = NULL; 623 for (size_t c = 0; c < current->comment_lines; c++) 624 { 625 size_t len = u32_strlen(*(current->comments + c)); 626 size_t size = len * UTF8REPMAX + 1; 627 if (!u8_comment) 628 { 629 u8_comment = malloc(size); 630 memset(u8_comment, 0, size); 631 } 632 else 633 u8_comment = realloc(u8_comment, size); 634 if (!u8_comment) 635 { 636 result = SAVE_ERR_CANT_ALLOC; 637 goto save_loop_entries_done; 638 } 639 unicode_string_to_u8(u8_comment, size, 640 *(current->comments + c), len); 641 fprintf(output, "#%s%s\n", current->obsolete ? "~" : "", 642 u8_comment); 643 } 644 free(u8_comment); 645 } 646 if (current->trans_comment_lines > 0) 647 { 648 char* u8_trans_comment = NULL; 649 for (size_t tc = 0; tc < current->trans_comment_lines; tc++) 650 { 651 size_t len 652 = u32_strlen(*(current->trans_comments + tc)); 653 size_t size = len * UTF8REPMAX + 1; 654 655 if (!u8_trans_comment) 656 { 657 u8_trans_comment = malloc(size); 658 memset(u8_trans_comment, 0, size); 659 } 660 else 661 u8_trans_comment 662 = realloc(u8_trans_comment, size); 663 if (!u8_trans_comment) 664 { 665 result = SAVE_ERR_CANT_ALLOC; 666 goto save_loop_entries_done; 667 } 668 unicode_string_to_u8(u8_trans_comment, size, 669 *(current->trans_comments + tc), len); 670 fprintf(output, "#.%s%s\n", 671 u8_trans_comment[0] == 0 ? "" : " ", 672 u8_trans_comment); 673 } 674 free(u8_trans_comment); 675 } 676 if (current->ref_comment_lines > 0) 677 { 678 for (size_t rc = 0; rc < current->ref_comment_lines; rc++) 679 fprintf(output, "#: %s\n", 680 *(current->ref_comments + rc)); 681 } 682 if (current->obsolete) 683 goto save_obsolete_check; 684 685 save_flags: 686 flags = current->flags; 687 if (current->flags != FL_NONE) 688 fprintf(output, "#, "); 689 while (flags != FL_NONE) 690 { 691 if (flags & FL_C_FORMAT) 692 { 693 flags &= ~FL_C_FORMAT; 694 fprintf(output, "%s", flag_strings[FL_C_FORMAT]); 695 } 696 else if (flags & FL_NO_C_FORMAT) 697 { 698 flags &= ~FL_NO_C_FORMAT; 699 fprintf(output, "%s", flag_strings[FL_NO_C_FORMAT]); 700 } 701 else if (flags & FL_FUZZY) 702 { 703 flags &= ~FL_FUZZY; 704 fprintf(output, "%s", flag_strings[FL_FUZZY]); 705 } 706 else 707 flags = FL_NONE; /* see po.h */ 708 709 if (flags != FL_NONE) 710 fprintf(output, ", "); 711 } 712 if (current->flags != FL_NONE) 713 fprintf(output, "\n"); 714 if (current->obsolete) 715 goto save_comments; 716 717 save_obsolete_check: 718 if (current->obsolete) 719 { 720 if (current < entries + real_msgid_count - 1) 721 fprintf(output, "\n"); 722 current++; 723 goto save_loop_entries_start; 724 } 725 726 if (current->msgid) 727 { 728 size_t num_lines = u32_lines_in_string(current->msgid, 1, 729 SAVE_WRAP_WIDTH); 730 731 if ((num_lines > 1) 732 && (u32_strlen(current->msgid) 733 > SAVE_MULTILINE_TRESHOLD)) 734 { 735 const uint32_t* pumsgid = current->msgid; 736 737 fprintf(output, "msgid \"\"\n"); 738 739 while (*pumsgid) 740 { 741 size_t len = 0; 742 size_t size; 743 char* u8_msgid = NULL; 744 const uint32_t* old_pumsgid = pumsgid; 745 746 len = u32_next_line(current->msgid, &pumsgid, 1, 747 SAVE_WRAP_WIDTH); 748 if (len == 0) 749 continue; 750 751 size = len * UTF8REPMAX + 1; 752 753 u8_msgid = malloc(size); 754 if (!u8_msgid) 755 { 756 result = SAVE_ERR_CANT_ALLOC; 757 goto save_loop_entries_done; 758 } 759 760 memset(u8_msgid, 0, size); 761 762 unicode_string_to_u8(u8_msgid, size, 763 old_pumsgid, len); 764 fprintf(output, "\"%s\"\n", u8_msgid); 765 free(u8_msgid); 766 } 767 } 768 else 769 { 770 size_t size; 771 char* u8_msgid = NULL; 772 773 size = current->msgid_size * UTF8REPMAX + 1; 774 u8_msgid = malloc(size); 775 if (!u8_msgid) 776 { 777 result = SAVE_ERR_CANT_ALLOC; 778 goto save_loop_entries_done; 779 } 780 781 memset(u8_msgid, 0, size); 782 783 unicode_string_to_u8(u8_msgid, size, current->msgid, 784 current->msgid_size); 785 fprintf(output, "msgid \"%s\"\n", u8_msgid); 786 free(u8_msgid); 787 } 788 } 789 else 790 fprintf(output, "msgid \"\"\n"); 791 792 if (current->msgid_plural) 793 { 794 size_t num_lines = u32_lines_in_string(current->msgid_plural, 1, 795 SAVE_WRAP_WIDTH); 796 797 if ((num_lines > 1) 798 && (u32_strlen(current->msgid_plural) 799 > SAVE_MULTILINE_TRESHOLD)) 800 { 801 const uint32_t* pumsgid = current->msgid_plural; 802 803 fprintf(output, "msgid_plural \"\"\n"); 804 805 while (*pumsgid) 806 { 807 size_t len = 0; 808 char* u8_msgid = NULL; 809 const uint32_t* old_pumsgid = pumsgid; 810 size_t size; 811 812 len = u32_next_line(current->msgid_plural, 813 &pumsgid, 1, SAVE_WRAP_WIDTH); 814 if (len == 0) 815 continue; 816 817 size = len * UTF8REPMAX + 1; 818 u8_msgid = malloc(size); 819 if (!u8_msgid) 820 { 821 result = SAVE_ERR_CANT_ALLOC; 822 goto save_loop_entries_done; 823 } 824 825 memset(u8_msgid, 0, size); 826 827 unicode_string_to_u8(u8_msgid, size, 828 old_pumsgid, len); 829 fprintf(output, "\"%s\"\n", u8_msgid); 830 free(u8_msgid); 831 } 832 } 833 else 834 { 835 char* u8_msgid = NULL; 836 size_t size; 837 size_t msgid_plural_len; 838 839 size = current->msgid_plural_size * UTF8REPMAX + 1; 840 u8_msgid = malloc(size); 841 if (!u8_msgid) 842 { 843 result = SAVE_ERR_CANT_ALLOC; 844 goto save_loop_entries_done; 845 } 846 847 memset(u8_msgid, 9, size); 848 849 msgid_plural_len = u32_strlen(current->msgid_plural); 850 unicode_string_to_u8(u8_msgid, size, 851 current->msgid_plural, msgid_plural_len); 852 fprintf(output, "msgid_plural \"%s\"\n", u8_msgid); 853 free(u8_msgid); 854 } 855 } 856 857 if (current->msgstr_count == 1) 858 { 859 size_t num_lines = u32_lines_in_string(*current->msgstr, 1, 860 SAVE_WRAP_WIDTH); 861 int first_entry = (current == entries) && !current->msgid; 862 863 if ((num_lines > 1) 864 && (first_entry 865 || (u32_strlen(*current->msgstr) 866 > SAVE_MULTILINE_TRESHOLD))) 867 { 868 const uint32_t* pumsgstr = *current->msgstr; 869 int seen_generator = 0; 870 #ifdef PLURAL_STRING 871 int seen_plural_forms = 0; 872 #endif 873 char now[MAXDATEBUF]; 874 time_t t = time(NULL); 875 struct tm* now_tm = localtime(&t); 876 877 strftime(now, MAXDATEBUF, "%F %H:%M%z", now_tm); 878 fprintf(output, "msgstr \"\"\n"); 879 880 while (*pumsgstr) 881 { 882 size_t len = 0; 883 size_t size; 884 char* u8_msgstr = NULL; 885 const uint32_t* old_pumsgstr = pumsgstr; 886 887 len = u32_next_line(*current->msgstr, &pumsgstr, 888 1, 889 ((first_entry && WRAP_FIRST_MSGSTR) 890 || !first_entry) 891 ? SAVE_WRAP_WIDTH 892 : 0); 893 894 if (len == 0) 895 continue; 896 897 size = len * UTF8REPMAX + 1; 898 u8_msgstr = malloc(size); 899 if (!u8_msgstr) 900 { 901 result = SAVE_ERR_CANT_ALLOC; 902 goto save_loop_entries_done; 903 } 904 905 memset(u8_msgstr, 0, size); 906 907 unicode_string_to_u8(u8_msgstr, size, 908 old_pumsgstr, len); 909 if (first_entry 910 && starts_with(u8_msgstr, 911 "PO-Revision-Date: ")) 912 fprintf(output, 913 "\"PO-Revision-Date: %s\\n\"\n", 914 now); 915 else if (first_entry 916 && starts_with(u8_msgstr, 917 "X-Generator: ")) 918 { 919 seen_generator = 1; 920 fprintf(output, 921 "\"X-Generator: poe %s\\n\"\n", 922 VERSION); 923 } 924 #ifdef PLURAL_STRING 925 else if (first_entry 926 && starts_with(u8_msgstr, 927 "Plural-Forms: ")) 928 { 929 seen_plural_forms = 1; 930 fprintf(output, 931 "\"Plural-Forms: %s\\n\"\n", 932 PLURAL_STRING); 933 } 934 #endif 935 else 936 fprintf(output, "\"%s\"\n", u8_msgstr); 937 free(u8_msgstr); 938 } 939 #ifdef PLURAL_STRING 940 if (first_entry && !seen_plural_forms) 941 fprintf(output, "\"Plural-Forms: %s\\n\"\n", 942 PLURAL_STRING); 943 #endif 944 if (first_entry && !seen_generator) 945 fprintf(output, "\"X-Generator: poe %s\\n\"\n", 946 VERSION); 947 } 948 else if (current->msgid_plural 949 && (!*current->msgstr || !**current->msgstr)) 950 for (int i = 0; i < nplurals; i++) 951 fprintf(output, "msgstr[%d] \"\"\n", i); 952 else 953 { 954 size_t size = MAXMSGLINE * UTF8REPMAX + 1; 955 char u8_msgstr[size]; 956 size_t msgstr_len; 957 958 msgstr_len = u32_strlen(*current->msgstr); 959 unicode_string_to_u8(u8_msgstr, size, *current->msgstr, 960 msgstr_len); 961 fprintf(output, "msgstr \"%s\"\n", u8_msgstr); 962 } 963 } 964 else if (current->msgstr_count > 0) 965 { 966 for (size_t m = 0; m < current->msgstr_count; m++) 967 { 968 size_t num_lines = u32_lines_in_string(*current->msgstr, 969 1, SAVE_WRAP_WIDTH); 970 971 if ((num_lines > 1) 972 && (u32_strlen(*(current->msgstr + m)) 973 > SAVE_MULTILINE_TRESHOLD)) 974 { 975 const uint32_t* pumsgstr 976 = *(current->msgstr + m); 977 978 fprintf(output, "msgstr[%ld] \"\"\n", m); 979 980 while (*pumsgstr) 981 { 982 size_t len = 0; 983 size_t size; 984 char* u8_msgstr = NULL; 985 const uint32_t* old_pumsgstr = pumsgstr; 986 987 len = u32_next_line(*(current->msgstr 988 + m), 989 &pumsgstr, 1, SAVE_WRAP_WIDTH); 990 if (len == 0) 991 continue; 992 993 size = len * UTF8REPMAX + 1; 994 u8_msgstr = malloc(size); 995 if (!u8_msgstr) 996 { 997 result = SAVE_ERR_CANT_ALLOC; 998 goto save_loop_entries_done; 999 } 1000 1001 memset(u8_msgstr, 0, size); 1002 1003 unicode_string_to_u8(u8_msgstr, size, 1004 old_pumsgstr, len); 1005 fprintf(output, "\"%s\"\n", u8_msgstr); 1006 free(u8_msgstr); 1007 } 1008 } 1009 else 1010 { 1011 size_t size = MAXMSGLINE * UTF8REPMAX + 1; 1012 char u8_msgstr[size]; 1013 size_t msgstr_len; 1014 msgstr_len = u32_strlen(*(current->msgstr + m)); 1015 unicode_string_to_u8(u8_msgstr, size, 1016 *(current->msgstr + m), msgstr_len); 1017 fprintf(output, "msgstr[%ld] \"%s\"\n", m, 1018 u8_msgstr); 1019 } 1020 } 1021 } 1022 else 1023 { 1024 if (current->msgid_plural) 1025 for (int i = 0; i < nplurals; i++) 1026 fprintf(output, "msgstr[%d] \"\"\n", i); 1027 else 1028 fprintf(output, "msgstr \"\"\n"); 1029 } 1030 1031 if (current < entries + real_msgid_count - 1) 1032 fprintf(output, "\n"); 1033 current++; 1034 goto save_loop_entries_start; 1035 save_loop_entries_done: 1036 fclose(output); 1037 #ifdef CREATE_BACKUPS 1038 free(backup_filename); 1039 #endif 1040 free(real_filename); 1041 1042 return result; 1043 } 1044 1045 int 1046 parse_po_line(const char* line, struct DrawState* dstate, 1047 struct PoEntry** current_entry, ParseState* pstate, long* col, 1048 int* msgstr_index) 1049 { 1050 size_t i = 0; 1051 *col = 1; 1052 const char* pline = line; 1053 char token[MAXBUFLINE]; 1054 *token = 0; 1055 char* ptoken = token; 1056 1057 if (!line) 1058 return PARSE_ERR_INVAL; 1059 1060 if (starts_with(line, "msgid ")) 1061 { 1062 size_t len = strlen("msgid "); 1063 *pstate |= PS_MSGID; 1064 pline += len; 1065 *col += len; 1066 } 1067 else if (starts_with(line, "msgid_plural ")) 1068 { 1069 size_t len = strlen("msgid_plural "); 1070 *pstate &= ~PS_MSGID; 1071 *pstate |= PS_MSGID_PLURAL; 1072 pline += len; 1073 *col += len; 1074 } 1075 else if (starts_with(line, "msgstr ")) 1076 { 1077 size_t len = strlen("msgstr "); 1078 *pstate &= ~PS_MSGID; 1079 *pstate &= ~PS_MSGID_PLURAL; 1080 *pstate |= PS_MSGSTR; 1081 pline += len; 1082 *col += len; 1083 } 1084 else if (starts_with(line, "msgstr[")) 1085 { 1086 size_t len = strlen("msgstr["); 1087 *pstate &= ~PS_MSGID; 1088 *pstate &= ~PS_MSGID_PLURAL; 1089 *pstate |= PS_MSGSTR_ARRAY; 1090 (*msgstr_index)++; 1091 pline += len; 1092 *col += len; 1093 1094 while (*pline >= '0' && *pline <= '9') 1095 { 1096 pline++; 1097 (*col)++; 1098 } 1099 1100 if (starts_with(pline, "] ")) 1101 { 1102 pline += 2; 1103 (*col) += 2; 1104 } 1105 else 1106 return PARSE_ERR_SYNTAX; 1107 } 1108 else if (*line == 0) 1109 { 1110 *pstate &= ~PS_MSGID; 1111 *pstate &= ~PS_MSGID_PLURAL; 1112 *pstate &= ~PS_MSGSTR; 1113 *pstate &= ~PS_MSGSTR_ARRAY; 1114 } 1115 else if (*line != ' ' && *line != '\t' && *line != '"' && *line != '#') 1116 return PARSE_ERR_SYNTAX; 1117 1118 while (*pline && i < strlen(line)) 1119 { 1120 switch (*pline) 1121 { 1122 case '#': 1123 if (*col == 1) 1124 { 1125 *pstate |= PS_COMMENT_TRAN; 1126 pline++; 1127 (*col)++; 1128 } 1129 else 1130 { 1131 *ptoken++ = *pline++; 1132 (*col)++; 1133 } 1134 break; 1135 case '.': 1136 if (*col == 2 && *pstate & PS_COMMENT_TRAN) 1137 { 1138 *pstate &= ~PS_COMMENT_TRAN; 1139 *pstate |= PS_COMMENT_EXTR; 1140 pline++; 1141 (*col)++; 1142 if (*pline == ' ') 1143 { 1144 pline++; 1145 (*col)++; 1146 } 1147 else if (*pline) 1148 return PARSE_ERR_SYNTAX; 1149 } 1150 else 1151 { 1152 *ptoken++ = *pline++; 1153 (*col)++; 1154 } 1155 break; 1156 case ',': 1157 if (*col == 2 && *pstate & PS_COMMENT_TRAN) 1158 { 1159 *pstate &= ~PS_COMMENT_TRAN; 1160 *pstate |= PS_COMMENT_FLAG; 1161 pline++; 1162 (*col)++; 1163 if (*pline == ' ') 1164 { 1165 pline++; 1166 (*col)++; 1167 } 1168 else if (*pline) 1169 return PARSE_ERR_SYNTAX; 1170 } 1171 else 1172 { 1173 *ptoken++ = *pline++; 1174 (*col)++; 1175 } 1176 break; 1177 case ':': 1178 if (*col == 2 && *pstate & PS_COMMENT_TRAN) 1179 { 1180 *pstate &= ~PS_COMMENT_TRAN; 1181 *pstate |= PS_COMMENT_REFN; 1182 pline++; 1183 (*col)++; 1184 if (*pline == ' ') 1185 { 1186 pline++; 1187 (*col)++; 1188 } 1189 else if (*pline) 1190 return PARSE_ERR_SYNTAX; 1191 } 1192 else 1193 { 1194 *ptoken++ = *pline++; 1195 (*col)++; 1196 } 1197 break; 1198 case '~': 1199 if (*col == 2 && *pstate & PS_COMMENT_TRAN) 1200 { 1201 *pstate &= ~PS_COMMENT_TRAN; 1202 *pstate |= PS_COMMENT_OBSO; 1203 pline++; 1204 (*col)++; 1205 } 1206 else 1207 { 1208 *ptoken++ = *pline++; 1209 (*col)++; 1210 } 1211 break; 1212 case '"': 1213 /* skip beginning and end quote */ 1214 if ((ptoken == token || *(pline + 1) == 0) 1215 && !(*pstate 1216 & (PS_COMMENT_TRAN | PS_COMMENT_EXTR 1217 | PS_COMMENT_OBSO))) 1218 { 1219 pline++; 1220 (*col)++; 1221 } 1222 else 1223 { 1224 *ptoken++ = *pline++; 1225 (*col)++; 1226 } 1227 break; 1228 default: 1229 *ptoken++ = *pline++; 1230 (*col)++; 1231 } 1232 } 1233 *ptoken = 0; 1234 if (*pstate == PS_NONE) 1235 { 1236 struct PoEntry* newchunk = NULL; 1237 1238 if (!(*current_entry)->obsolete) 1239 dstate->msgid_count++; 1240 1241 (*current_entry)++; 1242 if (*current_entry == dstate->entries + dstate->msgid_size) 1243 { 1244 dstate->msgid_size += ALLOC_DELTA; 1245 newchunk = realloc(dstate->entries, 1246 dstate->msgid_size * sizeof(struct PoEntry)); 1247 if (!newchunk) 1248 return PARSE_ERR_ALLOC; 1249 dstate->entries = newchunk; 1250 *current_entry = dstate->entries + dstate->msgid_size 1251 - ALLOC_DELTA; 1252 } 1253 init_po_entry(*current_entry); 1254 *msgstr_index = -1; 1255 } 1256 if (*token) 1257 { 1258 if (*pstate & PS_MSGID) 1259 set_msgid(*current_entry, token, 1); 1260 else if (*pstate & PS_MSGID_PLURAL) 1261 { 1262 set_msgid_plural(*current_entry, token, 1); 1263 if (*current_entry != dstate->entries) 1264 for (int i = 0; i < dstate->nplurals; i++) 1265 if (!set_msgstr(*current_entry, NULL, i, 1266 0, dstate)) 1267 return PARSE_ERR_ALLOC; 1268 } 1269 else if (*pstate & PS_MSGSTR) 1270 { 1271 /* first entry */ 1272 if (*current_entry == dstate->entries) 1273 { 1274 if (starts_with(token, "Plural-Forms: ")) 1275 { 1276 set_plural_forms(*current_entry, 1277 token + strlen("Plural-Forms: "), 1278 0); 1279 set_nplurals(dstate, *current_entry); 1280 } 1281 } 1282 1283 if (!set_msgstr(*current_entry, token, 0, 1, dstate)) 1284 return PARSE_ERR_ALLOC; 1285 } 1286 else if (*pstate & PS_MSGSTR_ARRAY) 1287 { 1288 if (!set_msgstr(*current_entry, token, *msgstr_index, 1, 1289 dstate)) 1290 return PARSE_ERR_ALLOC; 1291 } 1292 else if (*pstate & PS_COMMENT_TRAN) 1293 { 1294 uint32_t* u32_token = NULL; 1295 size_t len = strlen(token) + 1; 1296 u32_token = calloc(len, sizeof(uint32_t)); 1297 if (!u32_token) 1298 return PARSE_ERR_ALLOC; 1299 u8_string_to_unicode(u32_token, token, len); 1300 u32_add_comment(*current_entry, u32_token); 1301 free(u32_token); 1302 } 1303 else if (*pstate & PS_COMMENT_EXTR) 1304 { 1305 uint32_t* u32_token = NULL; 1306 size_t len = strlen(token) + 1; 1307 u32_token = calloc(len, sizeof(uint32_t)); 1308 if (!u32_token) 1309 return PARSE_ERR_ALLOC; 1310 u8_string_to_unicode(u32_token, token, len); 1311 u32_add_trans_comment(*current_entry, u32_token); 1312 free(u32_token); 1313 } 1314 else if (*pstate & PS_COMMENT_FLAG) 1315 { 1316 ptoken = token; 1317 while (*ptoken) 1318 { 1319 int first = *current_entry == dstate->entries; 1320 if (starts_with(ptoken, 1321 flag_strings[FL_C_FORMAT])) 1322 { 1323 (*current_entry)->flags |= FL_C_FORMAT; 1324 ptoken += strlen( 1325 flag_strings[FL_C_FORMAT]); 1326 } 1327 else if (starts_with(ptoken, 1328 flag_strings[FL_NO_C_FORMAT])) 1329 { 1330 (*current_entry)->flags 1331 |= FL_NO_C_FORMAT; 1332 ptoken += strlen( 1333 flag_strings[FL_NO_C_FORMAT]); 1334 } 1335 else if (starts_with(ptoken, 1336 flag_strings[FL_FUZZY]) 1337 && !first) 1338 { 1339 (*current_entry)->flags |= FL_FUZZY; 1340 dstate->fuzzy_count++; 1341 ptoken += strlen(flag_strings[FL_FUZZY]); 1342 } 1343 else if (*ptoken == ',' && *(ptoken + 1) == ' ') 1344 ptoken += 2; 1345 else 1346 { 1347 /* Let this slip, ignore everything 1348 * instead; see po.h for rationale */ 1349 /*return PARSE_ERR_SYNTAX;*/ 1350 1351 while (*ptoken && *ptoken != ' ' 1352 && *ptoken != ',' 1353 && *ptoken != '\t') 1354 ptoken++; 1355 } 1356 } 1357 } 1358 else if (*pstate & PS_COMMENT_REFN) 1359 add_ref_comment(*current_entry, token); 1360 else if (*pstate & PS_COMMENT_OBSO) 1361 { 1362 uint32_t* u32_token = NULL; 1363 size_t len = strlen(token) + 1; 1364 u32_token = calloc(len, sizeof(uint32_t)); 1365 if (!u32_token) 1366 return PARSE_ERR_ALLOC; 1367 u8_string_to_unicode(u32_token, token, len); 1368 if (u32_add_comment(*current_entry, u32_token)) 1369 (*current_entry)->obsolete = 1; 1370 free(u32_token); 1371 } 1372 *token = 0; 1373 } 1374 else if (*pstate & PS_COMMENT_TRAN) 1375 u32_add_comment(*current_entry, (uint32_t*)L""); 1376 else if (*pstate & PS_COMMENT_EXTR) 1377 u32_add_trans_comment(*current_entry, (uint32_t*)L""); 1378 1379 /* one-line states */ 1380 *pstate &= ~(PS_COMMENT_TRAN | PS_COMMENT_EXTR | PS_COMMENT_FLAG 1381 | PS_COMMENT_REFN | PS_COMMENT_OBSO); 1382 1383 return PARSE_ERR_NONE; 1384 } 1385 1386 void 1387 update_statistics(const struct PoEntry* entries, struct DrawState* state, 1388 struct PoEntry* entry) 1389 { 1390 const struct PoEntry* current = NULL; 1391 int advance = 1; 1392 1393 state->fuzzy_count = 0; 1394 state->obsolete_count = 0; 1395 state->untranslated_count = 0; 1396 1397 current = entries; 1398 while (current && advance) 1399 { 1400 if (current->obsolete) 1401 state->obsolete_count++; 1402 else 1403 { 1404 if (current->msgid 1405 && (!current->msgstr || !*current->msgstr 1406 || !**current->msgstr)) 1407 state->untranslated_count++; 1408 if (current->flags & FL_FUZZY) 1409 state->fuzzy_count++; 1410 } 1411 if (current == entry) 1412 advance = 0; 1413 else 1414 current++; 1415 } 1416 } 1417 1418 void 1419 update_warning(const uint32_t* msgid, const uint32_t* msgstr, int* warning) 1420 { 1421 if (!msgid || !msgstr || !warning) 1422 return; 1423 if (u32_count_strings(msgid, (const uint32_t*)L"\\n") 1424 != u32_count_strings(msgstr, (const uint32_t*)L"\\n") 1425 || (WARN_COUNT_DOTS 1426 && u32_count_chars(msgid, '.') 1427 != u32_count_chars(msgstr, '.'))) 1428 *warning = 1; 1429 else 1430 *warning = 0; 1431 }