poe

Уређивач .po фајлова
git clone https://git.sr.ht/~strahinja/poe
Дневник | Датотеке | Референце | ПРОЧИТАЈМЕ | ЛИЦЕНЦА

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 				&current->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 }