/ .. / / -> download
#include <stdio.h>
#include <unistd.h>
#include <getopt.h>
#include <stdbool.h>
#include <string.h>
#include <libxml/tree.h>
#include <libxml/xpath.h>
#include <libxslt/transform.h>
#include <libexslt/exslt.h>
#include "s1kd_tools.h"
#include "resources.h"

#define PROG_NAME "s1kd-aspp"
#define VERSION "5.0.1"

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

#define E_BAD_LIST ERR_PREFIX "Could not read list: %s\n"

#define W_MISSING_REF WRN_PREFIX "Could not read referenced object: %s\n"

#define I_PROCESS INF_PREFIX "Processing %s...\n"

/* ID for the inline <applic> element representing the whole data module's
 * applicability. */
#define DEFAULT_DM_APPLIC_ID BAD_CAST "app-0000"
static xmlChar *dmApplicId;

/* XPath to select all elements which may have applicability annotations.
 *
 * Read from elements_list.h*/
static xmlChar *applicElemsXPath;

/* Search for ACTs/CCTs recursively. */
static bool recursive_search = false;

/* Directory to start search for ACTs/CCTs in. */
static char *search_dir;

/* Overwrite existing display text in annotations. */
static bool overwriteDispText = true;

/* Verbose output. */
static enum verbosity { QUIET, NORMAL, VERBOSE } verbosity = NORMAL;

/* Assume objects were created with -N. */
static bool no_issue = false;

/* Delimiter for format strings. */
#define FMTSTR_DELIM '%'

/* Return the first node matching an XPath expression. */
static xmlNodePtr firstXPathNode(xmlDocPtr doc, xmlNodePtr node, const char *xpath)
{
	xmlXPathContextPtr ctx;
	xmlXPathObjectPtr obj;
	xmlNodePtr first;

	if (doc) {
		ctx = xmlXPathNewContext(doc);
	} else if (node) {
		ctx = xmlXPathNewContext(node->doc);
	} else {
		return NULL;
	}

	ctx->node = node;

	obj = xmlXPathEvalExpression(BAD_CAST xpath, ctx);

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

	xmlXPathFreeObject(obj);
	xmlXPathFreeContext(ctx);

	return first;
}

/* Return the last node matching an XPath expression. */
static xmlNodePtr lastXPathNode(xmlDocPtr doc, xmlNodePtr node, const char *xpath)
{
	xmlXPathContextPtr ctx;
	xmlXPathObjectPtr obj;
	xmlNodePtr last;

	if (doc) {
		ctx = xmlXPathNewContext(doc);
	} else if (node) {
		ctx = xmlXPathNewContext(node->doc);
	} else {
		return NULL;
	}

	ctx->node = node;

	obj = xmlXPathEvalExpression(BAD_CAST xpath, ctx);

	if (xmlXPathNodeSetIsEmpty(obj->nodesetval)) {
		last = NULL;
	} else {
		last = obj->nodesetval->nodeTab[obj->nodesetval->nodeNr - 1];
	}

	xmlXPathFreeObject(obj);
	xmlXPathFreeContext(ctx);

	return last;
}

/* Return the value of the first node matching an XPath expression. */
static xmlChar *first_xpath_value(xmlDocPtr doc, xmlNodePtr node, const char *xpath)
{
	return xmlNodeGetContent(firstXPathNode(doc, node, xpath));
}

/* Set an explicit applicability annotation on a node.
 *
 * Nodes with the attribute @applicRefId already have an explicit annotation.
 *
 * Nodes without @applicRefId have an implicit annotation inherited from their
 * parent/ancestor which does have an explicit annotation, or ultimately from
 * the applicability of the whole data module. */
static void processNode(xmlNodePtr node)
{
	xmlNodePtr attr;

	attr = firstXPathNode(NULL, node, "@applicRefId|@refapplic");

	if (!attr) {
		/* Inherit applicability from
		 * - an ancestor, or
		 * - the whole data module level */
		xmlNodePtr ancestor;
		xmlChar *name;

		ancestor = lastXPathNode(NULL, node, "ancestor::*[@applicRefId]|ancestor::*[@refapplic]");

		name = BAD_CAST (firstXPathNode(NULL, node, "//idstatus") ? "refapplic" : "applicRefId");

		if (!ancestor) {
			xmlSetProp(node, name, dmApplicId);
		} else {
			xmlChar *ancestorApplic = xmlGetProp(ancestor, name);
			xmlSetProp(node, name, ancestorApplic);
			xmlFree(ancestorApplic);
		}
	}
}

