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 }