/ .. / / -> download
#include <stdio.h>
#include <unistd.h>
#include <getopt.h>
#include <string.h>
#include <ctype.h>
#include <stdbool.h>
#include <time.h>

#include <libxml/tree.h>
#include <libxml/xpath.h>
#include <libxml/xpathInternals.h>
#include <libxml/uri.h>
#include <libxslt/transform.h>

#include "s1kd_tools.h"
#include "xsl.h"

#define PROG_NAME "s1kd-fmgen"
#define VERSION "3.6.0"

#define ERR_PREFIX PROG_NAME ": ERROR: "
#define INF_PREFIX PROG_NAME ": INFO: "

#define EXIT_BAD_DATE 1
#define EXIT_NO_TYPE 2
#define EXIT_BAD_TYPE 3
#define EXIT_MERGE 4
#define EXIT_BAD_STYLESHEET 5

#define S_NO_PM_ERR ERR_PREFIX "No publication module.\n"
#define S_NO_TYPE_ERR ERR_PREFIX "No FM type specified.\n"
#define S_BAD_TYPE_ERR ERR_PREFIX "Unknown front matter type: %s\n"
#define E_BAD_LIST ERR_PREFIX "Could not read list: %s\n"
#define E_BAD_DATE ERR_PREFIX "Bad date: %s\n"
#define E_MERGE_NAME ERR_PREFIX "Failed to update %s: no <%s> element to merge on.\n"
#define E_MERGE_ELEM ERR_PREFIX "Failed to update %s: no front matter contents generated.\n"
#define E_BAD_STYLESHEET ERR_PREFIX "Failed to update %s: %s is not a valid stylesheet.\n"
#define I_GENERATE INF_PREFIX "Generating FM content for %s (%s)...\n"
#define I_NO_INFOCODE INF_PREFIX "Skipping %s as no FM type is associated with info code: %s%s\n"
#define I_TRANSFORM INF_PREFIX "Applying transformation %s...\n"
#define I_XPROC_TRANSFORM INF_PREFIX "Applying XProc transforation %s/%s...\n"
#define I_XPROC_TRANSFORM_NONAME INF_PREFIX "Applying XProc transformation %s/line %ld...\n"

static enum verbosity { QUIET, NORMAL, VERBOSE, DEBUG } verbosity = NORMAL;

static xmlNodePtr first_xpath_node(xmlDocPtr doc, xmlNodePtr node, const xmlChar *expr)
{
	xmlXPathContextPtr ctx;
	xmlXPathObjectPtr obj;
	xmlNodePtr first;

	ctx = xmlXPathNewContext(doc ? doc : node->doc);
	ctx->node = node;

	obj = xmlXPathEvalExpression(BAD_CAST expr, ctx);

	first = xmlXPathNodeSetIsEmpty(obj->nodesetval) ? NULL : obj->nodesetval->nodeTab[0];

	xmlXPathFreeObject(obj);
	xmlXPathFreeContext(ctx);

	return first;
}

static xmlChar *first_xpath_string(xmlDocPtr doc, xmlNodePtr node, const xmlChar *expr)
{
	return xmlNodeGetContent(first_xpath_node(doc, node, expr));
}

/* Determine whether a set of parameters already contains a name. */
static bool has_param(const char **params, const char *name)
{
	int i;

	for (i = 0; params[i]; i += 2) {
		if (strcmp(params[i], name) == 0) {
			return true;
		}
	}

	return false;
}