/* Set explicit applicability on all nodes in a nodeset. */
static void processNodeSet(xmlNodeSetPtr nodes)
{
	int i;

	for (i = 0; i < nodes->nodeNr; ++i) {
		processNode(nodes->nodeTab[i]);
	}
}

/* Remove duplicate applicability annotations in document-order so that an
 * annotation is only shown when applicability changes. */
static void removeDuplicates(xmlNodeSetPtr nodes)
{
	int i;

	xmlChar *applic = xmlStrdup(dmApplicId);

	for (i = 0; i < nodes->nodeNr; ++i) {
		xmlNodePtr attr;
		xmlChar *applicRefId;

		attr = firstXPathNode(NULL, nodes->nodeTab[i], "@applicRefId|@refapplic");
		applicRefId = xmlNodeGetContent(attr);

		if (xmlStrcmp(applicRefId, applic) == 0) {
			xmlUnsetProp(nodes->nodeTab[i], attr->name);
		} else {
			xmlFree(applic);
			applic = xmlStrdup(applicRefId);
		}

		xmlFree(applicRefId);
	}

	xmlFree(applic);
}

/* Insert a new inline <applic> element representing the applicability of the
 * whole data module, based on the dmStatus/applic element. */
static void addDmApplic(xmlNodePtr dmodule)
{
	xmlNodePtr referencedApplicGroup;

	if ((referencedApplicGroup = firstXPathNode(NULL, dmodule, ".//referencedApplicGroup|.//inlineapplics"))) {
		xmlNodePtr applic;
		xmlNodePtr wholeDmApplic;

		wholeDmApplic = firstXPathNode(NULL, dmodule, ".//dmStatus/applic|.//status/applic");
		applic = xmlAddChild(referencedApplicGroup, xmlCopyNode(wholeDmApplic, 1));

		xmlSetProp(applic, BAD_CAST "id", dmApplicId);
	}
}

static xmlDocPtr parse_disptext(xmlDocPtr config)
{
	xmlDocPtr doc1, doc2, res;
	xsltStylesheetPtr style;

	doc1 = read_xml_mem((const char *) disptext_xsl, disptext_xsl_len);
	style = xsltParseStylesheetDoc(doc1);

	doc2 = xsltApplyStylesheet(style, config, NULL);
	xsltFreeStylesheet(style);

	res = xmlCopyDoc(doc2, 1);
	xmlFreeDoc(doc2);

	return res;
}

static void dumpGenDispTextXsl(void)
{
	xmlDocPtr doc1, doc2;
	doc1 = read_xml_mem((const char *) disptext_xml, disptext_xml_len);
	doc2 = parse_disptext(doc1);
	xmlFreeDoc(doc1);
	save_xml_doc(doc2, "-");
	xmlFreeDoc(doc2);
}

static void dumpDispText(void)
{
	printf("%.*s", disptext_xml_len, disptext_xml);
}

/* Customize the display text based on a format string. */
static void apply_format_str(xmlDocPtr doc, const char *fmt)
{
	xmlNodePtr t, c;
	int i;

	/* Get the assert text template. */
	t = firstXPathNode(doc, NULL, "//*[@match='assert' and @mode='text']");

	if (!t) {
		return;
	}

	/* Clear the template. */
	c = t->children;
	while (c) {
		xmlNodePtr n;
		n = c->next;
		xmlUnlinkNode(c);
		xmlFreeNode(c);
		c = n;
	}

	/* Parse the format string and generate the new template. */
	for (i = 0; fmt[i]; ++i) {
		xmlChar s[2] = {0};

		if (fmt[i] == FMTSTR_DELIM) {
			if (fmt[i + 1] == FMTSTR_DELIM) {
				s[0] = FMTSTR_DELIM;
				xmlNewChild(t, t->nsDef, BAD_CAST "text", s);
				++i;
			} else {
				const char *k, *e;
				int n;

				k = fmt + i + 1;
				e = strchr(k, FMTSTR_DELIM);
				if (!e) break;
				n = e - k;

				if (strncmp(k, "name", n) == 0) {
					c = xmlNewChild(t, t->nsDef, BAD_CAST "call-template", NULL);
					xmlSetProp(c, BAD_CAST "name", BAD_CAST "applicPropertyName");
				} else if (strncmp(k, "values", n) == 0) {
					c = xmlNewChild(t, t->nsDef, BAD_CAST "call-template", NULL);
					xmlSetProp(c, BAD_CAST "name", BAD_CAST "applicPropertyVal");
				}

				i += n + 1;
			}
		} else {
			if (fmt[i] == '\\') {
				switch (fmt[i + 1]) {
					case 'n': s[0] = '\n'; ++i; break;
					case 't': s[0] = '\t'; ++i; break;
					default: s[0] = fmt[i]; break;
				}
			} else {
				s[0] = fmt[i];
			}
			xmlNewChild(t, t->nsDef, BAD_CAST "text", s);
		}
	}
}

