/ .. / / -> download
#include <stdio.h>
#include <unistd.h>
#include <getopt.h>
#include <stdbool.h>
#include <string.h>
#include <libgen.h>
#include <regex.h>

#include <libxml/tree.h>
#include <libxml/valid.h>
#include <libxml/xpath.h>
#include <libxml/xpathInternals.h>

#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] [<object>...]");
	puts("");
	puts("Options:");
	puts("  -a, --add <icn>         Add an ICN to the catalog.");
	puts("  -C, --create            Create a new ICN catalog.");
	puts("  -c, --catalog <catalog> Use <catalog> as the ICN catalog.");
	puts("  -d, --del <icn>         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 <media>     Specify intended output media.");
	puts("  -n, --ndata <notation>  Set the notation of the new ICN.");
	puts("  -q, --quiet             Quiet mode.");
	puts("  -t, --type <type>       Set the type of the new catalog entry.");
	puts("  -u, --uri <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;
}


/ gopher://khzae.net/0/s1000d/s1kd-tools/src/tools/s1kd-icncatalog/s1kd-icncatalog.c
Styles: Light Dark Classic