/* XML resource locating rules Copyright (C) 2015-2016 Free Software Foundation, Inc. This file was written by Daiki Ueno , 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 . */ #ifdef HAVE_CONFIG_H # include #endif /* Specification. */ #include "locating-rule.h" #include "basename.h" #include "concat-filename.h" #include "c-strcase.h" #if HAVE_DIRENT_H # include #endif #if HAVE_DIRENT_H # define HAVE_DIR 1 #else # define HAVE_DIR 0 #endif #include "dir-list.h" #include #include "error.h" #include "filename.h" #include #include "gettext.h" #include "hash.h" #include #include #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, basename (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) { const char *target = NULL; size_t i; for (i = 0; i < rules->nitems; i++) { if (IS_ABSOLUTE_PATH (filename)) { target = locating_rule_match (&rules->items[i], filename, name); if (target != NULL) return target; } else { int j; for (j = 0; ; ++j) { const char *dir = dir_list_nth (j); char *new_filename; 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; } } } 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); continue; } 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; } 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); }