/* This file is part of Wyslij-po Copyright (C) 2007-2021 Sergey Poznyakoff Wyslij-po is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 3, or (at your option) any later version. Wyslij-po is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Wyslij-po. If not, see . */ #include "wyslij-po.h" mu_assoc_t lang_assoc; mu_header_t header; struct po_file { const char *name; unsigned line; FILE *fp; char buf[1024]; int level; char *curp; }; int po_fill_buf (struct po_file *file) { file->level = fread (file->buf, 1, sizeof file->buf, file->fp); if (file->level == 0) { int ec = errno; if (ferror (file->fp)) mu_error (_("%s: read error: %s"), file->name, mu_strerror (ec)); else mu_error (_("%s: short read"), file->name); exit (1); } file->curp = file->buf; return *file->curp; } int po_at_empty_line (struct po_file *file) { if (file->level == 0) po_fill_buf (file); return mu_isspace (*file->curp) || *file->curp == '#'; } void po_next_char (struct po_file *file) { if (file->level == 0) po_fill_buf (file); if (*file->curp == '\n') file->line++; file->level--; file->curp++; } /*#define po_next_char(file) \ ((file)->level == 0 ? \ po_fill_buf (file) : (*(file)->curp == '\n' ? file->line++ : 0), (file)->level--, *++(file)->curp) */ #define po_cur_char(file) \ ((file)->level == 0 ? po_fill_buf (file) : *(file)->curp) #define po_invalidate(file) ((file)->level = 0, (file)->curp = (file)->buf) void po_next_line (struct po_file *file) { do po_next_char (file); while (po_cur_char (file) != '\n'); po_next_char (file); } long po_tell (struct po_file *file) { return ftell (file->fp) + file->curp - file->buf; } void po_seek (struct po_file *file, long pos) { long needle = po_tell (file); for (; pos > needle; pos--) { if (file->curp == file->buf) { fseek (file->fp, needle - pos, SEEK_CUR); po_invalidate (file); break; } file->level++; file->curp--; } } int po_is_keyword (struct po_file *file, const char *kw) { const char *s; long needle = po_tell (file); for (s = kw; *s; s++, po_next_char (file)) { if (*s != po_cur_char (file)) { po_seek (file, needle); return 0; } } return 1; } int po_find_msgid (struct po_file *file) { while (po_at_empty_line (file)) po_next_line (file); return po_is_keyword (file, "msgid"); } void po_open (struct po_file *file, const char *name) { file->name = name; file->line = 1; file->fp = fopen (name, "r"); if (!file->fp) { mu_error (_("cannot open file %s: %s"), name, mu_strerror (errno)); exit (1); } po_fill_buf (file); } void po_close (struct po_file *file) { fclose (file->fp); } size_t po_read_line (struct po_file *file, char **buf, size_t *size) { size_t i = 0; do { if (i == *size) { *size += 512; *buf = mu_realloc (*buf, *size); } (*buf)[i] = po_cur_char (file); po_next_char (file); } while ((*buf)[i++] != '\n'); if (i == *size) { *size += 512; *buf = mu_realloc (*buf, *size); } (*buf)[i] = 0; return i; } void po_header (const char *name) { struct po_file pof; size_t bufsize = 0; char *buf = NULL; mu_opool_t pool; size_t size; int status; struct mu_wordsplit ws; int wsflags = MU_WRDSF_NOVAR | MU_WRDSF_NOCMD | MU_WRDSF_DQUOTE | MU_WRDSF_CESCAPES | MU_WRDSF_NOSPLIT; char *str; po_open (&pof, name); if (!po_find_msgid (&pof)) { mu_error (_("%s:%u: unexpected keyword (instead of \"msgid\")"), pof.name, pof.line); exit (1); } po_next_line (&pof); if (!po_is_keyword (&pof, "msgstr")) { mu_error (_("%s:%u: unexpected keyword (instead of \"msgstr\")"), pof.name, pof.line); exit (1); } mu_opool_create (&pool, MU_OPOOL_ENOMEMABRT); while (1) { char *p; size_t len; len = po_read_line (&pof, &buf, &bufsize); if (buf[len - 1] == '\n') buf[--len] = 0; for (p = buf; *p && mu_isblank (*p); p++, len--) ; if (*p == 0) break; if (len == 0) continue; if (mu_wordsplit (p, &ws, wsflags)) { mu_error (_("cannot split \"%s\": %s"), p, mu_wordsplit_strerror (&ws)); exit (1); } wsflags |= MU_WRDSF_REUSE; mu_opool_appendz (pool, ws.ws_wordv[0]); } mu_opool_append_char (pool, '\n'); mu_opool_append_char (pool, 0); po_close (&pof); if (wsflags & MU_WRDSF_REUSE) mu_wordsplit_free (&ws); free (buf); str = mu_opool_finish (pool, &size); status = mu_header_create (&header, str, size); if (status) { mu_error (_("cannot create header: %s"), mu_strerror (status)); exit (1); } mu_opool_destroy (&pool); } char const * lang_lookup(char const *id) { return mu_assoc_get (lang_assoc, id); } const char * parse_language_team (const char *lang_team) { size_t i; char *lang_name; const char *lang; int status; struct mu_wordsplit ws; status = mu_wordsplit (lang_team, &ws, MU_WRDSF_DEFFLAGS); if (status) { mu_error (_("Cannot parse Language-Team: %s"), mu_wordsplit_strerror (&ws)); exit (1); } for (i = 1; i < ws.ws_wordc; i++) { if (ws.ws_wordv[i][0] == '<') break; /* FIXME: Recode to ascii? */ } status = mu_argcv_string (i, ws.ws_wordv, &lang_name); mu_wordsplit_free (&ws); if (status) { mu_error (_("Cannot build language name")); exit (1); } lang = lang_lookup (lang_name); if (!lang) { mu_error(_("Unknown language: %s"), lang_name); exit (1); } free (lang_name); return lang; } static void get_header_time (const char *file_name, const char *hname, time_t *retval) { const char *s; if (mu_header_sget_value (header, hname, &s)) { mu_error (_("%s: No %s"), file_name, hname); exit (1); } if (mu_parse_date (s, retval, NULL)) { mu_error (_("%s: Cannot parse %s"), file_name, hname); exit (1); } } static void verify_times (const char *file_name) { time_t pot_date, po_date; if (verbose > 1) mu_printf (_("Verifying creation vs. revision date\n")); get_header_time (file_name, "POT-Creation-Date", &pot_date); get_header_time (file_name, "PO-Revision-Date", &po_date); if (po_date < pot_date) { mu_error (_("%s: po revision date is prior to pot creation date"), file_name); exit (1); } } #define PO_SUF ".po" char * restore_file_name (const char *file_name, char **pemail) { const char *project_id; const char *lang_team; const char *langcode; struct mu_wordsplit ws; char *name; int status; po_header (file_name); if (verbose > 1) mu_printf (_("Restoring canonical file name for `%s'\n"), file_name); status = mu_header_sget_value (header, "Project-Id-Version", &project_id); if (status) { mu_error (_("No Project-Id-Version")); exit (1); } status = mu_header_sget_value (header, "Language-Team", &lang_team); if (status) { mu_error (_("No Language-Team")); exit (1); } status = mu_wordsplit (project_id, &ws, MU_WRDSF_DEFFLAGS); if (status) { mu_error (_("Cannot parse Project-Id-Version: %s"), mu_wordsplit_strerror (&ws)); exit (1); } if (ws.ws_wordc != 2) { mu_error (_("Malformed Project-Id-Version")); exit (1); } if (verify_mask & VERIFY_PACKAGE_VERSION) verify_pot_version (ws.ws_wordv[0], ws.ws_wordv[1]); if (verify_mask & VERIFY_PO_TIME) verify_times (file_name); langcode = parse_language_team (lang_team); if (mu_asprintf (&name, "%s-%s.%s%s", ws.ws_wordv[0], ws.ws_wordv[1], langcode, PO_SUF)) mu_alloc_die (); mu_wordsplit_free (&ws); if (pemail) { const char *s; if (mu_header_sget_value (header, "Last-Translator", &s) == 0) { mu_address_t addr; status = mu_address_create (&addr, s); if (status) { mu_error (_("%s: cannot parse last translator address `%s': %s"), file_name, s, mu_strerror (status)); } else { status = mu_address_aget_email (addr, 1, pemail); if (status) { mu_error (_("%s: cannot retrieve email address from Last-Translator: %s"), file_name, mu_strerror (status)); exit (1); } mu_address_destroy (&addr); } } else { mu_error (_("%s: No Last-Translator"), file_name); exit (1); } } return name; } int parse_lang_datafile (const char *name) { int status; unsigned line = 0; mu_stream_t instream; char *buf = NULL; size_t bufsize = 0, n; if (verbose > 1) mu_printf (_("Sourcing language table `%s'\n"), name); status = mu_mapfile_stream_create (&instream, name, MU_STREAM_READ); if (status) { mu_error (_("Cannot open %s: %s"), name, mu_strerror (status)); return 1; } if (!lang_assoc) { status = mu_assoc_create (&lang_assoc, MU_ASSOC_ICASE); if (status) { mu_error (_("Cannot create assoc table: %s"), mu_strerror (status)); mu_stream_unref (instream); exit (1); } mu_assoc_set_destroy_item (lang_assoc, mu_list_free_item); } while (mu_stream_getline (instream, &buf, &bufsize, &n) == 0 && n > 0) { char *p, *q; line++; p = mu_str_stripws (buf); if (*p == 0 || *p == '#') continue; q = mu_str_skip_class_comp (p, MU_CTYPE_SPACE); if (*q) { *q++ = 0; q = mu_str_skip_class (q, MU_CTYPE_SPACE); } if (*q == 0) { mu_error (_("%s:%u: too few fields"), name, line); continue; } mu_assoc_install (lang_assoc, q, mu_strdup (p)); } mu_stream_unref (instream); return 0; }