#include #include #include #include #include #include #include #include #include #include #include #include "templates.h" #include "s1kd_tools.h" #define PROG_NAME "s1kd-icncatalog" #define VERSION "3.3.1" #define ERR_PREFIX PROG_NAME ": ERROR: " #define INF_PREFIX PROG_NAME ": INFO: " #define E_BAD_LIST ERR_PREFIX "Could not read list: %s\n" #define E_REGEX_INVALID ERR_PREFIX "Invalid regular expression: %s\n" #define E_REGEX_BADREF ERR_PREFIX "Undefined reference in URI template: \\%c\n" #define I_RESOLVE INF_PREFIX "Resolving ICN references in %s...\n" static enum verbosity { QUIET, NORMAL, VERBOSE } verbosity = NORMAL; /* Add a notation by its reference in the catalog file. */ static void add_notation_ref(xmlDocPtr doc, xmlDocPtr icns, const xmlChar *notation) { xmlXPathContextPtr ctx; xmlXPathObjectPtr obj; ctx = xmlXPathNewContext(icns); obj = xmlXPathEvalExpression(BAD_CAST "/icnCatalog/notation", ctx); if (!xmlXPathNodeSetIsEmpty(obj->nodesetval)) { int i; bool found = false; for (i = 0; i < obj->nodesetval->nodeNr && !found; ++i) { xmlChar *name; name = xmlGetProp(obj->nodesetval->nodeTab[i], BAD_CAST "name"); if (xmlStrcmp(name, notation) == 0) { xmlChar *pubId, *sysId; pubId = xmlGetProp(obj->nodesetval->nodeTab[i], BAD_CAST "publicId"); sysId = xmlGetProp(obj->nodesetval->nodeTab[i], BAD_CAST "systemId"); add_notation(doc, name, pubId, sysId); xmlFree(pubId); xmlFree(sysId); found = true; } xmlFree(name); } } xmlXPathFreeObject(obj); xmlXPathFreeContext(ctx); } /* Check whether an ICN is used in an object. */ static bool icn_is_used(xmlDocPtr doc, const xmlChar *ident) { xmlXPathContextPtr ctx; xmlXPathObjectPtr obj; bool used; ctx = xmlXPathNewContext(doc); xmlXPathRegisterVariable(ctx, BAD_CAST "id", xmlXPathNewString(ident)); obj = xmlXPathEvalExpression(BAD_CAST "//@*[.=$id]", ctx); used = !xmlXPathNodeSetIsEmpty(obj->nodesetval); xmlXPathFreeObject(obj); xmlXPathFreeContext(ctx); return used; } /* Replace the SYSTEM URI of an entity, adding a notation if necessary. */ static void replace_entity(xmlDocPtr doc, xmlDocPtr icns, xmlEntityPtr e, const xmlChar *ident, const xmlChar *uri, const xmlChar *notation) { xmlChar *ndata; if (!notation) { ndata = xmlStrdup(e->content); } xmlUnlinkNode((xmlNodePtr) e); xmlFreeEntity(e); if (notation) { xmlAddDocEntity(doc, ident, XML_EXTERNAL_GENERAL_UNPARSED_ENTITY, NULL, uri, notation); } else { xmlAddDocEntity(doc, ident, XML_EXTERNAL_GENERAL_UNPARSED_ENTITY, NULL, uri, ndata); } if (notation) { add_notation_ref(doc, icns, notation); } else { xmlFree(ndata); } } /* Fill in backreferences in a string from a set of regex matches. */ #define BUF_MAX 256 static xmlChar *regex_replace(const xmlChar *icn, const xmlChar *uri, size_t nmatch, regmatch_t pmatch[]) { int i, n = 0; xmlChar buf[BUF_MAX]; xmlChar *s; s = xmlStrdup(BAD_CAST ""); for (i = 0; uri[i]; ++i) { if (uri[i] == '\\') { int ref = uri[++i] - '0'; if (ref >= 0 && ref < nmatch && pmatch[ref].rm_so != -1) { s = xmlStrncat(s, buf, n); n = 0; s = xmlStrncat(s, icn + pmatch[ref].rm_so, pmatch[ref].rm_eo - pmatch[ref].rm_so); } else if (verbosity > QUIET) { fprintf(stderr, E_REGEX_BADREF, ref + '0'); } } else { buf[n++] = uri[i]; if (n == BUF_MAX) { s = xmlStrncat(s, buf, n); n = 0; } } } s = xmlStrncat(s, buf, n); return s; } /* Resolve an ICN using regular expressions. */ static void resolve_icn_regex(xmlDocPtr doc, xmlDocPtr icns, const xmlChar *pattern, const xmlChar *icn, const xmlChar *uri, const xmlChar *notation) { regex_t re; regmatch_t *pmatch; size_t nmatch; if (regcomp(&re, (char *) pattern, REG_EXTENDED) != 0) { if (verbosity > QUIET) { fprintf(stderr, E_REGEX_INVALID, (char *) pattern); } return; } nmatch = re.re_nsub + 1; pmatch = malloc(sizeof(regmatch_t) * nmatch); if (regexec(&re, (char *) icn, nmatch, pmatch, 0) == 0) { xmlChar *s; xmlEntityPtr e; s = regex_replace(icn, uri, nmatch, pmatch); e = xmlGetDocEntity(doc, icn); if (e) { replace_entity(doc, icns, e, icn, s, notation); } else if (notation) { add_notation_ref(doc, icns, notation); xmlAddDocEntity(doc, icn, XML_EXTERNAL_GENERAL_UNPARSED_ENTITY, NULL, s, notation); } else { add_icn(doc, (char *) s, true); } xmlFree(s); } free(pmatch); regfree(&re); } /* Resolve an ICN pattern rule from the catalog. */ static void resolve_pattern_icn(xmlDocPtr doc, xmlDocPtr icns, const xmlChar *pattern, const xmlChar *uri, const xmlChar *notation) { xmlXPathContextPtr ctx; xmlXPathObjectPtr obj; ctx = xmlXPathNewContext(doc); obj = xmlXPathEvalExpression(BAD_CAST "//@infoEntityIdent", ctx); if (!xmlXPathNodeSetIsEmpty(obj->nodesetval)) { int i; for (i = 0; i < obj->nodesetval->nodeNr; ++i) { xmlChar *icn; icn = xmlNodeGetContent(obj->nodesetval->nodeTab[i]); resolve_icn_regex(doc, icns, pattern, icn, uri, notation); xmlFree(icn); } } xmlXPathFreeObject(obj); xmlXPathFreeContext(ctx); } /* Resolve the ICNs in a document against the ICN catalog. */ static void resolve_icn(xmlDocPtr doc, xmlDocPtr icns, const xmlChar *ident, const xmlChar *uri, const xmlChar *notation) { xmlEntityPtr e; e = xmlGetDocEntity(doc, ident); if (e) { replace_entity(doc, icns, e, ident, uri, notation); } else if (icn_is_used(doc, ident)) { if (notation) { add_notation_ref(doc, icns, notation); xmlAddDocEntity(doc, ident, XML_EXTERNAL_GENERAL_UNPARSED_ENTITY, NULL, uri, notation); } else { add_icn(doc, (char *) uri, true); } } } /* Resolve ICNs in a file against the ICN catalog. */ static void resolve_icns_in_file(const char *fname, xmlDocPtr icns, bool overwrite, const char *media) { xmlDocPtr doc; xmlXPathContextPtr ctx; xmlXPathObjectPtr obj; xmlChar *xpath; if (verbosity == VERBOSE) { fprintf(stderr, I_RESOLVE, fname); } if (!(doc = read_xml_doc(fname))) { return; } ctx = xmlXPathNewContext(icns); if (media) { xmlXPathRegisterVariable(ctx, BAD_CAST "media", xmlXPathNewString(BAD_CAST media)); xpath = BAD_CAST "/icnCatalog/media[@name=$media]/icn"; } else { xpath = BAD_CAST "/icnCatalog/icn"; } obj = xmlXPathEvalExpression(xpath, ctx); if (!xmlXPathNodeSetIsEmpty(obj->nodesetval)) { int i; for (i = 0; i < obj->nodesetval->nodeNr; ++i) { xmlChar *type, *ident, *uri, *notation; type = xmlGetProp(obj->nodesetval->nodeTab[i], BAD_CAST "type"); ident = xmlGetProp(obj->nodesetval->nodeTab[i], BAD_CAST "infoEntityIdent"); uri = xmlGetProp(obj->nodesetval->nodeTab[i], BAD_CAST "uri"); notation = xmlGetProp(obj->nodesetval->nodeTab[i], BAD_CAST "notation"); if (xmlStrcmp(type, BAD_CAST "pattern") == 0) { resolve_pattern_icn(doc, icns, ident, uri, notation); } else { resolve_icn(doc, icns, ident, uri, notation); } xmlFree(type); xmlFree(ident); xmlFree(uri); xmlFree(notation); } } xmlXPathFreeObject(obj); xmlXPathFreeContext(ctx); if (overwrite) { save_xml_doc(doc, fname); } else { save_xml_doc(doc, "-"); } xmlFreeDoc(doc); } /* Resolve ICNs in objects in a list of file names. */ static void resolve_icns_in_list(const char *path, xmlDocPtr icns, bool overwrite, const char *media) { FILE *f; char line[PATH_MAX]; if (path) { if (!(f = fopen(path, "r"))) { if (verbosity > QUIET) { fprintf(stderr, E_BAD_LIST, path); } return; } } else { f = stdin; } while (fgets(line, PATH_MAX, f)) { strtok(line, "\t\r\n"); resolve_icns_in_file(line, icns, overwrite, media); } if (path) { fclose(f); } } /* Add ICNs to a catalog. */ static void add_icns(xmlDocPtr icns, xmlNodePtr add, const char *media) { xmlNodePtr root, cur; if (media) { xmlXPathContextPtr ctx; xmlXPathObjectPtr obj; ctx = xmlXPathNewContext(icns); xmlXPathRegisterVariable(ctx, BAD_CAST "media", xmlXPathNewString(BAD_CAST media)); obj = xmlXPathEvalExpression(BAD_CAST "/icnCatalog/media[@name=$media]", ctx); if (xmlXPathNodeSetIsEmpty(obj->nodesetval)) { root = NULL; } else { root = obj->nodesetval->nodeTab[0]; } xmlXPathFreeObject(obj); xmlXPathFreeContext(ctx); } else { root = xmlDocGetRootElement(icns); } for (cur = add->children; cur; cur = cur->next) { xmlAddChild(root, xmlCopyNode(cur, 1)); } } /* Remove ICNs from a catalog. */ static void del_icns(xmlDocPtr icns, xmlNodePtr del, const char *media) { xmlNodePtr cur; xmlXPathContextPtr ctx; ctx = xmlXPathNewContext(icns); for (cur = del->children; cur; cur = cur->next) { xmlChar *ident, *xpath; xmlXPathObjectPtr obj; ident = xmlGetProp(cur, BAD_CAST "infoEntityIdent"); xmlXPathRegisterVariable(ctx, BAD_CAST "id", xmlXPathNewString(ident)); xmlFree(ident); if (media) { xmlXPathRegisterVariable(ctx, BAD_CAST "media", xmlXPathNewString(BAD_CAST media)); xpath = BAD_CAST "/icnCatalog/media[@name=$media]/icn[@infoEntityIdent=$id]"; } else { xpath = BAD_CAST "/icnCatalog/icn[@infoEntityIdent=$id]"; } obj = xmlXPathEvalExpression(xpath, ctx); if (!xmlXPathNodeSetIsEmpty(obj->nodesetval)) { xmlUnlinkNode(obj->nodesetval->nodeTab[0]); xmlFreeNode(obj->nodesetval->nodeTab[0]); obj->nodesetval->nodeTab[0] = NULL; } xmlXPathFreeObject(obj); } xmlXPathFreeContext(ctx); } /* Help/usage message. */ static void show_help(void) { puts("Usage: " PROG_NAME " [options] [...]"); puts(""); puts("Options:"); puts(" -a, --add Add an ICN to the catalog."); puts(" -C, --create Create a new ICN catalog."); puts(" -c, --catalog Use as the ICN catalog."); puts(" -d, --del Delete an ICN from the catalog."); puts(" -f, --overwrite Overwrite input objects."); puts(" -h, -?, --help Show help/usage message."); puts(" -l, --list Treat input as list of objects."); puts(" -m, --media Specify intended output media."); puts(" -n, --ndata Set the notation of the new ICN."); puts(" -q, --quiet Quiet mode."); puts(" -t, --type Set the type of the new catalog entry."); puts(" -u, --uri Set the URI of the new ICN."); puts(" -v, --verbose Verbose output."); puts(" --version Show version information."); LIBXML2_PARSE_LONGOPT_HELP } /* Show version information. */ static void show_version(void) { printf("%s (s1kd-tools) %s\n", PROG_NAME, VERSION); printf("Using libxml %s\n", xmlParserVersion); } int main(int argc, char **argv) { int i; bool overwrite = false; char *icns_fname = NULL; bool createnew = false; char *media = NULL; xmlDocPtr icns; xmlNodePtr add, del, cur = NULL; bool islist = false; const char *sopts = "a:Cc:d:flm:n:qt:u:vxh?"; struct option lopts[] = { {"version" , no_argument , 0, 0}, {"help" , no_argument , 0, 'h'}, {"add" , required_argument, 0, 'a'}, {"create" , no_argument , 0, 'C'}, {"catalog" , required_argument, 0, 'c'}, {"del" , required_argument, 0, 'd'}, {"overwrite", no_argument , 0, 'f'}, {"list" , no_argument , 0, 'l'}, {"media" , required_argument, 0, 'm'}, {"ndata" , required_argument, 0, 'n'}, {"quiet" , no_argument , 0, 'q'}, {"type" , required_argument, 0, 't'}, {"uri" , required_argument, 0, 'u'}, {"verbose" , no_argument , 0, 'v'}, LIBXML2_PARSE_LONGOPT_DEFS {0, 0, 0, 0} }; int loptind = 0; add = xmlNewNode(NULL, BAD_CAST "add"); del = xmlNewNode(NULL, BAD_CAST "del"); while ((i = getopt_long(argc, argv, sopts, lopts, &loptind)) != -1) { switch (i) { case 0: if (strcmp(lopts[loptind].name, "version") == 0) { show_version(); return 0; } LIBXML2_PARSE_LONGOPT_HANDLE(lopts, loptind, optarg) break; case 'a': cur = xmlNewChild(add, NULL, BAD_CAST "icn", NULL); xmlSetProp(cur, BAD_CAST "infoEntityIdent", BAD_CAST optarg); break; case 'C': createnew = true; break; case 'c': if (!icns_fname) { icns_fname = strdup(optarg); } break; case 'd': cur = xmlNewChild(del, NULL, BAD_CAST "icn", NULL); xmlSetProp(cur, BAD_CAST "infoEntityIdent", BAD_CAST optarg); break; case 'f': overwrite = true; break; case 'l': islist = true; break; case 'm': if (!media) { media = strdup(optarg); } break; case 'n': if (cur) { xmlSetProp(cur, BAD_CAST "notation", BAD_CAST optarg); } break; case 'q': verbosity = QUIET; break; case 't': if (cur) { xmlSetProp(cur, BAD_CAST "type", BAD_CAST optarg); } break; case 'u': if (cur) { xmlSetProp(cur, BAD_CAST "uri", BAD_CAST optarg); } break; case 'v': verbosity = VERBOSE; break; case 'h': case '?': show_help(); return 0; } } if (!icns_fname) { icns_fname = malloc(PATH_MAX); find_config(icns_fname, DEFAULT_ICNCATALOG_FNAME); } if (createnew || access(icns_fname, F_OK) == -1) { icns = read_xml_mem((const char *) icncatalog_xml, icncatalog_xml_len); } else { icns = read_xml_doc(icns_fname); } if (add->children || del->children) { if (add->children) { add_icns(icns, add, media); } if (del->children) { del_icns(icns, del, media); } if (overwrite) { save_xml_doc(icns, icns_fname); } else { save_xml_doc(icns, "-"); } } else if (optind < argc) { for (i = optind; i < argc; ++i) { if (islist) { resolve_icns_in_list(argv[i], icns, overwrite, media); } else { resolve_icns_in_file(argv[i], icns, overwrite, media); } } } else if (createnew) { if (overwrite) { save_xml_doc(icns, icns_fname); } else { save_xml_doc(icns, "-"); } } else if (islist) { resolve_icns_in_list(NULL, icns, overwrite, media); } else { resolve_icns_in_file("-", icns, false, media); } free(icns_fname); free(media); xmlFreeNode(add); xmlFreeNode(del); xmlFreeDoc(icns); xmlCleanupParser(); return 0; }