/* Apply an XProc XSLT step to a document. */
static xmlDocPtr apply_xproc_xslt(const xmlDocPtr doc, const char *xslpath, const xmlNodePtr xslt, const char **params)
{
	xmlXPathContextPtr ctx;
	xmlXPathObjectPtr obj;
	xmlDocPtr res;
	const char **combined_params;
	int i, nparams = 0;

	if (verbosity >= DEBUG) {
		xmlChar *name = xmlGetProp(xslt, BAD_CAST "name");

		if (name) {
			fprintf(stderr, I_XPROC_TRANSFORM, xslpath, name);
		} else {
			fprintf(stderr, I_XPROC_TRANSFORM_NONAME, xslpath, xmlGetLineNo(xslt));
		}

		xmlFree(name);
	}

	ctx = xmlXPathNewContext(xslt->doc);
	xmlXPathRegisterNs(ctx, BAD_CAST "p", BAD_CAST "http://www.w3.org/ns/xproc");
	xmlXPathSetContextNode(xslt, ctx);

	/* Get number of current params. */
	for (i = 0; params[i]; i += 2, ++nparams);

	/* Combine the current params with additional params from the XSLT step. */
	obj = xmlXPathEvalExpression(BAD_CAST "p:with-param", ctx);
	if (xmlXPathNodeSetIsEmpty(obj->nodesetval)) {
		/* If there are no additional params, simply copy the current
		 * params. */
		combined_params = malloc((nparams * 2 + 1) * sizeof(char *));

		for (i = 0; params[i]; ++i) {
			combined_params[i] = strdup(params[i]);
		}

		combined_params[i] = NULL;
	} else {
		/* If there are additional params, copy the current params and
		 * then append the additional ones. */
		int j;

		combined_params = malloc(((nparams + obj->nodesetval->nodeNr) * 2 + 1) * sizeof(char *));

		for (j = 0; params[j]; ++j) {
			combined_params[j] = strdup(params[j]);
		}

		for (i = 0; i < obj->nodesetval->nodeNr; ++i) {
			char *name;

			name = (char *) xmlGetProp(obj->nodesetval->nodeTab[i], BAD_CAST "name");

			/* User-defined parameters override XProc parameters. */
			if (has_param(params, name)) {
				xmlFree(name);
			} else {
				char *select;

				select = (char *) xmlGetProp(obj->nodesetval->nodeTab[i], BAD_CAST "select");

				combined_params[j++] = name;
				combined_params[j++] = select;
			}
		}

		combined_params[j] = NULL;
	}
	xmlXPathFreeObject(obj);

	/* Read the XProc stylesheet input. */
	obj = xmlXPathEvalExpression(BAD_CAST "p:input[@port='stylesheet']/p:*", ctx);
	if (xmlXPathNodeSetIsEmpty(obj->nodesetval)) {
		res = xmlCopyDoc(doc, 1);
	} else {
		xmlNodePtr src = obj->nodesetval->nodeTab[0];
		xmlDocPtr s;

		if (xmlStrcmp(src->name, BAD_CAST "document") == 0) {
			xmlChar *href, *URI;
			href = xmlGetProp(obj->nodesetval->nodeTab[0], BAD_CAST "href");
			URI = xmlBuildURI(href, BAD_CAST xslpath);
			s = read_xml_doc((char *) URI);
			xmlFree(href);
			xmlFree(URI);
		} else if (xmlStrcmp(src->name, BAD_CAST "inline") == 0) {
			s = xmlNewDoc(BAD_CAST "1.0");
			xmlDocSetRootElement(s, xmlCopyNode(xmlFirstElementChild(src), 1));
		} else {
			s = NULL;
		}

		if (s) {
			xsltStylesheetPtr style;
			style = xsltParseStylesheetDoc(s);
			res   = xsltApplyStylesheet(style, doc, combined_params);
			xsltFreeStylesheet(style);
		} else {
			res = xmlCopyDoc(doc, 1);
		}
	}
	xmlXPathFreeObject(obj);

	xmlXPathFreeContext(ctx);

	for (i = 0; combined_params[i]; i += 2) {
		xmlFree((char *) combined_params[i]);
		xmlFree((char *) combined_params[i + 1]);
	}
	free(combined_params);

	return res;
}

static xmlDocPtr transform_doc(xmlDocPtr doc, const char *xslpath, const char **params)
{
	xmlDocPtr styledoc, res = NULL;
	xsltStylesheetPtr style;
	xmlXPathContextPtr ctx;
	xmlXPathObjectPtr obj;

	if (verbosity >= DEBUG) {
		fprintf(stderr, I_TRANSFORM, xslpath);
	}

	styledoc = read_xml_doc(xslpath);

	ctx = xmlXPathNewContext(styledoc);
	xmlXPathRegisterNs(ctx, BAD_CAST "p", BAD_CAST "http://www.w3.org/ns/xproc");
	obj = xmlXPathEvalExpression(BAD_CAST "/p:pipeline/p:xslt", ctx);

	if (xmlXPathNodeSetIsEmpty(obj->nodesetval)) {
		style = xsltParseStylesheetDoc(styledoc);
		res = xsltApplyStylesheet(style, doc, params);
		xsltFreeStylesheet(style);
		styledoc = NULL;
	} else {
		int i;
		xmlDocPtr d = xmlCopyDoc(doc, 1);

		for (i =0; i < obj->nodesetval->nodeNr; ++i) {
			res = apply_xproc_xslt(d, xslpath, obj->nodesetval->nodeTab[i], params);
			xmlFreeDoc(d);
			d = res;
		}
	}

	xmlXPathFreeObject(obj);
	xmlXPathFreeContext(ctx);

	xmlFreeDoc(styledoc);

	return res;
}