static void generateDisplayText(xmlDocPtr doc, xmlNodePtr acts, xmlNodePtr ccts, xsltStylesheetPtr style)
{
	xmlDocPtr res, muxdoc;
	xmlNodePtr mux, cur, muxacts, muxccts, new, old;
	const char *params[3];

	muxdoc = xmlNewDoc(BAD_CAST "1.0");
	mux = xmlNewNode(NULL, BAD_CAST "mux");
	xmlDocSetRootElement(muxdoc, mux);

	xmlAddChild(mux, xmlCopyNode(xmlDocGetRootElement(doc), 1));
	muxacts = xmlNewChild(mux, NULL, BAD_CAST "acts", NULL);
	muxccts = xmlNewChild(mux, NULL, BAD_CAST "ccts", NULL);
	for (cur = acts->children; cur; cur = cur->next) {
		xmlDocPtr act;
		xmlChar *path;
		path = xmlNodeGetContent(cur);
		act = read_xml_doc((char *) path);
		xmlAddChild(muxacts, xmlCopyNode(xmlDocGetRootElement(act), 1));
		xmlFreeDoc(act);
		xmlFree(path);
	}
	for (cur = ccts->children; cur; cur = cur->next) {
		xmlDocPtr cct;
		xmlChar *path;
		path = xmlNodeGetContent(cur);
		cct = read_xml_doc((char *) path);
		xmlAddChild(muxccts, xmlCopyNode(xmlDocGetRootElement(cct), 1));
		xmlFreeDoc(cct);
		xmlFree(path);
	}

	params[0] = "overwrite-display-text";
	params[1] = overwriteDispText ? "true()" : "false()";
	params[2] = NULL;

	res = xsltApplyStylesheet(style, muxdoc, params);

	new = xmlCopyNode(firstXPathNode(res, NULL, "/mux/dmodule"), 1);
	old = xmlDocSetRootElement(doc, new);
	xmlFreeNode(old);

	xmlFreeDoc(res);
	xmlFreeDoc(muxdoc);
}

static void processDmodule(xmlNodePtr dmodule)
{
	xmlXPathContextPtr ctx;
	xmlXPathObjectPtr obj;

	ctx = xmlXPathNewContext(dmodule->doc);
	ctx->node = dmodule;
	obj = xmlXPathEvalExpression(applicElemsXPath, ctx);

	processNodeSet(obj->nodesetval);
	removeDuplicates(obj->nodesetval);
	addDmApplic(dmodule);

	xmlXPathFreeObject(obj);
	xmlXPathFreeContext(ctx);
}

static void processDmodules(xmlNodeSetPtr dmodules)
{
	int i;

	for (i = 0; i < dmodules->nodeNr; ++i) {
		processDmodule(dmodules->nodeTab[i]);
	}
}

/* Find a data module filename in the current directory based on the dmRefIdent
 * element. */
