/ src / isocodes.c
isocodes.c
  1  /**
  2   * Copyright © 2016-2022 Dr. Tobias Quathamer <toddy@debian.org>
  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 <http://www.gnu.org/licenses/>.
 16   */
 17  
 18  #define _POSIX_C_SOURCE 200112L
 19  
 20  #include <stdlib.h>
 21  #include <glib.h>
 22  #include <glib/gi18n.h>
 23  #include <glib/gprintf.h>
 24  #include <locale.h>
 25  #include "isocodes.h"
 26  #include "options.h"
 27  #include "search.h"
 28  
 29  /**
 30   * Validate the given JSON data.
 31   */
 32  gboolean isocodes_validate(JsonParser *parser, GError **error)
 33  {
 34      // Get the root node
 35      JsonNode *root = json_parser_get_root(parser);
 36      // Ensure that there is a root element
 37      if (root == NULL) {
 38          isocodes_set_validation_error(error);
 39          return FALSE;
 40      }
 41      // Ensure that the root element is an object
 42      if (g_strcmp0(json_node_type_name(root), "JsonObject")) {
 43          isocodes_set_validation_error(error);
 44          return FALSE;
 45      }
 46      // Get the root object
 47      JsonObject *root_object = json_node_get_object(root);
 48      // Ensure that the root object has only one member
 49      if (json_object_get_size(root_object) != 1) {
 50          isocodes_set_validation_error(error);
 51          return FALSE;
 52      }
 53      // The root object must have the standard as only member
 54      if (!json_object_has_member(root_object, option_standard)) {
 55          isocodes_set_validation_error(error);
 56          return FALSE;
 57      }
 58      return TRUE;
 59  }
 60  
 61  /**
 62   * Helper function to set the GError
 63   */
 64  void isocodes_set_validation_error(GError **error)
 65  {
 66      g_set_error(error, g_quark_from_string(GETTEXT_PACKAGE), 0,
 67                  // TRANSLATORS:
 68                  // The first placeholder is a filename, including the directory path.
 69                  // The second placeholder is an ISO standard, e.g. 3166-1 or 639-3.
 70                  _("The file \"%1$s\" does not contain valid ISO %2$s data."), options_get_filename(), option_standard);
 71  }
 72  
 73  /**
 74   * Show codes from ISO standard
 75   */
 76  void isocodes_show_codes(JsonParser *parser, gchar **codes)
 77  {
 78      // Get the root node, object, and entries array
 79      JsonNode *root = json_parser_get_root(parser);
 80      JsonObject *root_object = json_node_get_object(root);
 81      JsonNode *standard = json_object_get_member(root_object, option_standard);
 82      JsonArray *entries_array = json_node_get_array(standard);
 83      GList *entries_list = json_array_get_elements(entries_array);
 84      GList *list_entry = g_list_first(entries_list);
 85  
 86      // If there are codes given on the command line,
 87      // only display entries matching those.
 88      if (codes[0]) {
 89          int i = 0;
 90          GError *error;
 91          while (codes[i]) {
 92              error = NULL;
 93              if (!search_entry(codes[i], entries_list, &error)) {
 94                  // TRANSLATORS: This is an error message.
 95                  g_printerr(_("isoquery: %s\n"), error->message);
 96                  g_error_free(error);
 97              }
 98              i++;
 99          }
100      } else {
101          // Set up a JSON object for an entry
102          JsonObject *entry;
103          // Show all entries
104          while (list_entry) {
105              entry = json_node_get_object(list_entry->data);
106              isocodes_show_entry(entry);
107              list_entry = g_list_next(list_entry);
108          }
109      }
110  
111      g_list_free(entries_list);
112  }
113  
114  /**
115   * Print the given entry to stdout
116   */
117  void isocodes_show_entry(JsonObject *entry)
118  {
119      gchar **fields = isocodes_get_fields();
120      gchar separator = '\n';
121  
122      // Ensure that we've got fields to display
123      if (!fields) {
124          return;
125      }
126      // Cycle through all fields
127      int i = 0;
128      while (fields[i]) {
129          // Handle optional fields gracefully
130          if (json_object_has_member(entry, fields[i])) {
131              g_printf("%s", json_object_get_string_member(entry, fields[i]));
132          }
133          // Always add the tab as separator
134          g_printf("\t");
135          i++;
136      }
137  
138      // Special case for the name:
139      // switching which field to display,
140      // translate the name
141      isocodes_show_name(entry);
142  
143      // Separate entries, either with NULL byte or newline
144      if (option_null_separator) {
145          separator = 0;
146      }
147      g_printf("%c", separator);
148      g_strfreev(fields);
149  }
150  
151  /**
152   * Returns a list of the fields in the current ISO standard
153   */
154  gchar **isocodes_get_fields(void)
155  {
156      gchar **fields = NULL;
157      if (!g_strcmp0(option_standard, "639-2")) {
158          gchar *f[] = {
159              "alpha_3", "bibliographic", "alpha_2", NULL
160          };
161          fields = g_strdupv(f);
162      } else if (!g_strcmp0(option_standard, "639-3")) {
163          gchar *f[] = {
164              "alpha_3", "scope", "type", "alpha_2", "bibliographic", NULL
165          };
166          fields = g_strdupv(f);
167      } else if (!g_strcmp0(option_standard, "639-5")) {
168          gchar *f[] = {
169              "alpha_3", NULL
170          };
171          fields = g_strdupv(f);
172      } else if (!g_strcmp0(option_standard, "3166-1")) {
173          if (option_flag_output) {
174              gchar *f[] = {
175                  "alpha_2", "alpha_3", "numeric", "flag", NULL
176              };
177              fields = g_strdupv(f);
178          } else {
179              gchar *f[] = {
180                  "alpha_2", "alpha_3", "numeric", NULL
181              };
182              fields = g_strdupv(f);
183          }
184      } else if (!g_strcmp0(option_standard, "3166-2")) {
185          gchar *f[] = {
186              "code", "type", "parent", NULL
187          };
188          fields = g_strdupv(f);
189      } else if (!g_strcmp0(option_standard, "3166-3")) {
190          gchar *f[] = {
191              "alpha_3", "alpha_4", "numeric", "comment", "withdrawal_date", NULL
192          };
193          fields = g_strdupv(f);
194      } else if (!g_strcmp0(option_standard, "4217")) {
195          gchar *f[] = {
196              "alpha_3", "numeric", NULL
197          };
198          fields = g_strdupv(f);
199      } else if (!g_strcmp0(option_standard, "15924")) {
200          gchar *f[] = {
201              "alpha_4", "numeric", NULL
202          };
203          fields = g_strdupv(f);
204      }
205      return fields;
206  }
207  
208  /**
209   * Special handling for the name fields
210   */
211  void isocodes_show_name(JsonObject *entry)
212  {
213      const gchar *output;
214      // Use the correct name field
215      if (json_object_has_member(entry, option_namefield)) {
216          output = json_object_get_string_member(entry, option_namefield);
217      } else {
218          // Fallback: use the "name" field, which is mandatory
219          output = json_object_get_string_member(entry, "name");
220      }
221      // Try to translate, if there's a locale given
222      if (strlen(option_locale) > 0) {
223          // Save the current locale and environment variable LANGUAGE.
224          gchar *locale_backup = setlocale(LC_ALL, NULL);
225          gchar *env_backup = g_strdup(getenv("LANGUAGE"));
226  
227          // Use the wanted locale to look for a translation
228          setenv("LANGUAGE", option_locale, TRUE);
229          // Use the default locale, based on the environment variable above
230          setlocale(LC_ALL, "");
231  
232          // Determine the gettext domain from the standard
233          gchar *domain = g_strdup_printf("iso_%s", option_standard);
234          // Do the actual translation
235          output = dgettext(domain, output);
236  
237          // Restore the environment variable LANGUAGE, either
238          // to the previous value or unset the variable.
239          if (env_backup == NULL) {
240              unsetenv("LANGUAGE");
241          } else {
242              setenv("LANGUAGE", env_backup, TRUE);
243          }
244          // Restore the locale from backup
245          setlocale(LC_ALL, locale_backup);
246  
247          g_free(env_backup);
248          g_free(domain);
249      }
250      g_printf("%s", output);
251  }