static xmlDocPtr transform_doc_builtin(xmlDocPtr doc, unsigned char *xsl, unsigned int len, const char **params)
{
	xmlDocPtr styledoc, res;
	xsltStylesheetPtr style;

	styledoc = read_xml_mem((const char *) xsl, len);

	style = xsltParseStylesheetDoc(styledoc);

	res = xsltApplyStylesheet(style, doc, params);

	xsltFreeStylesheet(style);

	return res;
}

static void get_builtin_xsl(const char *type, unsigned char **xsl, unsigned int *len)
{
	if (strcmp(type, "TP") == 0) {
		*xsl = xsl_tp_xsl;
		*len = xsl_tp_xsl_len;
	} else if (strcmp(type, "TOC") == 0) {
		*xsl = xsl_toc_xsl;
		*len = xsl_toc_xsl_len;
	} else if (strcmp(type, "HIGH") == 0) {
		*xsl = xsl_high_xsl;
		*len = xsl_high_xsl_len;
	} else if (strcmp(type, "LOEDM") == 0) {
		*xsl = xsl_loedm_xsl;
		*len = xsl_loedm_xsl_len;
	} else if (strcmp(type, "LOA") == 0) {
		*xsl = xsl_loa_xsl;
		*len = xsl_loa_xsl_len;
	} else if (strcmp(type, "LOASD") == 0) {
		*xsl = xsl_loasd_xsl;
		*len = xsl_loasd_xsl_len;
	} else if (strcmp(type, "LOI") == 0) {
		*xsl = xsl_loi_xsl;
		*len = xsl_loi_xsl_len;
	} else if (strcmp(type, "LOS") == 0) {
		*xsl = xsl_los_xsl;
		*len = xsl_los_xsl_len;
	} else if (strcmp(type, "LOT") == 0) {
		*xsl = xsl_lot_xsl;
		*len = xsl_lot_xsl_len;
	} else if (strcmp(type, "LOTBL") == 0) {
		*xsl = xsl_lotbl_xsl;
		*len = xsl_lotbl_xsl_len;
	} else {
		if (verbosity >= NORMAL) {
			fprintf(stderr, S_BAD_TYPE_ERR, type);
		}
		exit(EXIT_BAD_TYPE);
	}
}

static xmlDocPtr generate_fm(xmlDocPtr doc, const char *type, const char **params)
{
	unsigned char *xsl;
	unsigned int len;

	get_builtin_xsl(type, &xsl, &len);

	return transform_doc_builtin(doc, xsl, len, params);
}

static void set_def_param(const char **params, int i, const char *val)
{
	char *s;
	s = malloc(strlen(val) + 3);
	sprintf(s, "'%s'", val);
	free((char *) params[i]);
	params[i] = s;
}

/* Default types of frontmatter where "deleted" objects and elements should be
 * ignored. */
static bool default_ignore_del(const char *type)
{
	return
		strcmp(type, "LOA")   == 0 ||
		strcmp(type, "LOASD") == 0 ||
		strcmp(type, "LOEDM") == 0 ||
		strcmp(type, "LOI")   == 0 ||
		strcmp(type, "LOS")   == 0 ||
		strcmp(type, "LOT")   == 0 ||
		strcmp(type, "LOTBL") == 0 ||
		strcmp(type, "TOC")   == 0 ||
		strcmp(type, "TP")    == 0;
}

/* Remove "deleted" DMs from the flattend PM format. */
static void rem_deleted_dmodules(xmlDocPtr doc)
{
	xmlXPathContextPtr ctx;
	xmlXPathObjectPtr obj;

	ctx = xmlXPathNewContext(doc);
	obj = xmlXPathEvalExpression(BAD_CAST "//dmodule[identAndStatusSection/dmStatus/@issueType='deleted' or status/issno/@isstype='deleted']", ctx);

	if (!xmlXPathNodeSetIsEmpty(obj->nodesetval)) {
		int i;
		for (i = 0; i < obj->nodesetval->nodeNr; ++i) {
			xmlUnlinkNode(obj->nodesetval->nodeTab[i]);
			xmlFreeNode(obj->nodesetval->nodeTab[i]);
			obj->nodesetval->nodeTab[i] = NULL;
		}
	}

	xmlXPathFreeObject(obj);
	xmlXPathFreeContext(ctx);
}