static bool find_dmod_fname(char *dst, xmlNodePtr dmRefIdent)
{
	char *model_ident_code;
	char *system_diff_code;
	char *system_code;
	char *sub_system_code;
	char *sub_sub_system_code;
	char *assy_code;
	char *disassy_code;
	char *disassy_code_variant;
	char *info_code;
	char *info_code_variant;
	char *item_location_code;
	char *learn_code;
	char *learn_event_code;
	char code[64];
	xmlNodePtr dmCode, issueInfo, language;

	dmCode = firstXPathNode(NULL, dmRefIdent, "dmCode|avee");
	issueInfo = firstXPathNode(NULL, dmRefIdent, "issueInfo|issno");
	language = firstXPathNode(NULL, dmRefIdent, "language");

	model_ident_code     = (char *) first_xpath_value(NULL, dmCode, "modelic|@modelIdentCode");
	system_diff_code     = (char *) first_xpath_value(NULL, dmCode, "sdc|@systemDiffCode");
	system_code          = (char *) first_xpath_value(NULL, dmCode, "chapnum|@systemCode");
	sub_system_code      = (char *) first_xpath_value(NULL, dmCode, "section|@subSystemCode");
	sub_sub_system_code  = (char *) first_xpath_value(NULL, dmCode, "subsect|@subSubSystemCode");
	assy_code            = (char *) first_xpath_value(NULL, dmCode, "subject|@assyCode");
	disassy_code         = (char *) first_xpath_value(NULL, dmCode, "discode|@disassyCode");
	disassy_code_variant = (char *) first_xpath_value(NULL, dmCode, "discodev|@disassyCodeVariant");
	info_code            = (char *) first_xpath_value(NULL, dmCode, "incode|@infoCode");
	info_code_variant    = (char *) first_xpath_value(NULL, dmCode, "incodev|@infoCodeVariant");
	item_location_code   = (char *) first_xpath_value(NULL, dmCode, "itemloc|@itemLocationCode");
	learn_code           = (char *) first_xpath_value(NULL, dmCode, "@learnCode");
	learn_event_code     = (char *) first_xpath_value(NULL, dmCode, "@learnEventCode");

	snprintf(code, 64, "DMC-%s-%s-%s-%s%s-%s-%s%s-%s%s-%s",
		model_ident_code,
		system_diff_code,
		system_code,
		sub_system_code,
		sub_sub_system_code,
		assy_code,
		disassy_code,
		disassy_code_variant,
		info_code,
		info_code_variant,
		item_location_code);

	xmlFree(model_ident_code);
	xmlFree(system_diff_code);
	xmlFree(system_code);
	xmlFree(sub_system_code);
	xmlFree(sub_sub_system_code);
	xmlFree(assy_code);
	xmlFree(disassy_code);
	xmlFree(disassy_code_variant);
	xmlFree(info_code);
	xmlFree(info_code_variant);
	xmlFree(item_location_code);

	if (learn_code) {
		char learn[8];
		snprintf(learn, 8, "-%s%s", learn_code, learn_event_code);
		strcat(code, learn);
	}

	xmlFree(learn_code);
	xmlFree(learn_event_code);

	if (!no_issue) {
		if (issueInfo) {
			char *issue_number;
			char *in_work;
			char iss[8];

			issue_number = (char *) first_xpath_value(NULL, issueInfo, "@issno|@issueNumber");
			in_work      = (char *) first_xpath_value(NULL, issueInfo, "@inwork|@inWork");

			snprintf(iss, 8, "_%s-%s", issue_number, in_work ? in_work : "00");
			strcat(code, iss);

			xmlFree(issue_number);
			xmlFree(in_work);
		} else if (language) {
			strcat(code, "_\?\?\?-\?\?");
		}
	}

	if (language) {
		char *language_iso_code;
		char *country_iso_code;
		char lang[8];

		language_iso_code = (char *) first_xpath_value(NULL, language, "@language|@languageIsoCode");
		country_iso_code  = (char *) first_xpath_value(NULL, language, "@country|@countryIsoCode");

		snprintf(lang, 8, "_%s-%s", language_iso_code, country_iso_code);
		strcat(code, lang);

		xmlFree(language_iso_code);
		xmlFree(country_iso_code);
	}

	if (find_csdb_object(dst, search_dir, code, is_dm, recursive_search)) {
		return true;
	}

	if (verbosity >= NORMAL) {
		fprintf(stderr, W_MISSING_REF, code);
	}
	return false;
}

/* Find the filename of a referenced ACT data module. */
static bool find_act_fname(char *dst, xmlDocPtr doc)
{
	xmlNodePtr actref;
	actref = firstXPathNode(doc, NULL, "//applicCrossRefTableRef/dmRef/dmRefIdent|//actref/refdm");
	return actref && find_dmod_fname(dst, actref);
}

