| /* XML resource locating rules |
| Copyright (C) 2015, 2019-2020 Free Software Foundation, Inc. |
| |
| This file was written by Daiki Ueno <ueno@gnu.org>, 2015. |
| |
| This program 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 of the License, or |
| (at your option) any later version. |
| |
| This program 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 this program. If not, see <https://www.gnu.org/licenses/>. */ |
| |
| #ifdef HAVE_CONFIG_H |
| # include <config.h> |
| #endif |
| |
| /* Specification. */ |
| #include "locating-rule.h" |
| |
| #include "basename-lgpl.h" |
| #include "concat-filename.h" |
| #include "c-strcase.h" |
| |
| #if HAVE_DIRENT_H |
| # include <dirent.h> |
| #endif |
| |
| #if HAVE_DIRENT_H |
| # define HAVE_DIR 1 |
| #else |
| # define HAVE_DIR 0 |
| #endif |
| |
| #include "dir-list.h" |
| #include <errno.h> |
| #include "error.h" |
| #include "filename.h" |
| #include <fnmatch.h> |
| #include "gettext.h" |
| #include "mem-hash-map.h" |
| #include <libxml/parser.h> |
| #include <libxml/uri.h> |
| #include "xalloc.h" |
| |
| #define _(str) gettext (str) |
| |
| #define LOCATING_RULES_NS "https://www.gnu.org/s/gettext/ns/locating-rules/1.0" |
| |
| struct document_locating_rule_ty |
| { |
| char *ns; |
| char *local_name; |
| |
| char *target; |
| }; |
| |
| struct document_locating_rule_list_ty |
| { |
| struct document_locating_rule_ty *items; |
| size_t nitems; |
| size_t nitems_max; |
| }; |
| |
| struct locating_rule_ty |
| { |
| char *pattern; |
| char *name; |
| |
| struct document_locating_rule_list_ty doc_rules; |
| char *target; |
| }; |
| |
| struct locating_rule_list_ty |
| { |
| struct locating_rule_ty *items; |
| size_t nitems; |
| size_t nitems_max; |
| }; |
| |
| static char * |
| get_attribute (xmlNode *node, const char *attr) |
| { |
| xmlChar *value; |
| char *result; |
| |
| value = xmlGetProp (node, BAD_CAST attr); |
| result = xstrdup ((const char *) value); |
| xmlFree (value); |
| |
| return result; |
| } |
| |
| static const char * |
| document_locating_rule_match (struct document_locating_rule_ty *rule, |
| xmlDoc *doc) |
| { |
| xmlNode *root; |
| |
| root = xmlDocGetRootElement (doc); |
| if (rule->ns != NULL) |
| { |
| if (root->ns == NULL |
| || !xmlStrEqual (root->ns->href, BAD_CAST rule->ns)) |
| return NULL; |
| } |
| |
| if (rule->local_name != NULL) |
| { |
| if (!xmlStrEqual (root->name, |
| BAD_CAST rule->local_name)) |
| return NULL; |
| } |
| |
| return rule->target; |
| } |
| |
| static const char * |
| locating_rule_match (struct locating_rule_ty *rule, |
| const char *filename, |
| const char *name) |
| { |
| if (name != NULL) |
| { |
| if (rule->name == NULL || c_strcasecmp (name, rule->name) != 0) |
| return NULL; |
| } |
| else |
| { |
| const char *base; |
| char *reduced; |
| int err; |
| |
| base = strrchr (filename, '/'); |
| if (!base) |
| base = filename; |
| |
| reduced = xstrdup (base); |
| /* Remove a trailing ".in" - it's a generic suffix. */ |
| while (strlen (reduced) >= 3 |
| && memcmp (reduced + strlen (reduced) - 3, ".in", 3) == 0) |
| reduced[strlen (reduced) - 3] = '\0'; |
| |
| err = fnmatch (rule->pattern, last_component (reduced), FNM_PATHNAME); |
| free (reduced); |
| if (err != 0) |
| return NULL; |
| } |
| |
| /* Check documentRules. */ |
| if (rule->doc_rules.nitems > 0) |
| { |
| const char *target; |
| xmlDoc *doc; |
| size_t i; |
| |
| doc = xmlReadFile (filename, NULL, |
| XML_PARSE_NONET |
| | XML_PARSE_NOWARNING |
| | XML_PARSE_NOBLANKS |
| | XML_PARSE_NOERROR); |
| if (doc == NULL) |
| { |
| xmlError *err = xmlGetLastError (); |
| error (0, 0, _("cannot read %s: %s"), filename, err->message); |
| return NULL; |
| } |
| |
| for (i = 0, target = NULL; i < rule->doc_rules.nitems; i++) |
| { |
| target = |
| document_locating_rule_match (&rule->doc_rules.items[i], doc); |
| if (target) |
| break; |
| } |
| xmlFreeDoc (doc); |
| if (target != NULL) |
| return target; |
| } |
| |
| if (rule->target != NULL) |
| return rule->target; |
| |
| return NULL; |
| } |
| |
| const char * |
| locating_rule_list_locate (struct locating_rule_list_ty *rules, |
| const char *filename, |
| const char *name) |
| { |
| size_t i; |
| |
| for (i = 0; i < rules->nitems; i++) |
| { |
| if (IS_RELATIVE_FILE_NAME (filename)) |
| { |
| int j; |
| |
| for (j = 0; ; ++j) |
| { |
| const char *dir = dir_list_nth (j); |
| char *new_filename; |
| const char *target; |
| |
| if (dir == NULL) |
| break; |
| |
| new_filename = xconcatenated_filename (dir, filename, NULL); |
| target = locating_rule_match (&rules->items[i], new_filename, |
| name); |
| free (new_filename); |
| if (target != NULL) |
| return target; |
| } |
| } |
| else |
| { |
| const char *target = |
| locating_rule_match (&rules->items[i], filename, name); |
| |
| if (target != NULL) |
| return target; |
| } |
| } |
| |
| return NULL; |
| } |
| |
| static void |
| missing_attribute (xmlNode *node, const char *attribute) |
| { |
| error (0, 0, _("\"%s\" node does not have \"%s\""), node->name, attribute); |
| } |
| |
| static void |
| document_locating_rule_destroy (struct document_locating_rule_ty *rule) |
| { |
| free (rule->ns); |
| free (rule->local_name); |
| free (rule->target); |
| } |
| |
| static void |
| document_locating_rule_list_add (struct document_locating_rule_list_ty *rules, |
| xmlNode *node) |
| { |
| struct document_locating_rule_ty rule; |
| |
| if (!xmlHasProp (node, BAD_CAST "target")) |
| { |
| missing_attribute (node, "target"); |
| return; |
| } |
| |
| memset (&rule, 0, sizeof (struct document_locating_rule_ty)); |
| |
| if (xmlHasProp (node, BAD_CAST "ns")) |
| rule.ns = get_attribute (node, "ns"); |
| if (xmlHasProp (node, BAD_CAST "localName")) |
| rule.local_name = get_attribute (node, "localName"); |
| rule.target = get_attribute (node, "target"); |
| |
| if (rules->nitems == rules->nitems_max) |
| { |
| rules->nitems_max = 2 * rules->nitems_max + 1; |
| rules->items = |
| xrealloc (rules->items, |
| sizeof (struct document_locating_rule_ty) |
| * rules->nitems_max); |
| } |
| memcpy (&rules->items[rules->nitems++], &rule, |
| sizeof (struct document_locating_rule_ty)); |
| } |
| |
| static void |
| locating_rule_destroy (struct locating_rule_ty *rule) |
| { |
| size_t i; |
| |
| for (i = 0; i < rule->doc_rules.nitems; i++) |
| document_locating_rule_destroy (&rule->doc_rules.items[i]); |
| free (rule->doc_rules.items); |
| |
| free (rule->name); |
| free (rule->pattern); |
| free (rule->target); |
| } |
| |
| static bool |
| locating_rule_list_add_from_file (struct locating_rule_list_ty *rules, |
| const char *rule_file_name) |
| { |
| xmlDoc *doc; |
| xmlNode *root, *node; |
| |
| doc = xmlReadFile (rule_file_name, "utf-8", |
| XML_PARSE_NONET |
| | XML_PARSE_NOWARNING |
| | XML_PARSE_NOBLANKS |
| | XML_PARSE_NOERROR); |
| if (doc == NULL) |
| { |
| error (0, 0, _("cannot read XML file %s"), rule_file_name); |
| return false; |
| } |
| |
| root = xmlDocGetRootElement (doc); |
| if (!(xmlStrEqual (root->name, BAD_CAST "locatingRules") |
| #if 0 |
| && root->ns |
| && xmlStrEqual (root->ns->href, BAD_CAST LOCATING_RULES_NS) |
| #endif |
| )) |
| { |
| error (0, 0, _("the root element is not \"locatingRules\"")); |
| xmlFreeDoc (doc); |
| return false; |
| } |
| |
| for (node = root->children; node; node = node->next) |
| { |
| if (xmlStrEqual (node->name, BAD_CAST "locatingRule")) |
| { |
| struct locating_rule_ty rule; |
| |
| if (!xmlHasProp (node, BAD_CAST "pattern")) |
| { |
| missing_attribute (node, "pattern"); |
| xmlFreeDoc (doc); |
| } |
| else |
| { |
| memset (&rule, 0, sizeof (struct locating_rule_ty)); |
| rule.pattern = get_attribute (node, "pattern"); |
| if (xmlHasProp (node, BAD_CAST "name")) |
| rule.name = get_attribute (node, "name"); |
| if (xmlHasProp (node, BAD_CAST "target")) |
| rule.target = get_attribute (node, "target"); |
| else |
| { |
| xmlNode *n; |
| |
| for (n = node->children; n; n = n->next) |
| { |
| if (xmlStrEqual (n->name, BAD_CAST "documentRule")) |
| document_locating_rule_list_add (&rule.doc_rules, n); |
| } |
| } |
| if (rules->nitems == rules->nitems_max) |
| { |
| rules->nitems_max = 2 * rules->nitems_max + 1; |
| rules->items = |
| xrealloc (rules->items, |
| sizeof (struct locating_rule_ty) * rules->nitems_max); |
| } |
| memcpy (&rules->items[rules->nitems++], &rule, |
| sizeof (struct locating_rule_ty)); |
| } |
| } |
| } |
| |
| xmlFreeDoc (doc); |
| return true; |
| } |
| |
| bool |
| locating_rule_list_add_from_directory (struct locating_rule_list_ty *rules, |
| const char *directory) |
| { |
| #if HAVE_DIR |
| DIR *dirp; |
| |
| dirp = opendir (directory); |
| if (dirp == NULL) |
| return false; |
| |
| for (;;) |
| { |
| struct dirent *dp; |
| |
| errno = 0; |
| dp = readdir (dirp); |
| if (dp != NULL) |
| { |
| const char *name = dp->d_name; |
| size_t namlen = strlen (name); |
| |
| if (namlen > 4 && memcmp (name + namlen - 4, ".loc", 4) == 0) |
| { |
| char *locator_file_name = |
| xconcatenated_filename (directory, name, NULL); |
| locating_rule_list_add_from_file (rules, locator_file_name); |
| free (locator_file_name); |
| } |
| } |
| else if (errno != 0) |
| return false; |
| else |
| break; |
| } |
| if (closedir (dirp)) |
| return false; |
| |
| #endif |
| return true; |
| } |
| |
| struct locating_rule_list_ty * |
| locating_rule_list_alloc (void) |
| { |
| struct locating_rule_list_ty *result; |
| |
| xmlCheckVersion (LIBXML_VERSION); |
| |
| result = XCALLOC (1, struct locating_rule_list_ty); |
| |
| return result; |
| } |
| |
| static void |
| locating_rule_list_destroy (struct locating_rule_list_ty *rules) |
| { |
| while (rules->nitems-- > 0) |
| locating_rule_destroy (&rules->items[rules->nitems]); |
| free (rules->items); |
| } |
| |
| void |
| locating_rule_list_free (struct locating_rule_list_ty *rules) |
| { |
| if (rules != NULL) |
| locating_rule_list_destroy (rules); |
| free (rules); |
| } |