static xmlDocPtr generate_fm_content_for_type(xmlDocPtr doc, const char *type, const char *fmxsl, const char *xslpath, const char **params, const bool ignore_del)
{
	xmlDocPtr src, res = NULL;
	int i = 0;

	/* Supply values to default parameters. */
	for (i = 0; params[i]; i += 2) {
		if (strcmp(params[i], "type") == 0) {
			set_def_param(params, i + 1, type);
		}
	}

	/* Certain types of FM should ignore "deleted" objects/elements. */
	if (ignore_del) {
		src = xmlCopyDoc(doc, 1);
		rem_deleted_dmodules(src);
		rem_delete_elems(src);
	} else {
		src = doc;
	}

	/* Generate contents. */
	if (xslpath) {
		res = transform_doc(src, xslpath, params);
	} else if (fmxsl) {
		res = transform_doc(src, fmxsl, params);
	} else {
		res = generate_fm(src, type, params);
	}

	if (ignore_del) {
		xmlFreeDoc(src);
	}

	return res;
}

static xmlNodePtr find_fm(xmlDocPtr fmtypes, const xmlChar *incode, const xmlChar *incodev)
{
	xmlXPathContextPtr ctx;
	xmlXPathObjectPtr obj;
	xmlNodePtr node;

	ctx = xmlXPathNewContext(fmtypes);
	xmlXPathRegisterVariable(ctx, BAD_CAST "c", xmlXPathNewString(BAD_CAST incode));
	xmlXPathRegisterVariable(ctx, BAD_CAST "v", xmlXPathNewString(BAD_CAST incodev));

	obj = xmlXPathEvalExpression(BAD_CAST "//fm[starts-with(concat($c,$v),@infoCode)]", ctx);

	if (xmlXPathNodeSetIsEmpty(obj->nodesetval)) {
		node = NULL;
	} else {
		node = obj->nodesetval->nodeTab[0];
	}

	xmlXPathFreeObject(obj);
	xmlXPathFreeContext(ctx);

	return node;
}

/* Copy elements from the source TP DM that can't be derived from the PM. */
static void copy_tp_elems(xmlDocPtr res, xmlDocPtr doc)
{
	xmlNodePtr fmtp, node;

	fmtp = first_xpath_node(res, NULL, BAD_CAST "//frontMatterTitlePage");

	if (!fmtp) {
		return;
	}

	if ((node = first_xpath_node(doc, NULL, BAD_CAST "//productIntroName"))) {
		xmlAddPrevSibling(first_xpath_node(res, fmtp,
			BAD_CAST "pmTitle"),
			xmlCopyNode(node, 1));
	}

	if ((node = first_xpath_node(doc, NULL, BAD_CAST "//externalPubCode"))) {
		xmlAddNextSibling(first_xpath_node(res, fmtp,
			BAD_CAST "issueDate"),
			xmlCopyNode(node, 1));
	}

	if ((node = first_xpath_node(doc, NULL, BAD_CAST "//productAndModel"))) {
		xmlAddNextSibling(first_xpath_node(res, fmtp,
			BAD_CAST "(issueDate|externalPubCode)[last()]"),
			xmlCopyNode(node, 1));
	}

	if ((node = first_xpath_node(doc, NULL, BAD_CAST "//productIllustration"))) {
		xmlAddNextSibling(first_xpath_node(res, fmtp,
			BAD_CAST "(security|derivativeClassification|dataRestrictions)[last()]"),
			xmlCopyNode(node, 1));
	}

	if ((node = first_xpath_node(doc, NULL, BAD_CAST "//enterpriseSpec"))) {
		xmlAddNextSibling(first_xpath_node(res, fmtp,
			BAD_CAST "(security|derivativeClassification|dataRestrictions|productIllustration)[last()]"),
			xmlCopyNode(node, 1));
	}

	if ((node = first_xpath_node(doc, NULL, BAD_CAST "//enterpriseLogo"))) {
		xmlAddNextSibling(first_xpath_node(res, fmtp,
			BAD_CAST "(security|derivativeClassification|dataRestrictions|productIllustration|enterpriseSpec)[last()]"),
			xmlCopyNode(node, 1));
	}

	if ((node = first_xpath_node(doc, NULL, BAD_CAST "//barCode"))) {
		xmlAddNextSibling(first_xpath_node(res, fmtp,
			BAD_CAST "(responsiblePartnerCompany|publisherLogo)[last()]"),
			xmlCopyNode(node, 1));
	}

	if ((node = first_xpath_node(doc, NULL, BAD_CAST "//frontMatterInfo"))) {
		xmlAddNextSibling(first_xpath_node(res, fmtp,
			BAD_CAST "(responsiblePartnerCompany|publisherLogo|barCode)[last()]"),
			xmlCopyNode(node, 1));
	}
}