/* Find the filename of a referenced PCT data module via the ACT. */
static bool find_cct_fname(char *dst, xmlDocPtr act)
{
	xmlNodePtr pctref;
	bool found;

	pctref = firstXPathNode(act, NULL, "//condCrossRefTableRef/dmRef/dmRefIdent|//cctref/refdm");
	found = pctref && find_dmod_fname(dst, pctref);

	return found;
}

/* Add cross-reference tables by searching for them in the current directory. */
static void find_cross_ref_tables(xmlDocPtr doc, xmlNodePtr acts, xmlNodePtr ccts)
{
	char act_fname[PATH_MAX];
	xmlDocPtr act = NULL;

	if (find_act_fname(act_fname, doc) && (act = read_xml_doc(act_fname))) {
		char cct_fname[PATH_MAX];

		xmlNewChild(acts, NULL, BAD_CAST "act", BAD_CAST act_fname);

		if (find_cct_fname(cct_fname, act)) {
			xmlNewChild(ccts, NULL, BAD_CAST "cct", BAD_CAST cct_fname);
		}

		xmlFreeDoc(act);
	}
}

/* Add tags containing the display text of the referenecd applic annotation. */
static void addTags(xmlDocPtr doc, const char *tags)
{
	xmlDocPtr styledoc, src, res;
	xsltStylesheetPtr style;
	const char *params[3];
	xmlNodePtr old;

	styledoc = read_xml_mem((const char *) addTags_xsl, addTags_xsl_len);
	style = xsltParseStylesheetDoc(styledoc);

	params[0] = "mode";
	if (strcmp(tags, "comment") == 0) {
		params[1] = "'comment'";
	} else if (strcmp(tags, "pi") == 0) {
		params[1] = "'pi'";
	} else {
		params[1] = "'remove'";
	}
	params[2] = NULL;

	src = xmlCopyDoc(doc, 1);
	res = xsltApplyStylesheet(style, src, params);
	xmlFreeDoc(src);

	old = xmlDocSetRootElement(doc, xmlCopyNode(xmlDocGetRootElement(res), 1));
	xmlFreeNode(old);

	xmlFreeDoc(res);
	xsltFreeStylesheet(style);
}

/* Recursively delete display text nodes. */
static void deleteDisplayTextNode(xmlNodePtr node)
{
	xmlNodePtr cur;

	/* 3.0-: displaytext
	 * 4.0+: displayText
	 *
	 * Ignore display text nodes without any siblings. */
	if ((xmlStrcmp(node->name, BAD_CAST "displayText") == 0 || xmlStrcmp(node->name, BAD_CAST "displaytext") == 0) && xmlChildElementCount(node->parent) > 1) {
		xmlUnlinkNode(node);
		xmlFreeNode(node);
		return;
	}

	/* Call recursively on all children. */
	cur = node->children;
	while (cur) {
		xmlNodePtr next = cur->next;
		deleteDisplayTextNode(cur);
		cur = next;
	}

}

/* Delete all display text nodes in a document. */
static void deleteDisplayText(xmlDocPtr doc)
{
	deleteDisplayTextNode(xmlDocGetRootElement(doc));
}

static void processFile(const char *in, const char *out, bool process,
	bool genDispText, bool delDispText, xmlNodePtr acts, xmlNodePtr ccts,
	bool findcts, xsltStylesheetPtr style, const char *tags)
{
	xmlDocPtr doc;
	xmlXPathContextPtr ctx;
	xmlXPathObjectPtr obj;
	xmlNodePtr all_acts, all_ccts;

	if (verbosity >= VERBOSE) {
		fprintf(stderr, I_PROCESS, in);
	}

	doc = read_xml_doc(in);

	if (findcts) {
		/* Copy the user-defined ACTs/CCTs. */
		all_acts = xmlCopyNode(acts, 1);
		all_ccts = xmlCopyNode(ccts, 1);
		/* Find the ACT/CCT referenced by the current DM. */
		find_cross_ref_tables(doc, all_acts, all_ccts);
	} else {
		/* Only use the user-defined ACTs/CCTs. */
		all_acts = acts;
		all_ccts = ccts;
	}

	if (process) {
		ctx = xmlXPathNewContext(doc);
		obj = xmlXPathEvalExpression(BAD_CAST "//dmodule", ctx);

		processDmodules(obj->nodesetval);

		xmlXPathFreeObject(obj);
		xmlXPathFreeContext(ctx);
	}

	if (delDispText) {
		deleteDisplayText(doc);
	} else if (genDispText) {
		generateDisplayText(doc, all_acts, all_ccts, style);
	}

	if (tags) {
		addTags(doc, tags);
	}

	save_xml_doc(doc, out);

	/* The next data module could reference a different ACT/CCT, so
	 * the list must be cleared. */
	if (findcts) {
		xmlFreeNode(all_acts);
		xmlFreeNode(all_ccts);
	}

	xmlFreeDoc(doc);
}