static void set_issue_date(xmlDocPtr doc, xmlDocPtr pm, const char *issdate)
{
	xmlNodePtr dm_issue_date;
	xmlChar *year, *month, *day;

	if (!(dm_issue_date = first_xpath_node(doc, NULL, BAD_CAST "//issueDate|//issdate"))) {
		return;
	}

	if (strcasecmp(issdate, "pm") == 0) {
		xmlNodePtr pm_issue_date;

		if (!(pm_issue_date = first_xpath_node(pm, NULL, BAD_CAST "//issueDate|//issdate"))) {
			return;
		}

		year  = xmlGetProp(pm_issue_date, BAD_CAST "year");
		month = xmlGetProp(pm_issue_date, BAD_CAST "month");
		day   = xmlGetProp(pm_issue_date, BAD_CAST "day");
	} else {
		year  = malloc(5 * sizeof(xmlChar));
		month = malloc(3 * sizeof(xmlChar));
		day   = malloc(3 * sizeof(xmlChar));

		if (strcmp(issdate, "-") == 0) {
			time_t now;
			struct tm *local;

			time(&now);
			local = localtime(&now);

			xmlStrPrintf(year, 5, "%.4d", local->tm_year + 1900);
			xmlStrPrintf(month, 3, "%.2d", local->tm_mon + 1);
			xmlStrPrintf(day, 3, "%.2d", local->tm_mday);
		} else {
			int n;

			n = sscanf(issdate, "%4s-%2s-%2s", year, month, day);

			if (n != 3) {
				if (verbosity >= NORMAL) {
					fprintf(stderr, E_BAD_DATE, issdate);
				}
				exit(EXIT_BAD_DATE);
			}
		}
	}

	xmlSetProp(dm_issue_date, BAD_CAST "year", year);
	xmlSetProp(dm_issue_date, BAD_CAST "month", month);
	xmlSetProp(dm_issue_date, BAD_CAST "day", day);

	xmlFree(year);
	xmlFree(month);
	xmlFree(day);
}