static void process_list(const char *path, bool overwrite, bool process,
	bool genDispText, bool delDispText, xmlNodePtr acts, xmlNodePtr ccts,
	bool findcts, xsltStylesheetPtr style, const char *tags)
{
	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");
		processFile(line, overwrite ? line : "-", process, genDispText, delDispText, acts, ccts, findcts, style, tags);
	}

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

static void show_help(void)
{
	puts("Usage:");
	puts("  " PROG_NAME " [options] [<object> ...]");
	puts("");
	puts("Options:");
	puts("  -., --dump-disptext   Dump the built-in .disptext file.");
	puts("  -,, --dump-xsl        Dump the built-in XSLT for generating display text.");
	puts("  -A, --act <ACT>       Use <ACT> when generating display text.");
	puts("  -a, --id <ID>  Use <ID> for DM-level applic.");
	puts("  -C, --cct <CCT>       Use <CCT> when generating display text.");
	puts("  -c, --search          Search for ACT/CCT data modules.");
	puts("  -D, --delete          Remove all display text.");
	puts("  -d, --dir <dir>       Directory to start search for ACT/CCT in.");
	puts("  -F, --format <fmt>    Use a custom format string for generating display text.");
	puts("  -f, --overwrite       Overwrite input file(s).");
	puts("  -G, --disptext        Specify .disptext file.");
	puts("  -g, --generate        Generate display text for applicability annotations.");
	puts("  -k, --keep            Do not overwrite existing display text.");
	puts("  -l, --list            Treat input as list of modules.");
	puts("  -N, --omit-issue      Assume issue/inwork number are omitted.");
	puts("  -p, --presentation    Convert semantic applicability to presentation applicability.");
	puts("  -q, --quiet           Quiet mode.");
	puts("  -r, --recursive       Search for ACT/CCT recursively.");
	puts("  -t, --tags <mode>     Add display text tags before elements with applicability.");
	puts("  -v, --verbose         Verbose output.");
	puts("  -x, --xsl <XSL>       Use custom XSLT script to generate display text.");
	puts("  -h, -?, --help        Show help/usage message.");
	puts("  --version             Show version information.");
	puts("  <object> ...          CSDB objects to process.");
	LIBXML2_PARSE_LONGOPT_HELP
}

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