static void generate_fm_content_for_dm(
	xmlDocPtr pm,
	const char *dmpath,
	xmlDocPtr fmtypes,
	const char *fmtype,
	bool overwrite,
	const char *xslpath,
	const char **params,
	const char *issdate)
{
	xmlDocPtr doc, res = NULL;
	char *type, *fmxsl;
	bool ignore_del;

	if (!(doc = read_xml_doc(dmpath))) {
		return;
	}

	if (fmtype) {
		type  = strdup(fmtype);
		fmxsl = NULL;
		ignore_del = default_ignore_del(type);
	} else {
		xmlChar *incode, *incodev;
		xmlNodePtr fm;

		incode  = first_xpath_string(doc, NULL, BAD_CAST "//@infoCode|//incode");
		incodev = first_xpath_string(doc, NULL, BAD_CAST "//@infoCodeVariant|//incodev");

		fm = find_fm(fmtypes, incode, incodev);

		if (fm) {
			xmlChar *igndel;

			type  = (char *) xmlGetProp(fm, BAD_CAST "type");
			fmxsl = (char *) xmlGetProp(fm, BAD_CAST "xsl");

			if ((igndel = xmlGetProp(fm, BAD_CAST "ignoreDel"))) {
				ignore_del = xmlStrcmp(igndel, BAD_CAST "yes") == 0;
			} else {
				ignore_del = default_ignore_del(type);
			}

			xmlFree(igndel);
		} else {
			if (verbosity >= DEBUG) {
				fprintf(stderr, I_NO_INFOCODE, dmpath, incode, incodev);
			}

			type = NULL;
			fmxsl = NULL;
			ignore_del = false;
		}

		xmlFree(incode);
		xmlFree(incodev);
	}

	if (type) {
		xmlNodePtr root;

		if (verbosity >= VERBOSE) {
			fprintf(stderr, I_GENERATE, dmpath, type);
		}

		if (!(res = generate_fm_content_for_type(pm, type, fmxsl, xslpath, params, ignore_del))) {
			if (verbosity >= NORMAL) {
				fprintf(stderr, E_BAD_STYLESHEET, dmpath, xslpath ? xslpath : fmxsl);
			}
			exit(EXIT_BAD_STYLESHEET);
		}

		if (strcmp(type, "TP") == 0) {
			copy_tp_elems(res, doc);
		}

		/* Merge the results of the transformation with the original DM
		 * based on the name of the root element. */
		if ((root = xmlDocGetRootElement(res))) {
			xmlXPathContextPtr ctx;
			xmlXPathObjectPtr obj;

			ctx = xmlXPathNewContext(doc);
			xmlXPathRegisterVariable(ctx, BAD_CAST "name", xmlXPathNewString(root->name));
			obj = xmlXPathEvalExpression(BAD_CAST "//*[name()=$name]", ctx);

			if (xmlXPathNodeSetIsEmpty(obj->nodesetval)) {
				if (verbosity >= NORMAL) {
					fprintf(stderr, E_MERGE_NAME, dmpath, (char *) root->name);
				}
				exit(EXIT_MERGE);
			} else {
				xmlAddNextSibling(obj->nodesetval->nodeTab[0], xmlCopyNode(root, 1));
				xmlUnlinkNode(obj->nodesetval->nodeTab[0]);
				xmlFreeNode(obj->nodesetval->nodeTab[0]);
				obj->nodesetval->nodeTab[0] = NULL;
			}

			xmlXPathFreeObject(obj);
			xmlXPathFreeContext(ctx);
		} else {
			if (verbosity >= NORMAL) {
				fprintf(stderr, E_MERGE_ELEM, dmpath);
			}
			exit(EXIT_MERGE);
		}

		if (issdate) {
			set_issue_date(doc, pm, issdate);
		}

		if (overwrite) {
			save_xml_doc(doc, dmpath);
		} else {
			save_xml_doc(doc, "-");
		}
	}

	xmlFree(type);
	xmlFree(fmxsl);
	xmlFreeDoc(doc);
	xmlFreeDoc(res);
}

static void generate_fm_content_for_list(
	xmlDocPtr pm,
	const char *path,
	xmlDocPtr fmtypes,
	const char *fmtype,
	bool overwrite,
	const char *xslpath,
	const char **params,
	const char *issdate)
{
	FILE *f;
	char line[PATH_MAX];

	if (path) {
		if (!(f = fopen(path, "r"))) {
			if (verbosity >= NORMAL) {
				fprintf(stderr, E_BAD_LIST, path);
			}
			return;
		}
	} else {
		f = stdin;
	}

	while (fgets(line, PATH_MAX, f)) {
		strtok(line, "\t\r\n");
		generate_fm_content_for_dm(pm, line, fmtypes, fmtype, overwrite, xslpath, params, issdate);
	}

	if (path) {
		fclose(f);
	}
}

static void dump_fmtypes_xml(void)
{
	xmlDocPtr doc;
	doc = read_xml_mem((const char *) fmtypes_xml, fmtypes_xml_len);
	save_xml_doc(doc, "-");
	xmlFreeDoc(doc);
}

static void dump_fmtypes_txt(void)
{
	printf("%.*s", fmtypes_txt_len, fmtypes_txt);
}

static void dump_builtin_xsl(const char *type)
{
	unsigned char *xsl;
	unsigned int len;

	get_builtin_xsl(type, &xsl, &len);

	printf("%.*s", len, xsl);
}

static void fix_fmxsl_paths(xmlDocPtr doc, const char *path)
{
	xmlXPathContextPtr ctx;
	xmlXPathObjectPtr obj;

	ctx = xmlXPathNewContext(doc);
	obj = xmlXPathEvalExpression(BAD_CAST "//@xsl", ctx);

	if (!xmlXPathNodeSetIsEmpty(obj->nodesetval)) {
		int i;

		for (i = 0; i < obj->nodesetval->nodeNr; ++i) {
			xmlChar *xsl = xmlNodeGetContent(obj->nodesetval->nodeTab[i]);
			xmlChar *URI = xmlBuildURI(xsl, BAD_CAST path);
			xmlNodeSetContent(obj->nodesetval->nodeTab[i], URI);
			xmlFree(xsl);
			xmlFree(URI);
		}
	}

	xmlXPathFreeObject(obj);
	xmlXPathFreeContext(ctx);
}