int main(int argc, char **argv)
{
	int i;
	bool overwrite = false;
	bool genDispText = false;
	bool process = false;
	bool delDispText = false;
	bool findcts = false;
	bool islist = false;
	char *format = NULL;
	char *tags = NULL;
	char *customGenDispTextFile = NULL;
	xmlDocPtr disptext = NULL;
	xmlDocPtr styledoc;
	xsltStylesheetPtr style;
	
	xmlNodePtr acts, ccts;

	const char *sopts = ".,A:a:C:cDd:F:fG:gklNpqrt:vx:h?";
	struct option lopts[] = {
		{"version"      , no_argument      , 0, 0},
		{"help"         , no_argument      , 0, 'h'},
		{"dump-disptext", no_argument      , 0, '.'},
		{"dump-xsl"     , no_argument      , 0, ','},
		{"act"          , required_argument, 0, 'A'},
		{"id"           , required_argument, 0, 'a'},
		{"cct"          , required_argument, 0, 'C'},
		{"search"       , no_argument      , 0, 'c'},
		{"delete"       , no_argument      , 0, 'D'},
		{"dir"          , required_argument, 0, 'd'},
		{"format"       , required_argument, 0, 'F'},
		{"overwrite"    , no_argument      , 0, 'f'},
		{"disptext"     , required_argument, 0, 'G'},
		{"generate"     , no_argument      , 0, 'g'},
		{"keep"         , no_argument      , 0, 'k'},
		{"list"         , no_argument      , 0, 'l'},
		{"omit-issue"   , no_argument      , 0, 'N'},
		{"presentation" , no_argument      , 0, 'p'},
		{"quiet"        , no_argument      , 0, 'q'},
		{"recursive"    , no_argument      , 0, 'r'},
		{"tags"         , 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;

	exsltRegisterAll();

	dmApplicId = xmlStrdup(DEFAULT_DM_APPLIC_ID);

	applicElemsXPath = xmlStrndup(elements_list, elements_list_len);

	acts = xmlNewNode(NULL, BAD_CAST "acts");
	ccts = xmlNewNode(NULL, BAD_CAST "ccts");

	search_dir = strdup(".");

	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 '.':
				dumpDispText();
				return 0;
			case ',':
				dumpGenDispTextXsl();
				return 0;
			case 'A':
				xmlNewChild(acts, NULL, BAD_CAST "act", BAD_CAST optarg);
				findcts = false;
				break;
			case 'a':
				xmlFree(dmApplicId);
				dmApplicId = xmlStrdup(BAD_CAST optarg);
				break;
			case 'C':
				xmlNewChild(ccts, NULL, BAD_CAST "cct", BAD_CAST optarg);
				findcts = false;
				break;
			case 'c':
				findcts = true;
				break;
			case 'D':
				delDispText = true;
				break;
			case 'd':
				free(search_dir);
				search_dir = strdup(optarg);
				break;
			case 'F':
				genDispText = true;
				free(format);
				format = strdup(optarg);
				break;
			case 'f':
				overwrite = true;
				break;
			case 'G':
				genDispText = true;
				xmlFreeDoc(disptext);
				disptext = read_xml_doc(optarg);
				break;
			case 'g':
				genDispText = true;
				break;
			case 'k':
				overwriteDispText = false;
				break;
			case 'l':
				islist = true;
				break;
			case 'N':
				no_issue = true;
				break;
			case 'p':
				process = true;
				break;
			case 'q':
				--verbosity;
				break;
			case 'r':
				recursive_search = true;
				break;
			case 't':
				free(tags);
				tags = strdup(optarg);
				break;
			case 'v':
				++verbosity;
				break;
			case 'x':
				genDispText = true;
				free(customGenDispTextFile);
				customGenDispTextFile = strdup(optarg);
				break;
			case 'h':
			case '?':
				show_help();
				return 0;
		}
	}

	if (customGenDispTextFile == NULL) {
		if (disptext == NULL) {
			char disptext_fname[PATH_MAX];

			if (find_config(disptext_fname, DEFAULT_DISPTEXT_FNAME)) {
				disptext = read_xml_doc(disptext_fname);
			} else {
				disptext = read_xml_mem((const char *) disptext_xml, disptext_xml_len);
			}
		}

		styledoc = parse_disptext(disptext);
	} else {
		styledoc = read_xml_doc(customGenDispTextFile);
	}

	if (format) {
		apply_format_str(styledoc, format);
	}
	style = xsltParseStylesheetDoc(styledoc);

	if (optind >= argc) {
		if (islist) {
			process_list(NULL, overwrite, process, genDispText, delDispText, acts, ccts, findcts, style, tags);
		} else {
			processFile("-", "-", process, genDispText, delDispText, acts, ccts, findcts, style, tags);
		}
	} else {
		for (i = optind; i < argc; ++i) {
			if (islist) {
				process_list(argv[i], overwrite, process, genDispText, delDispText, acts, ccts, findcts, style, tags);
			} else {
				processFile(argv[i], overwrite ? argv[i] : "-", process, genDispText, delDispText, acts, ccts, findcts, style, tags);
			}
		}
	}

	xmlFree(dmApplicId);
	xmlFree(applicElemsXPath);

	xmlFreeNode(acts);
	xmlFreeNode(ccts);

	free(customGenDispTextFile);
	free(search_dir);
	free(format);
	free(tags);
	xmlFreeDoc(disptext);
	xsltFreeStylesheet(style);

	xsltCleanupGlobals();
	xmlCleanupParser();

	return 0;
}


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