static xmlDocPtr read_fmtypes(const char *path)
{
	xmlDocPtr doc;

	if (!(doc = read_xml_doc(path))) {
		FILE *f;
		char line[256];
		xmlNodePtr root;

		doc = xmlNewDoc(BAD_CAST "1.0");
		root = xmlNewNode(NULL, BAD_CAST "fmtypes");
		xmlDocSetRootElement(doc, root);

		f = fopen(path, "r");

		while (fgets(line, 256, f)) {
			char incode[5] = "", type[32] = "", xsl[1024] = "";
			xmlNodePtr fm;
			int n;

			n = sscanf(line, "%4s %31s %1023[^\n]", incode, type, xsl);

			fm = xmlNewChild(root, NULL, BAD_CAST "fm", NULL);
			xmlSetProp(fm, BAD_CAST "infoCode", BAD_CAST incode);
			xmlSetProp(fm, BAD_CAST "type", BAD_CAST type);
			if (n == 3) {
				xmlSetProp(fm, BAD_CAST "xsl", BAD_CAST xsl);
			}
		}

		fclose(f);
	}

	fix_fmxsl_paths(doc, path);

	return doc;
}

static void add_param(xmlNodePtr params, char *s)
{
	char *n, *v;
	xmlNodePtr p;

	n = strtok(s, "=");
	v = strtok(NULL, "");

	p = xmlNewChild(params, NULL, BAD_CAST "param", NULL);
	xmlSetProp(p, BAD_CAST "name", BAD_CAST n);
	xmlSetProp(p, BAD_CAST "value", BAD_CAST v);
}

static void add_def_param(xmlNodePtr params, const char *s)
{
	xmlNodePtr p;
	p = xmlNewChild(params, NULL, BAD_CAST "param", NULL);
	xmlSetProp(p, BAD_CAST "name", BAD_CAST s);
	xmlSetProp(p, BAD_CAST "value", BAD_CAST "");
}

static void show_help(void)
{
	puts("Usage: " PROG_NAME " [-D <TYPE>] [-F <FMTYPES>] [-I <date>] [-P <PM>] [-p <name>=<val> ...] [-t <TYPE>] [-x <XSL>] [-,flqvh?] [<DM>...]");
	puts("");
	puts("Options:");
	puts("  -,, --dump-fmtypes-xml      Dump the built-in .fmtypes file in XML format.");
	puts("  -., --dump-fmtypes          Dump the built-in .fmtypes file in simple text format.");
	puts("  -D, --dump-xsl <TYPE>       Dump the built-in XSLT for a type of front matter.");
	puts("  -F, --fmtypes <FMTYPES>     Specify .fmtypes file.");
	puts("  -f, --overwrite             Overwrite input data modules.");
	puts("  -h, -?, --help              Show usage message.");
	puts("  -I, --date <date>           Set the issue date of the generated front matter.");
	puts("  -l, --list                  Treat input as list of data modules.");
	puts("  -P, --pm <PM>               Generate front matter from the specified PM.");
	puts("  -p, --param <name>=<value>  Pass parameters to the XSLT used to generate the front matter.");
	puts("  -q, --quiet                 Quiet mode.");
	puts("  -t, --type <TYPE>           Generate the specified type of front matter.");
	puts("  -v, --verbose               Verbose output.");
	puts("  -x, --xsl <XSL>             Override built-in or user-configured XSLT.");
	puts("  --version                   Show version information.");
	puts("  <DM>                        Generate front matter content based on the specified data modules.");
	LIBXML2_PARSE_LONGOPT_HELP
}

static void show_version(void)
{
	printf("%s (s1kd-tools) %s\n", PROG_NAME, VERSION);
	printf("Using libxml %s and libxslt %s\n", xmlParserVersion, xsltEngineVersion);
}

int main(int argc, char **argv)
{
	int i;

	const char *sopts = ",.D:F:fI:lP:p:qt:vx:h?";
	struct option lopts[] = {
		{"version"         , no_argument      , 0, 0},
		{"help"            , no_argument      , 0, 'h'},
		{"dump-fmtypes-xml", no_argument      , 0, ','},
		{"dump-fmtypes"    , no_argument      , 0, '.'},
		{"dump-xsl"        , required_argument, 0, 'D'},
		{"fmtypes"         , required_argument, 0, 'F'},
		{"date"            , required_argument, 0, 'I'},
		{"overwrite"       , no_argument      , 0, 'f'},
		{"list"            , no_argument      , 0, 'l'},
		{"pm"              , required_argument, 0, 'P'},
		{"param"           , required_argument, 0, 'p'},
		{"quiet"           , no_argument      , 0, 'q'},
		{"type"            , required_argument, 0, 't'},
		{"verbose"         , no_argument      , 0, 'v'},
		{"xsl"             , required_argument, 0, 'x'},
		LIBXML2_PARSE_LONGOPT_DEFS
		{0, 0, 0, 0}
	};
	int loptind = 0;

	char *pmpath = NULL;
	char *fmtype = NULL;

	xmlDocPtr pm, fmtypes = NULL;

	bool overwrite = false;
	bool islist = false;
	char *xslpath = NULL;
	xmlNodePtr params_node;
	int nparams = 0;
	const char **params = NULL;
	char *issdate = NULL;

	params_node = xmlNewNode(NULL, BAD_CAST "params");

	/* Default parameter placeholders. */
	add_def_param(params_node, "type"); ++nparams;

	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 ',':
				dump_fmtypes_xml();
				return 0;
			case '.':
				dump_fmtypes_txt();
				return 0;
			case 'D':
				dump_builtin_xsl(optarg);
				return 0;
			case 'F':
				if (!fmtypes) {
					fmtypes = read_fmtypes(optarg);
				}
				break;
			case 'f':
				overwrite = true;
				break;
			case 'I':
				issdate = strdup(optarg);
				break;
			case 'l':
				islist = true;
				break;
			case 'P':
				pmpath = strdup(optarg);
				break;
			case 'p':
				add_param(params_node, optarg);
				++nparams;
				break;
			case 'q':
				--verbosity;
				break;
			case 't':
				fmtype = strdup(optarg);
				break;
			case 'v':
				++verbosity;
				break;
			case 'x':
				xslpath = strdup(optarg);
				break;
			case 'h':
			case '?':
				show_help();
				return 0;
		}
	}

	if (nparams > 0) {
		xmlNodePtr p;
		int n = 0;

		params = malloc((nparams * 2 + 1) * sizeof(char *));

		for (p = params_node->children; p; p = p->next) {
			char *name, *value;

			name  = (char *) xmlGetProp(p, BAD_CAST "name");
			value = (char *) xmlGetProp(p, BAD_CAST "value");

			params[n++] = name;
			params[n++] = value;
		}

		params[n] = NULL;
	}
	xmlFreeNode(params_node);

	if (!fmtypes) {
		char fmtypes_fname[PATH_MAX];

		if (find_config(fmtypes_fname, DEFAULT_FMTYPES_FNAME)) {
			fmtypes = read_fmtypes(fmtypes_fname);
		} else {
			fmtypes = read_xml_mem((const char *) fmtypes_xml, fmtypes_xml_len);
		}
	}

	if (!pmpath) {
		pmpath = strdup("-");
	}

	pm = read_xml_doc(pmpath);

	if (optind < argc) {
		void (*gen_fn)(xmlDocPtr, const char *, xmlDocPtr, const char *,
			bool, const char *, const char **, const char *);

		if (islist) {
			gen_fn = generate_fm_content_for_list;
		} else {
			gen_fn = generate_fm_content_for_dm;
		}

		for (i = optind; i < argc; ++i) {
			gen_fn(pm, argv[i], fmtypes, fmtype, overwrite, xslpath, params, issdate);
		}
	} else if (fmtype) {
		xmlDocPtr res;
		res = generate_fm_content_for_type(pm, fmtype, NULL, xslpath, params, default_ignore_del(fmtype));
		save_xml_doc(res, "-");
		xmlFreeDoc(res);
	} else if (islist) {
		generate_fm_content_for_list(pm, NULL, fmtypes, fmtype, overwrite, xslpath, params, issdate);
	} else {
		if (verbosity >= NORMAL) {
			fprintf(stderr, S_NO_TYPE_ERR);
		}
		exit(EXIT_NO_TYPE);
	}

	for (i = 0; i < nparams; ++i) {
		xmlFree((char *) params[i * 2]);
		xmlFree((char *) params[i * 2 + 1]);
	}
	free(params);

	xmlFreeDoc(pm);
	xmlFreeDoc(fmtypes);

	free(pmpath);
	free(fmtype);
	free(xslpath);

	xmlFree(issdate);

	xsltCleanupGlobals();
	xmlCleanupParser();

	return 0;
}


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