/ .. / / -> download
#include <stdio.h>
#include <stdbool.h>
#include <string.h>
#include <unistd.h>
#include <getopt.h>
#include <ctype.h>
#include <sys/stat.h>
#include <time.h>
#include <libgen.h>
#include <libxml/tree.h>
#include <libxml/xpath.h>
#include <libxml/xpathInternals.h>
#include <libxslt/xsltInternals.h>
#include <libxslt/transform.h>
#include <libexslt/exslt.h>
#include "s1kd_tools.h"
#include "xsl.h"

#define PROG_NAME "s1kd-instance"
#define VERSION "12.3.2"

/* Prefixes before messages printed to console */
#define ERR_PREFIX PROG_NAME ": ERROR: "
#define WRN_PREFIX PROG_NAME ": WARNING: "
#define INF_PREFIX PROG_NAME ": INFO: "

/* Error codes */
#define EXIT_MISSING_ARGS 1	/* Option or parameter missing */
#define EXIT_MISSING_FILE 2	/* File does not exist */
#define EXIT_MISSING_SOURCE 3	/* Source object could not be found */
#define EXIT_BAD_APPLIC 4	/* Malformed applic definitions */
#define EXIT_BAD_XML 6		/* Invalid XML/S1000D */
#define EXIT_BAD_ARG 7		/* Malformed argument */
#define EXIT_BAD_DATE 8		/* Malformed issue date */
#define EXIT_MAX_OBJECTS 9      /* Out of memory */

/* Error messages */
#define S_MISSING_OBJECT ERR_PREFIX "Could not read source object: %s\n"
#define S_MISSING_LIST ERR_PREFIX "Could not read list file: %s\n"
#define S_BAD_TYPE ERR_PREFIX "Cannot automatically name unsupported object types.\n"
#define S_BAD_XML ERR_PREFIX "%s does not contain valid XML. If it is a list, specify the -L option.\n"
#define S_MISSING_ANDOR ERR_PREFIX "Evaluate has no operator.\n"
#define S_BAD_CODE ERR_PREFIX "Bad %s code: %s.\n"
#define S_INVALID_CIR ERR_PREFIX "%s is not a valid CIR data module.\n"
#define S_INVALID_ISSFMT ERR_PREFIX "Invalid format for issue/in-work number.\n"
#define S_BAD_DATE ERR_PREFIX "Bad issue date: %s\n"
#define S_BAD_ASSIGN ERR_PREFIX "Malformed applicability definition: \"%s\". Definitions must be in the form of \"<ident>:<type>=<value>\".\n"
#define S_MISSING_ACT ERR_PREFIX "Could not read ACT %s\n"
#define S_MISSING_CCT ERR_PREFIX "Could not read CCT %s\n"
#define S_MISSING_PCT ERR_PREFIX "Could not read PCT %s\n"
#define S_MKDIR_FAILED ERR_PREFIX "Could not create directory %s\n"
#define S_MISSING_SOURCE ERR_PREFIX "Could not find source object for instance %s\n"
#define S_NOT_DIR ERR_PREFIX "%s is not a directory.\n"
#define E_MAX_OBJECTS ERR_PREFIX "Out of memory\n"

/* Warning messages */
#define S_FILE_EXISTS WRN_PREFIX "%s already exists. Use -f to overwrite.\n"
#define S_NO_PRODUCT WRN_PREFIX "No product matching '%s' in PCT '%s'.\n"
#define S_NO_XSLT WRN_PREFIX "No built-in XSLT for CIR type: %s\n"
#define S_MISSING_REF_DM WRN_PREFIX "Could not read referenced object: %s\n"
#define S_MISSING_CIR WRN_PREFIX "Could not find CIR %s.\n"
#define S_RESOLVE_CONTAINER WRN_PREFIX "Could not resolve container %s\n"
#define S_NO_CT WRN_PREFIX "%s is a %s, but no %s was found.\n"
#define S_NO_CT_PROP WRN_PREFIX "Could not find definition of %s %s\n"

/* Info messages */
#define I_UPDATE_INST INF_PREFIX "Updating instance %s from source %s...\n"
#define I_CUSTOMIZE INF_PREFIX "Customizing %s...\n"
#define I_CUSTOMIZE_DIR INF_PREFIX "Customizing %s -> %s ...\n"
#define I_COPY INF_PREFIX "Copying %s -> %s ...\n"
#define I_FIND_CIR INF_PREFIX "Searching for CIRs in \"%s\"...\n"
#define I_FIND_CIR_FOUND INF_PREFIX "Found CIR %s...\n"
#define I_FIND_CIR_ADD INF_PREFIX "Added CIR %s\n"
#define I_NON_APPLIC INF_PREFIX "Ignoring non-applicable object: %s\n"

/* When using the -g option, these are set as the values for the
 * originator.
 */
#define DEFAULT_ORIG_CODE "S1KDI"
#define DEFAULT_ORIG_NAME "s1kd-instance tool"

/* Text of the default RFU added when a "new" master produces non-new
 * instances. */
#define DEFAULT_RFU BAD_CAST "New master"

/* Search for ACT/PCT recursively. */
static bool recursive_search = false;

/* Directory to start searching for ACT/PCT in. */
static char *search_dir;

/* Tag non-applicable elements instead of deleting them. */
static bool tag_non_applic = false;

/* Remove display text from annotations which are modified in -A mode. */
static bool clean_disp_text = false;

/* Convenient structure for all strings related to uniquely identifying a
 * CSDB object.
 */
enum object_type { DM, PM, DML, COM, DDN, IMF, UPF };
enum issue { ISS_30, ISS_4X };
struct ident {
	bool extended;
	enum object_type type;
	enum issue issue;
	char *extensionProducer;
	char *extensionCode;
	char *modelIdentCode;
	char *systemDiffCode;
	char *systemCode;
	char *subSystemCode;
	char *subSubSystemCode;
	char *assyCode;
	char *disassyCode;
	char *disassyCodeVariant;
	char *infoCode;
	char *infoCodeVariant;
	char *itemLocationCode;
	char *learnCode;
	char *learnEventCode;
	char *senderIdent;
	char *pmNumber;
	char *pmVolume;
	char *issueNumber;
	char *inWork;
	char *languageIsoCode;
	char *countryIsoCode;
	char *dmlCommentType;
	char *seqNumber;
	char *yearOfDataIssue;
	char *receiverIdent;
	char *imfIdentIcn;
};

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

/* Verbosity level */
static enum verbosity { QUIET, NORMAL, VERBOSE, DEBUG } verbosity = NORMAL;

/* Method for listing properties. */
enum listprops { STANDALONE, APPLIC, ALL };

/* Determine whether an applicability definition may be modified.
 * User-definitions may only be modified by other user-definitions.
 * User-definitions may modify non-user-definitions.
 */
static bool allow_def_modify(bool userdefined, const xmlChar *attr)
{
	return userdefined || xmlStrcmp(attr, BAD_CAST "false") == 0;
}

/* Define a value for a product attribute or condition. */
static void define_applic(xmlNodePtr defs, int *napplics, const xmlChar *ident, const xmlChar *type, const xmlChar *value, bool perdm, bool userdefined)
{
	xmlNodePtr assert = NULL;
	xmlNodePtr cur;

	if (!(ident && type && value)) {
		return;
	}

	/* Check if an assert has already been created for this property. */
	for (cur = defs->children; cur; cur = cur->next) {
		xmlChar *cur_ident = xmlGetProp(cur, BAD_CAST "applicPropertyIdent");
		xmlChar *cur_type  = xmlGetProp(cur, BAD_CAST "applicPropertyType");

		if (xmlStrcmp(cur_ident, ident) == 0 && xmlStrcmp(cur_type, type) == 0) {
			assert = cur;
		}

		xmlFree(cur_ident);
		xmlFree(cur_type);
	}

	/* If no assert exists, add a new one. */
	if (!assert) {
		assert = xmlNewChild(defs, NULL, BAD_CAST "assert", NULL);
		xmlSetProp(assert, BAD_CAST "applicPropertyIdent", ident);
		xmlSetProp(assert, BAD_CAST "applicPropertyType",  type);
		xmlSetProp(assert, BAD_CAST "applicPropertyValues", value);
		xmlSetProp(assert, BAD_CAST "userDefined", BAD_CAST (userdefined ? "true" : "false"));

		if (userdefined) {
			++(*napplics);
		}
	/* Or, if an assert already exists... */
	} else {
		xmlChar *user_defined_attr;

		user_defined_attr = xmlGetProp(assert, BAD_CAST "userDefined");

		/* Check for duplicate value in a single-assert. */
		if (xmlHasProp(assert, BAD_CAST "applicPropertyValues")) {
			xmlChar *first_value;

			first_value = xmlGetProp(assert, BAD_CAST "applicPropertyValues");

			/* If the value is not a duplicate, and the assertion
			 * may be modified, convert to a multi-assert and add
			 * the original and new values. */
			if (xmlStrcmp(first_value, BAD_CAST value) != 0 && allow_def_modify(userdefined, user_defined_attr)) {
				xmlNewChild(assert, NULL, BAD_CAST "value", first_value);
				xmlNewChild(assert, NULL, BAD_CAST "value", value);
				xmlUnsetProp(assert, BAD_CAST "applicPropertyValues");
			}

			xmlFree(first_value);
		/* Check for duplicate value in a multi-assert. */
		} else {
			bool dup = false;

			for (cur = assert->children; cur && !dup; cur = cur->next) {
				xmlChar *cur_value;
				cur_value = xmlNodeGetContent(cur);
				dup = xmlStrcmp(cur_value, value) == 0;
				xmlFree(cur_value);
			}

			/* If the value is not a duplicate, and the assertion
			 * may be modified, add the new value to the
			 * multi-assert. */
			if (!dup && allow_def_modify(userdefined, user_defined_attr)) {
				xmlNewChild(assert, NULL, BAD_CAST "value", value);
			}
		}

		xmlFree(user_defined_attr);
	}

	/* Tag asserts that may only be true for individual DMs. */
	if (perdm) {
		xmlSetProp(assert, BAD_CAST "perDm", BAD_CAST "true");
	}
}

/* Find the first child element with a given name */
static xmlNodePtr find_child(xmlNodePtr parent, const char *name)
{
	xmlNodePtr cur;

	for (cur = parent->children; cur; cur = cur->next) {
		if (strcmp((char *) cur->name, name) == 0) {
			return cur;
		}
	}

	return NULL;
}

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

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

	ctx->node = node;

	obj = xmlXPathEvalExpression(BAD_CAST path, ctx);

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

	xmlXPathFreeObject(obj);
	xmlXPathFreeContext(ctx);

	return first;
}

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

/* Copy strings related to uniquely identifying a CSDB object. The strings are
 * dynamically allocated so they must be freed using free_ident. */
#define IDENT_XPATH BAD_CAST \
	"//dmIdent|//dmaddres|" \
	"//pmIdent|//pmaddres|" \
	"//dmlIdent|//dml[dmlc]|" \
	"//commentIdent|//cstatus|" \
	"//ddnIdent|//ddn|" \
	"//imfIdent|" \
	"//updateIdent"
#define EXTENSION_XPATH BAD_CAST \
	"//dmIdent/identExtension|//dmaddres/dmcextension|" \
	"//pmIdent/identExtension|" \
	"//updateIdent/identExtension"
#define CODE_XPATH BAD_CAST \
	"//dmIdent/dmCode|//dmaddres/dmc/avee|" \
	"//pmIdent/pmCode|//pmaddres/pmc|" \
	"//dmlIdent/dmlCode|//dml/dmlc|" \
	"//commentIdent/commentCode|//cstatus/ccode|" \
	"//ddnIdent/ddnCode|//ddn/ddnc|" \
	"//imfIdent/imfCode|" \
	"//updateIdent/updateCode"
#define LANGUAGE_XPATH BAD_CAST \
	"//dmIdent/language|//dmaddres/language|" \
	"//pmIdent/language|//pmaddres/language|" \
	"//commentIdent/language|//cstatus/language|" \
	"//updateIdent/language"
#define ISSUE_INFO_XPATH BAD_CAST \
	"//dmIdent/issueInfo|//dmaddres/issno|" \
	"//pmIdent/issueInfo|//pmaddres/issno|" \
	"//dmlIdent/issueInfo|//dml/issno|" \
	"//imfIdent/issueInfo|" \
	"//updateIdent/issueInfo"
static bool init_ident(struct ident *ident, xmlDocPtr doc)
{
	xmlNodePtr moduleIdent, identExtension, code, language, issueInfo;

	moduleIdent = first_xpath_node(doc, NULL, IDENT_XPATH);

	if (!moduleIdent) {
		return false;
	}

	if (xmlStrcmp(moduleIdent->name, BAD_CAST "pmIdent") == 0) {
		ident->type = PM;
		ident->issue = ISS_4X;
	} else if (xmlStrcmp(moduleIdent->name, BAD_CAST "pmaddres") == 0) {
		ident->type = PM;
		ident->issue = ISS_30;
	} else if (xmlStrcmp(moduleIdent->name, BAD_CAST "dmlIdent") == 0) {
		ident->type = DML;
		ident->issue = ISS_4X;
	} else if (xmlStrcmp(moduleIdent->name, BAD_CAST "dml") == 0) {
		ident->type = DML;
		ident->issue = ISS_30;
	} else if (xmlStrcmp(moduleIdent->name, BAD_CAST "commentIdent") == 0) {
		ident->type = COM;
		ident->issue = ISS_4X;
	} else if (xmlStrcmp(moduleIdent->name, BAD_CAST "cstatus") == 0) {
		ident->type = COM;
		ident->issue = ISS_30;
	} else if (xmlStrcmp(moduleIdent->name, BAD_CAST "dmIdent") == 0) {
		ident->type = DM;
		ident->issue = ISS_4X;
	} else if (xmlStrcmp(moduleIdent->name, BAD_CAST "dmaddres") == 0) {
		ident->type = DM;
		ident->issue = ISS_30;
	} else if (xmlStrcmp(moduleIdent->name, BAD_CAST "ddnIdent") == 0) {
		ident->type = DDN;
		ident->issue = ISS_4X;
	} else if (xmlStrcmp(moduleIdent->name, BAD_CAST "ddn") == 0) {
		ident->type = DDN;
		ident->issue = ISS_30;
	} else if (xmlStrcmp(moduleIdent->name, BAD_CAST "imfIdent") == 0) {
		ident->type = IMF;
		ident->issue = ISS_4X;
	} else if (xmlStrcmp(moduleIdent->name, BAD_CAST "updateIdent") == 0) {
		ident->type = UPF;
		ident->issue = ISS_4X;
	}

	identExtension = first_xpath_node(doc, NULL, EXTENSION_XPATH);
	code = first_xpath_node(doc, NULL, CODE_XPATH);
	language = first_xpath_node(doc, NULL, LANGUAGE_XPATH);
	issueInfo = first_xpath_node(doc, NULL, ISSUE_INFO_XPATH);

	if (!code) {
		return false;
	}

	if (ident->issue == ISS_30) {
		ident->modelIdentCode = (char *) xmlNodeGetContent(find_child(code, "modelic"));
	} else {
		ident->modelIdentCode = (char *) xmlGetProp(code, BAD_CAST "modelIdentCode");
	}

	if (ident->type == PM) {
		if (ident->issue == ISS_30) {
			ident->senderIdent = (char *) xmlNodeGetContent(find_child(code, "pmissuer"));
			ident->pmNumber    = (char *) xmlNodeGetContent(find_child(code, "pmnumber"));
			ident->pmVolume    = (char *) xmlNodeGetContent(find_child(code, "pmvolume"));
		} else {
			ident->senderIdent = (char *) xmlGetProp(code, BAD_CAST "pmIssuer");
			ident->pmNumber    = (char *) xmlGetProp(code, BAD_CAST "pmNumber");
			ident->pmVolume    = (char *) xmlGetProp(code, BAD_CAST "pmVolume");
		}
	} else if (ident->type == DML || ident->type == COM) {
		if (ident->issue == ISS_30) {
			ident->senderIdent = (char *) xmlNodeGetContent(find_child(code, "sendid"));
			ident->yearOfDataIssue = (char *) xmlNodeGetContent(find_child(code, "diyear"));
			ident->seqNumber = (char *) xmlNodeGetContent(find_child(code, "seqnum"));
		} else {
			ident->senderIdent        = (char *) xmlGetProp(code, BAD_CAST "senderIdent");
			ident->yearOfDataIssue    = (char *) xmlGetProp(code, BAD_CAST "yearOfDataIssue");
			ident->seqNumber          = (char *) xmlGetProp(code, BAD_CAST "seqNumber");
		}

		if (ident->type == DML) {
			if (ident->issue == ISS_30) {
				ident->dmlCommentType = (char *) xmlGetProp(find_child(code, "dmltype"), BAD_CAST "type");
			} else {
				ident->dmlCommentType = (char *) xmlGetProp(code, BAD_CAST "dmlType");
			}
		} else {
			if (ident->issue == ISS_30) {
				ident->dmlCommentType = (char *) xmlGetProp(find_child(code, "ctype"), BAD_CAST "type");
			} else {
				ident->dmlCommentType = (char *) xmlGetProp(code, BAD_CAST "commentType");
			}
		}
	} else if (ident->type == DDN) {
		if (ident->issue == ISS_30) {
			ident->senderIdent     = (char *) xmlNodeGetContent(find_child(code, "sendid"));
			ident->receiverIdent   = (char *) xmlNodeGetContent(find_child(code, "recvid"));
			ident->yearOfDataIssue = (char *) xmlNodeGetContent(find_child(code, "diyear"));
			ident->seqNumber       = (char *) xmlNodeGetContent(find_child(code, "seqnum"));
		} else {
			ident->senderIdent     = (char *) xmlGetProp(code, BAD_CAST "senderIdent");
			ident->receiverIdent   = (char *) xmlGetProp(code, BAD_CAST "receiverIdent");
			ident->yearOfDataIssue = (char *) xmlGetProp(code, BAD_CAST "yearOfDataIssue");
			ident->seqNumber       = (char *) xmlGetProp(code, BAD_CAST "seqNumber");
		}
	} else if (ident->type == DM || ident->type == UPF) {
		if (ident->issue == ISS_30) {
			ident->systemDiffCode     = (char *) xmlNodeGetContent(find_child(code, "sdc"));
			ident->systemCode         = (char *) xmlNodeGetContent(find_child(code, "chapnum"));
			ident->subSystemCode      = (char *) xmlNodeGetContent(find_child(code, "section"));
			ident->subSubSystemCode   = (char *) xmlNodeGetContent(find_child(code, "subsect"));
			ident->assyCode           = (char *) xmlNodeGetContent(find_child(code, "subject"));
			ident->disassyCode        = (char *) xmlNodeGetContent(find_child(code, "discode"));
			ident->disassyCodeVariant = (char *) xmlNodeGetContent(find_child(code, "discodev"));
			ident->infoCode           = (char *) xmlNodeGetContent(find_child(code, "incode"));
			ident->infoCodeVariant    = (char *) xmlNodeGetContent(find_child(code, "incodev"));
			ident->itemLocationCode   = (char *) xmlNodeGetContent(find_child(code, "itemloc"));
			ident->learnCode = NULL;
			ident->learnEventCode = NULL;
		} else {
			ident->systemDiffCode     = (char *) xmlGetProp(code, BAD_CAST "systemDiffCode");
			ident->systemCode         = (char *) xmlGetProp(code, BAD_CAST "systemCode");
			ident->subSystemCode      = (char *) xmlGetProp(code, BAD_CAST "subSystemCode");
			ident->subSubSystemCode   = (char *) xmlGetProp(code, BAD_CAST "subSubSystemCode");
			ident->assyCode           = (char *) xmlGetProp(code, BAD_CAST "assyCode");
			ident->disassyCode        = (char *) xmlGetProp(code, BAD_CAST "disassyCode");
			ident->disassyCodeVariant = (char *) xmlGetProp(code, BAD_CAST "disassyCodeVariant");
			ident->infoCode           = (char *) xmlGetProp(code, BAD_CAST "infoCode");
			ident->infoCodeVariant    = (char *) xmlGetProp(code, BAD_CAST "infoCodeVariant");
			ident->itemLocationCode   = (char *) xmlGetProp(code, BAD_CAST "itemLocationCode");
			ident->learnCode          = (char *) xmlGetProp(code, BAD_CAST "learnCode");
			ident->learnEventCode     = (char *) xmlGetProp(code, BAD_CAST "learnEventCode");
		}
	} else if (ident->type == IMF) {
		ident->imfIdentIcn = (char *) xmlGetProp(code, BAD_CAST "imfIdentIcn");
	}

	if (ident->type == DM || ident->type == PM || ident->type == DML || ident->type == IMF || ident->type == UPF) {
		const char *issueNumberName, *inWorkName;

		if (!issueInfo) return false;

		issueNumberName = ident->issue == ISS_30 ? "issno" : "issueNumber";
		inWorkName      = ident->issue == ISS_30 ? "inwork" : "inWork";

		ident->issueNumber = (char *) xmlGetProp(issueInfo, BAD_CAST issueNumberName);
		ident->inWork      = (char *) xmlGetProp(issueInfo, BAD_CAST inWorkName);

		if (!ident->inWork) {
			ident->inWork = strdup("00");
		}
	}

	if (ident->type == DM || ident->type == PM || ident->type == COM || ident->type == UPF) {
		const char *languageIsoCodeName, *countryIsoCodeName;

		if (!language) return false;

		languageIsoCodeName = ident->issue == ISS_30 ? "language" : "languageIsoCode";
		countryIsoCodeName  = ident->issue == ISS_30 ? "country" : "countryIsoCode";

		ident->languageIsoCode = (char *) xmlGetProp(language, BAD_CAST languageIsoCodeName);
		ident->countryIsoCode  = (char *) xmlGetProp(language, BAD_CAST countryIsoCodeName);
	}

	if (identExtension) {
		ident->extended = true;

		if (ident->issue == ISS_30) {
			ident->extensionProducer = (char *) xmlNodeGetContent(find_child(identExtension, "dmeproducer"));
			ident->extensionCode     = (char *) xmlNodeGetContent(find_child(identExtension, "dmecode"));
		} else {
			ident->extensionProducer = (char *) xmlGetProp(identExtension, BAD_CAST "extensionProducer");
			ident->extensionCode     = (char *) xmlGetProp(identExtension, BAD_CAST "extensionCode");
		}
	} else {
		ident->extended = false;
	}

	return true;
}

static void free_ident(struct ident *ident)
{
	if (ident->extended) {
		xmlFree(ident->extensionProducer);
		xmlFree(ident->extensionCode);
	}

	xmlFree(ident->modelIdentCode);

	if (ident->type == PM) {
		xmlFree(ident->senderIdent);
		xmlFree(ident->pmNumber);
		xmlFree(ident->pmVolume);
	} else if (ident->type == DML || ident->type == COM) {
		xmlFree(ident->senderIdent);
		xmlFree(ident->yearOfDataIssue);
		xmlFree(ident->seqNumber);
		xmlFree(ident->dmlCommentType);
	} else if (ident->type == DM || ident->type == UPF) {
		xmlFree(ident->systemDiffCode);
		xmlFree(ident->systemCode);
		xmlFree(ident->subSystemCode);
		xmlFree(ident->subSubSystemCode);
		xmlFree(ident->assyCode);
		xmlFree(ident->disassyCode);
		xmlFree(ident->disassyCodeVariant);
		xmlFree(ident->infoCode);
		xmlFree(ident->infoCodeVariant);
		xmlFree(ident->itemLocationCode);
		xmlFree(ident->learnCode);
		xmlFree(ident->learnEventCode);
	} else if (ident->type == DDN) {
		xmlFree(ident->senderIdent);
		xmlFree(ident->receiverIdent);
		xmlFree(ident->yearOfDataIssue);
		xmlFree(ident->seqNumber);
	}

	if (ident->type == DM || ident->type == PM || ident->type == DML) {
		xmlFree(ident->issueNumber);
		xmlFree(ident->inWork);
	}

	if (ident->type == DM || ident->type == PM || ident->type == COM) {
		xmlFree(ident->languageIsoCode);
		xmlFree(ident->countryIsoCode);
	}
}

/* Search recursively for a descendant element with the given id */
static xmlNodePtr get_element_by_id(xmlNodePtr root, const char *id)
{
	xmlNodePtr cur;
	char *cid;

	if (!root) {
		return NULL;
	}

	for (cur = root->children; cur; cur = cur->next) {
		xmlNodePtr ch;
		bool match;

		cid = (char *) xmlGetProp(cur, BAD_CAST "id");

		match = cid && strcmp(cid, id) == 0;

		xmlFree(cid);

		if (match) {
			return cur;
		} else if ((ch = get_element_by_id(cur, id))) {
			return ch;
		}
	}

	return NULL;
}

/* Tests whether an <applic> element is true. */
static bool eval_applic_stmt(xmlNodePtr defs, xmlNodePtr applic, bool assume)
{
	xmlNodePtr stmt;

	stmt = find_child(applic, "assert");

	if (!stmt) {
		stmt = find_child(applic, "evaluate");
	}

	if (!stmt) {
		return assume;
	}

	return eval_applic(defs, stmt, assume);
}

/* Remove non-applicable elements from content */
static void strip_applic(xmlNodePtr defs, xmlNodePtr referencedApplicGroup, xmlNodePtr node)
{
	xmlNodePtr cur, next;
	xmlNodePtr attr;

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

	if (attr) {
		xmlChar *applicRefId;
		xmlNodePtr applic;

		applicRefId = xmlNodeGetContent(attr);
		applic = get_element_by_id(referencedApplicGroup, (char *) applicRefId);
		xmlFree(applicRefId);

		if (applic && !eval_applic_stmt(defs, applic, true)) {
			if (tag_non_applic) {
				add_first_child(node, xmlNewPI(BAD_CAST "notApplicable", NULL));
			} else {
				xmlUnlinkNode(node);
				xmlFreeNode(node);
			}
			return;
		}
	}

	cur = node->children;
	while (cur) {
		next = cur->next;
		strip_applic(defs, referencedApplicGroup, cur);
		cur = next;
	}
}

/* Remove unambiguously true or false applic statements. */
static void clean_applic_stmts(xmlNodePtr defs, xmlNodePtr referencedApplicGroup, bool remtrue)
{
	xmlNodePtr cur;

	cur = referencedApplicGroup->children;

	while (cur) {
		xmlNodePtr next = cur->next;

		if (cur->type == XML_ELEMENT_NODE && ((remtrue && eval_applic_stmt(defs, cur, false)) || !eval_applic_stmt(defs, cur, true))) {
			xmlUnlinkNode(cur);
			xmlFreeNode(cur);
		}

		cur = next;
	}
}

/* Remove applic references on content where the applic statement was removed by clean_applic_stmts. */
static void clean_applic(xmlNodePtr referencedApplicGroup, xmlNodePtr node)
{
	xmlNodePtr cur;

	if (xmlHasProp(node, BAD_CAST "applicRefId")) {
		char *applicRefId;
		xmlNodePtr applic;

		applicRefId = (char *) xmlGetProp(node, BAD_CAST "applicRefId");
		applic = get_element_by_id(referencedApplicGroup, applicRefId);
		xmlFree(applicRefId);

		if (!applic) {
			xmlUnsetProp(node, BAD_CAST "applicRefId");
		}
	}

	for (cur = node->children; cur; cur = cur->next) {
		clean_applic(referencedApplicGroup, cur);
	}
}

/* Remove unused applicability annotations. */
static xmlNodePtr rem_unused_annotations(xmlDocPtr doc, xmlNodePtr referencedApplicGroup)
{
	xmlXPathContextPtr ctx;
	xmlXPathObjectPtr obj;

	ctx = xmlXPathNewContext(doc);
	xmlXPathSetContextNode(referencedApplicGroup, ctx);

	if (xmlStrcmp(referencedApplicGroup->name, BAD_CAST "referencedApplicGroup") == 0) {
		obj = xmlXPathEval(BAD_CAST "applic[not(@id=//@applicRefId)]", ctx);
	} else {
		obj = xmlXPathEval(BAD_CAST "applic[not(@id=//@refapplic)]", 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);

	if (xmlChildElementCount(referencedApplicGroup) == 0) {
		xmlUnlinkNode(referencedApplicGroup);
		xmlFreeNode(referencedApplicGroup);
		return NULL;
	}

	return referencedApplicGroup;
}

/* Remove display text from the containing annotation. */
static void rem_disp_text(xmlNodePtr node)
{
	xmlNodePtr disptext;

	disptext = first_xpath_node(NULL, node, BAD_CAST "ancestor::applic/*[self::displayText or self::displaytext]");

	if (disptext) {
		xmlUnlinkNode(disptext);
		xmlFreeNode(disptext);
	}
}

/* Remove applic statements or parts of applic statements where all assertions
 * are unambiguously true or false.
 *
 * Returns true if the whole annotation is removed, or false if only parts of
 * it are removed.
 */
static bool simpl_applic(xmlNodePtr defs, xmlNodePtr node, bool remtrue)
{
	xmlNodePtr cur, next;

	if (xmlStrcmp(node->name, BAD_CAST "applic") == 0) {
		if ((remtrue && eval_applic_stmt(defs, node, false)) || !eval_applic_stmt(defs, node, true)) {
			xmlUnlinkNode(node);
			xmlFreeNode(node);
			return true;
		}
	} else if (xmlStrcmp(node->name, BAD_CAST "evaluate") == 0) {
		if ((remtrue && eval_applic(defs, node, false)) || !eval_applic(defs, node, true)) {
			if (clean_disp_text) {
				rem_disp_text(node);
			}

			xmlUnlinkNode(node);
			xmlFreeNode(node);

			return false;
		}
	} else if (xmlStrcmp(node->name, BAD_CAST "assert") == 0) {
		if ((remtrue && eval_assert(defs, node, false)) || !eval_assert(defs, node, true)) {
			if (clean_disp_text) {
				rem_disp_text(node);
			}

			xmlUnlinkNode(node);
			xmlFreeNode(node);

			return false;
		}
	}

	cur = node->children;
	while (cur) {
		next = cur->next;
		simpl_applic(defs, cur, remtrue);
		cur = next;
	}

	return false;
}

/* If an <evaluate> contains only one (or no) child elements, remove it. */
static void simpl_evaluate(xmlNodePtr evaluate)
{
	int nchild = 0;
	xmlNodePtr cur;

	for (cur = evaluate->children; cur; cur = cur->next) {
		if (cur->type == XML_ELEMENT_NODE) {
			++nchild;
		}
	}

	if (nchild < 2) {
		xmlNodePtr child;

		child = find_child(evaluate, "assert");
		if (!child) child = find_child(evaluate, "evaluate");
		xmlAddNextSibling(evaluate, child);
		xmlUnlinkNode(evaluate);
		xmlFreeNode(evaluate);
	}
}

/* Simplify <evaluate> elements recursively. */
static void simpl_applic_evals(xmlNodePtr node)
{
	xmlNodePtr cur, next;

	if (!node) {
		return;
	}

	cur = node->children;
	while (cur) {
		next = cur->next;
		if (cur->type == XML_ELEMENT_NODE) {
			simpl_applic_evals(cur);
		}
		cur = next;
	}

	if (xmlStrcmp(node->name, BAD_CAST "evaluate") == 0) {
		simpl_evaluate(node);
	}
}

/* Remove <referencedApplicGroup> if all applic statements are removed */
static xmlNodePtr simpl_applic_clean(xmlNodePtr defs, xmlNodePtr referencedApplicGroup, bool remtrue)
{
	bool has_applic = false;
	xmlNodePtr cur;

	if (!referencedApplicGroup) {
		return NULL;
	}

	simpl_applic(defs, referencedApplicGroup, remtrue);
	simpl_applic_evals(referencedApplicGroup);

	for (cur = referencedApplicGroup->children; cur; cur = cur->next) {
		if (strcmp((char *) cur->name, "applic") == 0) {
			has_applic = true;
		}
	}

	if (!has_applic) {
		xmlUnlinkNode(referencedApplicGroup);
		xmlFreeNode(referencedApplicGroup);
		return NULL;
	}

	return referencedApplicGroup;
}

/* Copy applic defs without non-user-definitions. */
static xmlNodePtr remove_non_user_defs(xmlNodePtr defs)
{
	xmlNodePtr cur, userdefs;

	userdefs = xmlCopyNode(defs, 1);
	cur = userdefs->children;

	while (cur) {
		xmlNodePtr next;
		xmlChar *userdefined;

		next = cur->next;

		userdefined = xmlGetProp(cur, BAD_CAST "userDefined");

		if (xmlStrcmp(userdefined, BAD_CAST "false") == 0) {
			xmlUnlinkNode(cur);
			xmlFreeNode(cur);
		}

		xmlFree(userdefined);

		cur = next;
	}

	return userdefs;
}

/* Simplify the applicability of the whole object. */
static xmlNodePtr simpl_whole_applic(xmlNodePtr defs, xmlDocPtr doc, bool remtrue)
{
	xmlNodePtr applic, orig, userdefs;

	/* Remove non-user-definitions. */
	userdefs = remove_non_user_defs(defs);

	orig = first_xpath_node(doc, NULL, BAD_CAST "//dmStatus/applic|//pmStatus/applic");

	if (!orig) {
		return NULL;
	}

	applic = xmlCopyNode(orig, 1);

	if (simpl_applic(userdefs, applic, remtrue)) {
		xmlNodePtr disptext;
		applic = xmlNewNode(NULL, BAD_CAST "applic");
		disptext = xmlNewChild(applic, NULL, BAD_CAST "displayText", NULL);
		xmlNewChild(disptext, NULL, BAD_CAST "simplePara", BAD_CAST "All");
	} else {
		simpl_applic_evals(applic);
	}

	xmlAddNextSibling(orig, applic);
	xmlUnlinkNode(orig);
	xmlFreeNode(orig);
	xmlFreeNode(userdefs);

	return applic;
}

/* Add metadata linking the data module instance with the source data module */
static void add_source(xmlDocPtr source)
{
	xmlNodePtr ident, sourceIdent, node, cur;
	const xmlChar *type;

	ident       = first_xpath_node(source, NULL, BAD_CAST "//dmIdent|//pmIdent|//dmaddres");
	sourceIdent = first_xpath_node(source, NULL, BAD_CAST "//dmStatus/sourceDmIdent|//pmStatus/sourcePmIdent|//status/srcdmaddres");
	node        = first_xpath_node(source, NULL, BAD_CAST "(//dmStatus/repositorySourceDmIdent|//dmStatus/security|//pmStatus/security|//status/security)[1]");

	if (!node) {
		return;
	}

	if (sourceIdent) {
		xmlUnlinkNode(sourceIdent);
		xmlFreeNode(sourceIdent);
	}

	type = ident->name;

	if (xmlStrcmp(type, BAD_CAST "dmIdent") == 0) {
		sourceIdent = xmlNewNode(NULL, BAD_CAST "sourceDmIdent");
	} else if (xmlStrcmp(type, BAD_CAST "pmIdent") == 0) {
		sourceIdent = xmlNewNode(NULL, BAD_CAST "sourcePmIdent");
	} else if (xmlStrcmp(type, BAD_CAST "dmaddres") == 0) {
		sourceIdent = xmlNewNode(NULL, BAD_CAST "srcdmaddres");
	} else {
		return;
	}

	sourceIdent = xmlAddPrevSibling(node, sourceIdent);

	for (cur = ident->children; cur; cur = cur->next) {
		xmlAddChild(sourceIdent, xmlCopyNode(cur, 1));
	}
}

/* Add an extension to the data module code */
static void set_extd(xmlDocPtr doc, const char *extension)
{
	xmlNodePtr identExtension, code;
	char *ext, *extensionProducer, *extensionCode;
	enum issue issue;

	identExtension = first_xpath_node(doc, NULL, BAD_CAST "//dmIdent/identExtension|//pmIdent/identExtension|//dmaddres/dmcextension");
	code = first_xpath_node(doc, NULL, BAD_CAST "//dmIdent/dmCode|//pmIdent/pmCode|//dmaddres/dmc");

	if (xmlStrcmp(code->name, BAD_CAST "dmCode") == 0) {
		issue = ISS_4X;
	} else if (xmlStrcmp(code->name, BAD_CAST "pmCode") == 0) {
		issue = ISS_4X;
	} else if (xmlStrcmp(code->name, BAD_CAST "dmc") == 0) {
		issue = ISS_30;
	} else {
		return;
	}

	ext = strdup(extension);

	if (!identExtension) {
		identExtension = xmlNewNode(NULL, BAD_CAST (issue == ISS_30 ? "dmcextension" : "identExtension"));
		identExtension = xmlAddPrevSibling(code, identExtension);
	}

	extensionProducer = strtok(ext, "-");
	extensionCode     = strtok(NULL, "-");

	if (issue == ISS_30) {
		xmlNewChild(identExtension, NULL, BAD_CAST "dmeproducer", BAD_CAST extensionProducer);
		xmlNewChild(identExtension, NULL, BAD_CAST "dmecode", BAD_CAST extensionCode);
	} else {
		xmlSetProp(identExtension, BAD_CAST "extensionProducer", BAD_CAST extensionProducer);
		xmlSetProp(identExtension, BAD_CAST "extensionCode",     BAD_CAST extensionCode);
	}

	free(ext);
}

static void set_dm_code(xmlNodePtr code, enum issue iss, const char *s)
{
	char model_ident_code[15];
	char system_diff_code[5];
	char system_code[4];
	char sub_system_code[2];
	char sub_sub_system_code[2];
	char assy_code[5];
	char disassy_code[3];
	char disassy_code_variant[4];
	char info_code[4];
	char info_code_variant[2];
	char item_location_code[2];
	char learn_code[4];
	char learn_event_code[2];
	int n;

	n = sscanf(s,
		"%14[^-]-%4[^-]-%3[^-]-%1s%1s-%4[^-]-%2s%3[^-]-%3s%1s-%1s-%3s%1s",
		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,
		learn_code,
		learn_event_code);

	if (n != 11 && n != 13) {
		if (verbosity > QUIET) {
			fprintf(stderr, S_BAD_CODE, "data module", s);
		}
		exit(EXIT_BAD_ARG);
	}

	if (iss == ISS_4X) {
		xmlSetProp(code, BAD_CAST "modelIdentCode",     BAD_CAST model_ident_code);
		xmlSetProp(code, BAD_CAST "systemDiffCode",     BAD_CAST system_diff_code);
		xmlSetProp(code, BAD_CAST "systemCode",         BAD_CAST system_code);
		xmlSetProp(code, BAD_CAST "subSystemCode",      BAD_CAST sub_system_code);
		xmlSetProp(code, BAD_CAST "subSubSystemCode",   BAD_CAST sub_sub_system_code);
		xmlSetProp(code, BAD_CAST "assyCode",           BAD_CAST assy_code);
		xmlSetProp(code, BAD_CAST "disassyCode",        BAD_CAST disassy_code);
		xmlSetProp(code, BAD_CAST "disassyCodeVariant", BAD_CAST disassy_code_variant);
		xmlSetProp(code, BAD_CAST "infoCode",           BAD_CAST info_code);
		xmlSetProp(code, BAD_CAST "infoCodeVariant",    BAD_CAST info_code_variant);
		xmlSetProp(code, BAD_CAST "itemLocationCode",   BAD_CAST item_location_code);

		if (n == 13) {
			xmlSetProp(code, BAD_CAST "learnCode", BAD_CAST learn_code);
			xmlSetProp(code, BAD_CAST "learnEventCode", BAD_CAST learn_event_code);
		}
	} else if (iss == ISS_30) {
		xmlNodeSetContent(find_child(code, "modelic"), BAD_CAST model_ident_code);
		xmlNodeSetContent(find_child(code, "sdc"), BAD_CAST system_diff_code);
		xmlNodeSetContent(find_child(code, "chapnum"), BAD_CAST system_code);
		xmlNodeSetContent(find_child(code, "section"), BAD_CAST sub_system_code);
		xmlNodeSetContent(find_child(code, "subsect"), BAD_CAST sub_sub_system_code);
		xmlNodeSetContent(find_child(code, "subject"), BAD_CAST assy_code);
		xmlNodeSetContent(find_child(code, "discode"), BAD_CAST disassy_code);
		xmlNodeSetContent(find_child(code, "discodev"), BAD_CAST disassy_code_variant);
		xmlNodeSetContent(find_child(code, "incode"), BAD_CAST info_code);
		xmlNodeSetContent(find_child(code, "incodev"), BAD_CAST info_code_variant);
		xmlNodeSetContent(find_child(code, "ilc"), BAD_CAST item_location_code);
	}
}

static void set_pm_code(xmlNodePtr code, enum issue iss, const char *s)
{
	char model_ident_code[15];
	char pm_issuer[6];
	char pm_number[6];
	char pm_volume[3];
	int n;

	n = sscanf(s,
		"%14[^-]-%5[^-]-%5[^-]-%2s",
		model_ident_code,
		pm_issuer,
		pm_number,
		pm_volume);

	if (n != 4) {
		if (verbosity > QUIET) {
			fprintf(stderr, S_BAD_CODE, "publication module", s);
		}
		exit(EXIT_BAD_ARG);
	}

	if (iss == ISS_4X) {
		xmlSetProp(code, BAD_CAST "modelIdentCode", BAD_CAST model_ident_code);
		xmlSetProp(code, BAD_CAST "pmIssuer", BAD_CAST pm_issuer);
		xmlSetProp(code, BAD_CAST "pmNumber", BAD_CAST pm_number);
		xmlSetProp(code, BAD_CAST "pmVolume", BAD_CAST pm_volume);
	} else if (iss == ISS_30) {
		xmlNodeSetContent(find_child(code, "modelic"), BAD_CAST model_ident_code);
		xmlNodeSetContent(find_child(code, "pmissuer"), BAD_CAST pm_issuer);
		xmlNodeSetContent(find_child(code, "pmnumber"), BAD_CAST pm_number);
		xmlNodeSetContent(find_child(code, "pmvolume"), BAD_CAST pm_volume);
	}
}

static void set_com_code(xmlNodePtr code, enum issue iss, const char *s)
{
	char model_ident_code[15];
	char sender_ident[6];
	char year_of_data_issue[5];
	char seq_number[6];
	char comment_type[2];
	int n;

	n = sscanf(s,
		"%14[^-]-%5s-%4s-%5s-%1s",
		model_ident_code,
		sender_ident,
		year_of_data_issue,
		seq_number,
		comment_type);

	if (n != 5) {
		if (verbosity > QUIET) {
			fprintf(stderr, S_BAD_CODE, "comment", s);
		}
		exit(EXIT_BAD_ARG);
	}

	lowercase(comment_type);

	if (iss == ISS_4X) {
		xmlSetProp(code, BAD_CAST "modelIdentCode", BAD_CAST model_ident_code);
		xmlSetProp(code, BAD_CAST "senderIdent", BAD_CAST sender_ident);
		xmlSetProp(code, BAD_CAST "yearOfDataIssue", BAD_CAST year_of_data_issue);
		xmlSetProp(code, BAD_CAST "seqNumber", BAD_CAST seq_number);
		xmlSetProp(code, BAD_CAST "commentType", BAD_CAST comment_type);
	} else if (iss == ISS_30) {
		xmlNodeSetContent(find_child(code, "modelic"), BAD_CAST model_ident_code);
		xmlNodeSetContent(find_child(code, "sendid"), BAD_CAST sender_ident);
		xmlNodeSetContent(find_child(code, "diyear"), BAD_CAST year_of_data_issue);
		xmlNodeSetContent(find_child(code, "seqnum"), BAD_CAST seq_number);
	}
}

static void set_dml_code(xmlNodePtr code, enum issue iss, const char *s)
{
	char model_ident_code[15];
	char sender_ident[6];
	char dml_type[2];
	char year_of_data_issue[5];
	char seq_number[6];
	int n;

	n = sscanf(s,
		"%14[^-]-%5s-%1s-%4s-%5s",
		model_ident_code,
		sender_ident,
		dml_type,
		year_of_data_issue,
		seq_number);

	if (n != 5) {
		if (verbosity > QUIET) {
			fprintf(stderr, S_BAD_CODE, "data management list", s);
		}
		exit(EXIT_BAD_ARG);
	}

	lowercase(dml_type);

	if (iss == ISS_4X) {
		xmlSetProp(code, BAD_CAST "modelIdentCode", BAD_CAST model_ident_code);
		xmlSetProp(code, BAD_CAST "senderIdent", BAD_CAST sender_ident);
		xmlSetProp(code, BAD_CAST "dmlType", BAD_CAST dml_type);
		xmlSetProp(code, BAD_CAST "yearOfDataIssue", BAD_CAST year_of_data_issue);
		xmlSetProp(code, BAD_CAST "seqNumber", BAD_CAST seq_number);
	} else if (iss == ISS_30) {
		xmlNodeSetContent(find_child(code, "modelic"), BAD_CAST model_ident_code);
		xmlNodeSetContent(find_child(code, "sendid"), BAD_CAST sender_ident);
		xmlSetProp(find_child(code, "dmltype"), BAD_CAST "type", BAD_CAST dml_type);
		xmlNodeSetContent(find_child(code, "diyear"), BAD_CAST year_of_data_issue);
		xmlNodeSetContent(find_child(code, "seqnum"), BAD_CAST seq_number);
	}
}

static void set_code(xmlDocPtr doc, const char *new_code)
{
	xmlNodePtr code;

	code = first_xpath_node(doc, NULL, BAD_CAST
		"//dmIdent/dmCode|"
		"//pmIdent/pmCode|"
		"//commentIdent/commentCode|"
		"//dmlIdent/dmlCode|"
		"//dmaddres/dmc/avee|"
		"//pmaddres/pmc|"
		"//cstatus/ccode|"
		"//dml/dmlc");

	if (!code) {
		return;
	}

	if (xmlStrcmp(code->name, BAD_CAST "dmCode") == 0) {
		set_dm_code(code, ISS_4X, new_code);
	} else if (xmlStrcmp(code->name, BAD_CAST "avee") == 0) {
		set_dm_code(code, ISS_30, new_code);
	} else if (xmlStrcmp(code->name, BAD_CAST "pmCode") == 0) {
		set_pm_code(code, ISS_4X, new_code);
	} else if (xmlStrcmp(code->name, BAD_CAST "pmc") == 0) {
		set_pm_code(code, ISS_30, new_code);
	} else if (xmlStrcmp(code->name, BAD_CAST "commentCode") == 0) {
		set_com_code(code, ISS_4X, new_code);
	} else if (xmlStrcmp(code->name, BAD_CAST "ccode") == 0) {
		set_com_code(code, ISS_30, new_code);
	} else if (xmlStrcmp(code->name, BAD_CAST "dmlCode") == 0) {
		set_dml_code(code, ISS_4X, new_code);
	} else if (xmlStrcmp(code->name, BAD_CAST "dmlc") == 0) {
		set_dml_code(code, ISS_30, new_code);
	}
}

/* Set the techName and/or infoName of the data module instance */
static void set_title(xmlDocPtr doc, const char *tech, const char *info, const xmlChar *info_name_variant, bool no_info_name)
{
	xmlNodePtr dmTitle, techName, infoName, infoNameVariant;
	enum issue iss;
	
	dmTitle  = first_xpath_node(doc, NULL, BAD_CAST
		"//dmAddressItems/dmTitle|"
		"//dmaddres/dmtitle");
	techName = first_xpath_node(doc, NULL, BAD_CAST
		"//dmAddressItems/dmTitle/techName|"
		"//pmAddressItems/pmTitle|"
		"//commentAddressItems/commentTitle|"
		"//dmaddres/dmtitle/techname|"
		"//pmaddres/pmtitle|"
		"//cstatus/ctitle");
	infoName = first_xpath_node(doc, NULL, BAD_CAST
		"//dmAddressItems/dmTitle/infoName|"
		"//dmaddres/dmtitle/infoname");
	infoNameVariant = first_xpath_node(doc, NULL, BAD_CAST
		"//dmAddressItems/dmTitle/infoNameVariant");

	if (!techName) {
		return;
	}

	if (xmlStrcmp(techName->name, BAD_CAST "techName") == 0) {
		iss = ISS_4X;
	} else if (xmlStrcmp(techName->name, BAD_CAST "pmTitle") == 0) {
		iss = ISS_4X;
	} else if (xmlStrcmp(techName->name, BAD_CAST "commentTitle") == 0) {
		iss = ISS_4X;
	} else {
		iss = ISS_30;
	}

	if (tech) {
		xmlNodeSetContent(techName, BAD_CAST tech);
	}

	if (info) {
		if (!infoName) {
			infoName = xmlNewChild(dmTitle, NULL, BAD_CAST (iss == ISS_30 ? "infoname" : "infoName"), NULL);
		}
		xmlNodeSetContent(infoName, BAD_CAST info);
	} else if (no_info_name && infoName) {
		xmlUnlinkNode(infoName);
		xmlFreeNode(infoName);
	}

	if (info_name_variant) {
		if (infoNameVariant) {
			xmlChar *s;
			s = xmlEncodeEntitiesReentrant(doc, info_name_variant);
			xmlNodeSetContent(infoNameVariant, s);
			xmlFree(s);
		} else {
			infoNameVariant = xmlNewTextChild(dmTitle, NULL, BAD_CAST "infoNameVariant", info_name_variant);
		}
	} else if (no_info_name && infoNameVariant) {
		xmlUnlinkNode(infoNameVariant);
		xmlFreeNode(infoNameVariant);
	}
}

static xmlNodePtr create_assert(xmlChar *ident, xmlChar *type, xmlChar *values, enum issue iss)
{
	xmlNodePtr assert;

	assert = xmlNewNode(NULL, BAD_CAST "assert");

	xmlSetProp(assert, BAD_CAST (iss == ISS_30 ? "actidref" : "applicPropertyIdent"), ident);
	xmlSetProp(assert, BAD_CAST (iss == ISS_30 ? "actreftype" : "applicPropertyType"), type);
	xmlSetProp(assert, BAD_CAST (iss == ISS_30 ? "actvalues" : "applicPropertyValues"), values);

	return assert;
}

static xmlNodePtr create_or(xmlChar *ident, xmlChar *type, xmlNodePtr values, enum issue iss)
{
	xmlNodePtr or, cur;

	or = xmlNewNode(NULL, BAD_CAST "evaluate");
	xmlSetProp(or, BAD_CAST (iss == ISS_30 ? "operator" : "andOr"), BAD_CAST "or");

	for (cur = values->children; cur; cur = cur->next) {
		xmlChar *value;

		value = xmlNodeGetContent(cur);
		xmlAddChild(or, create_assert(ident, type, value, iss));
		xmlFree(value);
	}

	return or;
}

/* Set the applicability for the whole data module instance */
static void set_applic(xmlDocPtr doc, xmlNodePtr defs, int napplics, char *new_text, bool combine)
{
	xmlNodePtr new_applic, new_displayText, new_simplePara, new_evaluate, cur, applic;
	enum issue iss;

	applic = first_xpath_node(doc, NULL, BAD_CAST "//dmStatus/applic|//pmStatus/applic|//status/applic|//pmstatus/applic");

	if (!applic) {
		return;
	} else if (xmlStrcmp(applic->parent->name, BAD_CAST "dmStatus") == 0 || xmlStrcmp(applic->parent->name, BAD_CAST "pmStatus") == 0) {
		iss = ISS_4X;
	} else if (xmlStrcmp(applic->parent->name, BAD_CAST "status") == 0 || xmlStrcmp(applic->parent->name, BAD_CAST "pmstatus") == 0) {
		iss = ISS_30;
	} else {
		return;
	}

	new_applic = xmlNewNode(NULL, BAD_CAST "applic");
	xmlAddNextSibling(applic, new_applic);

	if (strcmp(new_text, "") != 0) {
		new_displayText = xmlNewChild(new_applic, NULL, BAD_CAST (iss == ISS_30 ? "displaytext" : "displayText"), NULL);
		new_simplePara = xmlNewChild(new_displayText, NULL, BAD_CAST (iss == ISS_30 ? "p" : "simplePara"), NULL);
		xmlNodeSetContent(new_simplePara, BAD_CAST new_text);
	}

	if (combine && first_xpath_node(doc, applic, BAD_CAST "assert|evaluate|expression")) {
		new_applic = xmlNewChild(new_applic, NULL, BAD_CAST "evaluate", NULL);
		xmlSetProp(new_applic, BAD_CAST (iss == ISS_30 ? "operator" : "andOr"), BAD_CAST "and");
		for (cur = applic->children; cur; cur = cur->next) {
			if (cur->type != XML_ELEMENT_NODE || xmlStrcmp(cur->name, BAD_CAST "displayText") == 0 || xmlStrcmp(cur->name, BAD_CAST "displaytext") == 0) {
				continue;
			}
			xmlAddChild(new_applic, xmlCopyNode(cur, 1));
		}
	}

	if (napplics > 1) {
		new_evaluate = xmlNewChild(new_applic, NULL, BAD_CAST "evaluate", NULL);
		xmlSetProp(new_evaluate, BAD_CAST (iss == ISS_30 ? "operator" : "andOr"), BAD_CAST "and");
	} else {
		new_evaluate = new_applic;
	}

	for (cur = defs->children; cur; cur = cur->next) {
		xmlChar *user_def;

		user_def = xmlGetProp(cur, BAD_CAST "userDefined");

		if (xmlStrcmp(user_def, BAD_CAST "true") == 0) {
			xmlChar *cur_ident, *cur_type, *cur_value;

			cur_ident = xmlGetProp(cur, BAD_CAST "applicPropertyIdent");
			cur_type  = xmlGetProp(cur, BAD_CAST "applicPropertyType");
			cur_value = xmlGetProp(cur, BAD_CAST "applicPropertyValues");

			if (cur_value) {
				xmlAddChild(new_evaluate, create_assert(cur_ident, cur_type, cur_value, iss));
			} else {
				xmlAddChild(new_evaluate, create_or(cur_ident, cur_type, cur, iss));
			}

			xmlFree(cur_ident);
			xmlFree(cur_type);
			xmlFree(cur_value);
		}

		xmlFree(user_def);
	}

	xmlUnlinkNode(applic);
	xmlFreeNode(applic);
}

/* Set the language/country for the data module instance */
static void set_lang(xmlDocPtr doc, const char *lang)
{
	xmlNodePtr language;
	char *l;
	char *language_iso_code;
	char *country_iso_code;
	enum issue iss;

	language = first_xpath_node(doc, NULL, LANGUAGE_XPATH);

	if (!language) {
		return;
	} else if (xmlStrcmp(language->parent->name, BAD_CAST "dmIdent") == 0 ||
	           xmlStrcmp(language->parent->name, BAD_CAST "pmIdent") == 0) {
		iss = ISS_4X;
	} else if (xmlStrcmp(language->parent->name, BAD_CAST "dmaddres") == 0 ||
	           xmlStrcmp(language->parent->name, BAD_CAST "pmaddres") == 0) {
		iss = ISS_30;
	} else {
		return;
	}

	l = strdup(lang);
	language_iso_code = strtok(l, "-");
	country_iso_code = strtok(NULL, "");

	lowercase(language_iso_code);
	uppercase(country_iso_code);

	xmlSetProp(language, BAD_CAST (iss == ISS_30 ? "language" : "languageIsoCode"), BAD_CAST language_iso_code);
	xmlSetProp(language, BAD_CAST (iss == ISS_30 ? "country" : "countryIsoCode"), BAD_CAST country_iso_code);

	free(l);
}

static bool auto_name(char *out, char *src, xmlDocPtr dm, const char *dir, bool noiss)
{
	struct ident ident = {0};
	char iss[8] = "";
	const char *dname, *sep;

	if (strcmp(dir, ".") == 0) {
		dname = "";
		sep = "";
	} else {
		dname = dir;
		sep = "/";
	}

	if (!init_ident(&ident, dm)) {
		char *base;
		base = basename(src);
		snprintf(out, PATH_MAX, "%s%s%s", dname, sep, base);
		return true;
	}

	if (ident.type == DM || ident.type == PM || ident.type == COM || ident.type == UPF) {
		int i;
		for (i = 0; ident.languageIsoCode[i]; ++i) {
			ident.languageIsoCode[i] = toupper(ident.languageIsoCode[i]);
		}
	}

	if (ident.type == DML || ident.type == COM) {
		ident.dmlCommentType[0] = toupper(ident.dmlCommentType[0]);
	}	

	if (!noiss && (ident.type == DM || ident.type == PM || ident.type == DML || ident.type == IMF || ident.type == UPF)) {
		sprintf(iss, "_%s-%s", ident.issueNumber, ident.inWork);
	}

	if (ident.type == PM) {
		if (ident.extended) {
			sprintf(out, "%s%sPME-%s-%s-%s-%s-%s-%s%s_%s-%s.XML",
				dname,
				sep,
				ident.extensionProducer,
				ident.extensionCode,
				ident.modelIdentCode,
				ident.senderIdent,
				ident.pmNumber,
				ident.pmVolume,
				iss,
				ident.languageIsoCode,
				ident.countryIsoCode);
		} else {
			sprintf(out, "%s%sPMC-%s-%s-%s-%s%s_%s-%s.XML",
				dname,
				sep,
				ident.modelIdentCode,
				ident.senderIdent,
				ident.pmNumber,
				ident.pmVolume,
				iss,
				ident.languageIsoCode,
				ident.countryIsoCode);
		}
	} else if (ident.type == DML) {
		sprintf(out, "%s%sDML-%s-%s-%s-%s-%s%s.XML",
			dname,
			sep,
			ident.modelIdentCode,
			ident.senderIdent,
			ident.dmlCommentType,
			ident.yearOfDataIssue,
			ident.seqNumber,
			iss);
	} else if (ident.type == DM || ident.type == UPF) {
		char learn[6] = "";

		if (ident.learnCode && ident.learnEventCode) {
			sprintf(learn, "-%s%s", ident.learnCode, ident.learnEventCode);
		}

		if (ident.extended) {
			sprintf(out, "%s%s%s-%s-%s-%s-%s-%s-%s%s-%s-%s%s-%s%s-%s%s%s_%s-%s.XML",
				dname,
				sep,
				ident.type == DM ? "DME" : "UPE",
				ident.extensionProducer,
				ident.extensionCode,
				ident.modelIdentCode,
				ident.systemDiffCode,
				ident.systemCode,
				ident.subSystemCode,
				ident.subSubSystemCode,
				ident.assyCode,
				ident.disassyCode,
				ident.disassyCodeVariant,
				ident.infoCode,
				ident.infoCodeVariant,
				ident.itemLocationCode,
				learn,
				iss,
				ident.languageIsoCode,
				ident.countryIsoCode);
		} else {
			sprintf(out, "%s%s%s-%s-%s-%s-%s%s-%s-%s%s-%s%s-%s%s%s_%s-%s.XML",
				dname,
				sep,
				ident.type == DM ? "DMC" : "UPF",
				ident.modelIdentCode,
				ident.systemDiffCode,
				ident.systemCode,
				ident.subSystemCode,
				ident.subSubSystemCode,
				ident.assyCode,
				ident.disassyCode,
				ident.disassyCodeVariant,
				ident.infoCode,
				ident.infoCodeVariant,
				ident.itemLocationCode,
				learn,
				iss,
				ident.languageIsoCode,
				ident.countryIsoCode);
		}
	} else if (ident.type == COM) {
		sprintf(out, "%s%sCOM-%s-%s-%s-%s-%s_%s-%s.XML",
			dname,
			sep,
			ident.modelIdentCode,
			ident.senderIdent,
			ident.yearOfDataIssue,
			ident.seqNumber,
			ident.dmlCommentType,
			ident.languageIsoCode,
			ident.countryIsoCode);
	} else if (ident.type == DDN) {
		sprintf(out, "%s%sDDN-%s-%s-%s-%s-%s.XML",
			dname,
			sep,
			ident.modelIdentCode,
			ident.senderIdent,
			ident.receiverIdent,
			ident.yearOfDataIssue,
			ident.seqNumber);
	} else if (ident.type == IMF) {
		sprintf(out, "%s%sIMF-%s%s.XML",
			dname,
			sep,
			ident.imfIdentIcn,
			iss);
	} else {
		return false;
	}

	free_ident(&ident);

	return true;
}

/* Get the appropriate built-in CIR repository XSLT by name */
static bool get_cir_xsl(const char *cirtype, unsigned char **xsl, unsigned int *len)
{
	if (strcmp(cirtype, "accessPointRepository") == 0) {
		*xsl = cirxsl_accessPointRepository_xsl;
		*len = cirxsl_accessPointRepository_xsl_len;
	} else if (strcmp(cirtype, "applicRepository") == 0) {
		*xsl = cirxsl_applicRepository_xsl;
		*len = cirxsl_applicRepository_xsl_len;
	} else if (strcmp(cirtype, "cautionRepository") == 0) {
		*xsl = cirxsl_cautionRepository_xsl;
		*len = cirxsl_cautionRepository_xsl_len;
	} else if (strcmp(cirtype, "circuitBreakerRepository") == 0) {
		*xsl = cirxsl_circuitBreakerRepository_xsl;
		*len = cirxsl_circuitBreakerRepository_xsl_len;
	} else if (strcmp(cirtype, "controlIndicatorRepository") == 0) {
		*xsl = cirxsl_controlIndicatorRepository_xsl;
		*len = cirxsl_controlIndicatorRepository_xsl_len;
	} else if (strcmp(cirtype, "enterpriseRepository") == 0) {
		*xsl = cirxsl_enterpriseRepository_xsl;
		*len = cirxsl_enterpriseRepository_xsl_len;
	} else if (strcmp(cirtype, "functionalItemRepository") == 0) {
		*xsl = cirxsl_functionalItemRepository_xsl;
		*len = cirxsl_functionalItemRepository_xsl_len;
	} else if (strcmp(cirtype, "einlist") == 0) {
		*xsl = cirxsl_einlist_xsl;
		*len = cirxsl_einlist_xsl_len;
	} else if (strcmp(cirtype, "partRepository") == 0) {
		*xsl = cirxsl_partRepository_xsl;
		*len = cirxsl_partRepository_xsl_len;
	} else if (strcmp(cirtype, "illustratedPartsCatalog") == 0) {
		*xsl = cirxsl_illustratedPartsCatalog_xsl;
		*len = cirxsl_illustratedPartsCatalog_xsl_len;
	} else if (strcmp(cirtype, "supplyRepository") == 0) {
		*xsl = cirxsl_supplyRepository_xsl;
		*len = cirxsl_supplyRepository_xsl_len;
	} else if (strcmp(cirtype, "toolRepository") == 0) {
		*xsl = cirxsl_toolRepository_xsl;
		*len = cirxsl_toolRepository_xsl_len;
	} else if (strcmp(cirtype, "warningRepository") == 0) {
		*xsl = cirxsl_warningRepository_xsl;
		*len = cirxsl_warningRepository_xsl_len;
	} else if (strcmp(cirtype, "zoneRepository") == 0) {
		*xsl = cirxsl_zoneRepository_xsl;
		*len = cirxsl_zoneRepository_xsl_len;
	} else {
		if (verbosity > QUIET) {
			fprintf(stderr, S_NO_XSLT, cirtype);
		}
		return false;
	}

	return true;
}

/* Dump built-in XSLT for resolving CIR repository dependencies */
static void dump_cir_xsl(const char *repo)
{
	unsigned char *xsl;
	unsigned int len;

	if (get_cir_xsl(repo, &xsl, &len)) {
		printf("%.*s", len, xsl);
	} else {
		exit(EXIT_BAD_ARG);
	}
}

/* Use user-supplied XSL script to resolve CIR references. */
static void undepend_cir_xsl(xmlDocPtr dm, xmlDocPtr cir, xsltStylesheetPtr style)
{
	xmlDocPtr res, muxdoc;
	xmlNodePtr mux, old;

	muxdoc = xmlNewDoc(BAD_CAST "1.0");
	mux = xmlNewNode(NULL, BAD_CAST "mux");
	xmlDocSetRootElement(muxdoc, mux);
	xmlAddChild(mux, xmlCopyNode(xmlDocGetRootElement(dm), 1));
	xmlAddChild(mux, xmlCopyNode(xmlDocGetRootElement(cir), 1));

	res = xsltApplyStylesheet(style, muxdoc, NULL);

	old = xmlDocSetRootElement(dm, xmlCopyNode(first_xpath_node(res, NULL, BAD_CAST "/mux/*[1]"), 1));
	xmlFreeNode(old);

	xmlFreeDoc(res);
	xmlFreeDoc(muxdoc);
}

/* Apply the user-defined applicability to the CIR data module, then call the
 * appropriate function for the specific type of CIR. */
static xmlNodePtr undepend_cir(xmlDocPtr dm, xmlNodePtr defs, const char *cirdocfname, bool add_src, const char *cir_xsl, xmlDocPtr def_cir_xsl)
{
	xmlDocPtr cir;
	xmlXPathContextPtr ctxt;
	xmlXPathObjectPtr results;

	xmlNodePtr cirnode;
	xmlNodePtr content;
	xmlNodePtr referencedApplicGroup;

	char *cirtype;

	xmlDocPtr styledoc = NULL;

	cir = read_xml_doc(cirdocfname);

	if (!cir) {
		if (verbosity > QUIET) {
			fprintf(stderr, S_INVALID_CIR, cirdocfname);
		}
		exit(EXIT_BAD_XML);
	}

	ctxt = xmlXPathNewContext(cir);

	results = xmlXPathEvalExpression(BAD_CAST "//content", ctxt);

	if (xmlXPathNodeSetIsEmpty(results->nodesetval)) {
		cirnode = xmlDocGetRootElement(cir);
	} else {
		content = results->nodesetval->nodeTab[0];
		xmlXPathFreeObject(results);

		results = xmlXPathEvalExpression(BAD_CAST "//referencedApplicGroup", ctxt);

		if (!xmlXPathNodeSetIsEmpty(results->nodesetval)) {
			referencedApplicGroup = results->nodesetval->nodeTab[0];
			strip_applic(defs, referencedApplicGroup, content);
		}

		xmlXPathFreeObject(results);

		results = xmlXPathEvalExpression(BAD_CAST
			"//content/commonRepository/*[position()=last()]|"
			"//content/techRepository/*[position()=last()]|"
			"//content/techrep/*[position()=last()]|"
			"//content/illustratedPartsCatalog",
			ctxt);

		if (xmlXPathNodeSetIsEmpty(results->nodesetval)) {
			cirnode = xmlDocGetRootElement(cir);
		} else {
			cirnode = results->nodesetval->nodeTab[0];
		}
	}

	xmlXPathFreeObject(results);

	cirtype = (char *) cirnode->name;

	if (cir_xsl) {
		styledoc = read_xml_doc(cir_xsl);
	} else if (def_cir_xsl) {
		styledoc = xmlCopyDoc(def_cir_xsl, 1);
	} else {
		unsigned char *xsl = NULL;
		unsigned int len = 0;

		if (!get_cir_xsl(cirtype, &xsl, &len)) {
			add_src = false;
		}

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

	if (styledoc) {
		xsltStylesheetPtr style;
		style = xsltParseStylesheetDoc(styledoc);
		undepend_cir_xsl(dm, cir, style);
		xsltFreeStylesheet(style);
	}

	xmlXPathFreeContext(ctxt);

	if (first_xpath_node(dm, NULL, BAD_CAST "//idstatus")) {
		add_src = false;
	}

	if (add_src) {
		xmlNodePtr dmIdent;

		dmIdent = xpath_first_node(cir, NULL, BAD_CAST "//dmIdent");

		if (dmIdent) {
			xmlNodePtr security, repositorySourceDmIdent, cur;

			security = xpath_first_node(dm, NULL, BAD_CAST "//security");

			repositorySourceDmIdent = xmlNewNode(NULL, BAD_CAST "repositorySourceDmIdent");
			repositorySourceDmIdent = xmlAddPrevSibling(security, repositorySourceDmIdent);

			for (cur = dmIdent->children; cur; cur = cur->next) {
				xmlAddChild(repositorySourceDmIdent, xmlCopyNode(cur, 1));
			}
		}
	}

	xmlFreeDoc(cir);

	return xmlDocGetRootElement(dm);
}

/* General XSLT transformation with embedded stylesheet, preserving the DTD. */
static void transform_doc(xmlDocPtr doc, unsigned char *xml, unsigned int len, const char **params)
{
	xmlDocPtr styledoc, res, src;
	xsltStylesheetPtr style;
	xmlNodePtr old;

	styledoc = read_xml_mem((const char *) xml, len);
	style = xsltParseStylesheetDoc(styledoc);

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

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

	xmlFreeDoc(res);
	xsltFreeStylesheet(style);
}

/* Remove all change markup from the instance. */
static void remove_change_markup(xmlDocPtr doc)
{
	transform_doc(doc, xsl_remove_change_markup_xsl, xsl_remove_change_markup_xsl_len, NULL);
}

/* Set the issue type of the instance. */
static void set_issue_type(xmlDocPtr doc, const char *type)
{
	xmlNodePtr status;

	status = first_xpath_node(doc, NULL, BAD_CAST "//dmStatus|//pmStatus|//commentStatus|//dmlStatus|//scormContentPackageStatus|//issno");

	if (xmlStrcmp(status->name, BAD_CAST "issno") == 0) {
		xmlSetProp(status, BAD_CAST "type", BAD_CAST type);
	} else {
		xmlSetProp(status, BAD_CAST "issueType", BAD_CAST type);
	}
}

/* Add a default RFU when a "new" master produces non-new instances. */
static void add_default_rfu(xmlDocPtr dm)
{
	xmlNodePtr node, rfu;
	bool iss30;

	/* Issue 4.2+ allows "new" DMs to have RFUs, so use this instead if
	 * present. */
	if (first_xpath_node(dm, NULL, BAD_CAST "//rfu|//reasonForUpdate")) {
		return;
	}

	node = first_xpath_node(dm, NULL, BAD_CAST
		"("
		"//dmStatus/*|//status/*|"
		"//pmStatus/*|//pmstatus/*|"
		"//commentStatus/*|"
		"//ddnStatus/*|"
		"//dmlStatus/*|"
		"//scormContentPackageStatus/*"
		")[not(self::productSafety or self::remarks)][last()]");

	if (!node) {
		return;
	}

	iss30 = xmlStrcmp(node->parent->name, BAD_CAST "status") == 0 || xmlStrcmp(node->parent->name, BAD_CAST "pmstatus") == 0;

	rfu = xmlNewNode(node->ns, BAD_CAST (iss30 ? "rfu" : "reasonForUpdate"));
	xmlAddNextSibling(node, rfu);
	xmlNewTextChild(rfu, rfu->ns, BAD_CAST (iss30 ? "p" : "simplePara"), DEFAULT_RFU);
}

/* Set the issue and inwork numbers of the instance. */
static void set_issue(xmlDocPtr dm, char *issinfo, bool incr_iss)
{
	char issue[32], inwork[32];
	xmlNodePtr issueInfo;

	if (!(issueInfo = first_xpath_node(dm, NULL, ISSUE_INFO_XPATH))) {
		return;
	}

	if (incr_iss) {
		xmlChar *issue_s, *inwork_s;
		int inwork_i;

		issue_s  = first_xpath_value(dm, issueInfo, BAD_CAST "@issueNumber|@issno");
		inwork_s = first_xpath_value(dm, issueInfo, BAD_CAST "@inWork|@inwork");

		strcpy(issue, (char *) issue_s);
		if (inwork_s) {
			strcpy(inwork, (char *) inwork_s);
		} else {
			strcpy(inwork, "00");
		}

		xmlFree(issue_s);
		xmlFree(inwork_s);

		inwork_i = atoi(inwork);

		snprintf(inwork, 32, "%.2d", inwork_i + 1);
	} else if (sscanf(issinfo, "%3s-%2s", issue, inwork) != 2) {
		if (verbosity > QUIET) {
			fprintf(stderr, ERR_PREFIX S_INVALID_ISSFMT);
		}
		exit(EXIT_MISSING_ARGS);
	}

	/* If the issue is set below 001-01, there cannot be change marks, and issue type is "new" */
	if (strcmp(issue, "000") == 0 || (strcmp(issue, "001") == 0 && strcmp(inwork, "00") == 0)) {
		remove_change_markup(dm);
		set_issue_type(dm, "new");
	/* Otherwise, try to default to the issue type of the master. */
	} else {
		xmlChar *type;

		type = first_xpath_value(dm, NULL, BAD_CAST "//@issueType|//issno/@type");

		/* If the master is "new" but the target issue cannot be,
		 * default to "status" as their should be no change marks. */
		if (xmlStrcmp(type, BAD_CAST "new") == 0) {
			set_issue_type(dm, "status");
			add_default_rfu(dm);
		/* Otherwise, use the master's issue type. */
		} else {
			set_issue_type(dm, (char *) type);
		}

		xmlFree(type);
	}

	issueInfo = first_xpath_node(dm, NULL, ISSUE_INFO_XPATH);

	if (xmlStrcmp(issueInfo->name, BAD_CAST "issueInfo") == 0) {
		xmlSetProp(issueInfo, BAD_CAST "issueNumber", BAD_CAST issue);
		xmlSetProp(issueInfo, BAD_CAST "inWork", BAD_CAST inwork);
	} else {
		xmlSetProp(issueInfo, BAD_CAST "issno", BAD_CAST issue);
		xmlSetProp(issueInfo, BAD_CAST "inwork", BAD_CAST inwork);
	}
}

/* Set the issue date of the instance. */
static void set_issue_date(xmlDocPtr doc, const char *year, const char *month, const char *day)
{
	xmlNodePtr issueDate;

	issueDate = first_xpath_node(doc, NULL, BAD_CAST "//issueDate|//issdate");

	xmlSetProp(issueDate, BAD_CAST "year", BAD_CAST year);
	xmlSetProp(issueDate, BAD_CAST "month", BAD_CAST month);
	xmlSetProp(issueDate, BAD_CAST "day", BAD_CAST day);
}

/* Set the securty classification of the instance. */
static void set_security(xmlDocPtr dm, char *sec)
{
	xmlNodePtr security;
	enum issue iss;

	security = first_xpath_node(dm, NULL, BAD_CAST "//security");

	if (!security) {
		return;
	} else if (xmlStrcmp(security->parent->name, BAD_CAST "dmStatus") == 0 || xmlStrcmp(security->parent->name, BAD_CAST "pmStatus") == 0) {
		iss = ISS_4X;
	} else {
		iss = ISS_30;
	}

	xmlSetProp(security, BAD_CAST (iss == ISS_30 ? "class" : "securityClassification"), BAD_CAST sec);
}

/* Get the originator of the source. If it has no originator
 * (e.g. pub modules do not require one) then create one in the
 * instance.
 */
static xmlNodePtr find_or_create_orig(xmlDocPtr doc)
{
	xmlNodePtr orig, rpc;
	orig = first_xpath_node(doc, NULL, BAD_CAST "//originator|//orig");
	if (!orig) {
		rpc = first_xpath_node(doc, NULL, BAD_CAST "//responsiblePartnerCompany|//rpc");
		orig = xmlNewNode(NULL, BAD_CAST (xmlStrcmp(rpc->name, BAD_CAST "rpc") == 0 ? "orig" : "originator"));
		orig = xmlAddNextSibling(rpc, orig);
	}
	return orig;
}

/* Set the originator of the instance.
 *
 * When origspec == NULL, a default code and name are used to identify this
 * tool as the originator.
 *
 * Otherwise, origspec is a string in the form of "CODE/NAME", where CODE is
 * the NCAGE code and NAME is the enterprise name.
 */
static void set_orig(xmlDocPtr doc, const char *origspec)
{
	xmlNodePtr originator;
	const char *code, *name;
	enum issue iss;
	char *s;

	if (origspec) {
		s = strdup(origspec);
		code = strtok(s, "/");
		name = strtok(NULL, "");
	} else {
		code = DEFAULT_ORIG_CODE;
		name = DEFAULT_ORIG_NAME;
	}

	originator = find_or_create_orig(doc);

	if (xmlStrcmp(originator->name, BAD_CAST "orig") == 0) {
		iss = ISS_30;
	} else {
		iss = ISS_4X;
	}

	if (code && strcmp(code, "-") != 0) {
		if (iss == ISS_30) {
			xmlNodeSetContent(originator, BAD_CAST code);
		} else {
			xmlSetProp(originator, BAD_CAST "enterpriseCode", BAD_CAST code);
		}
	}

	if (name) {
		if (iss == ISS_30) {
			xmlSetProp(originator, BAD_CAST "origname", BAD_CAST name);
		} else {
			xmlNodePtr enterpriseName;
			enterpriseName = find_child(originator, "enterpriseName");	
			if (enterpriseName) {
				xmlNodeSetContent(enterpriseName, BAD_CAST name);
			} else {
				xmlNewChild(originator, NULL, BAD_CAST "enterpriseName", BAD_CAST name);
			}
		}
	}

	if (origspec) {
		free(s);
	}
}

/* Determine if the whole object is applicable. */
static bool check_wholedm_applic(xmlDocPtr dm, xmlNodePtr defs)
{
	xmlNodePtr applic;

	applic = first_xpath_node(dm, NULL, BAD_CAST "//dmStatus/applic|//pmStatus/applic");

	if (!applic) {
		return true;
	}

	return eval_applic_stmt(defs, applic, true);
}

/* Read applicability definitions from the <assign> elements of a
 * product instance in the specified PCT data module.\
 */
static void load_applic_from_pct(xmlNodePtr defs, int *napplics, xmlDocPtr pct, const char *pctfname, const char *product)
{
	xmlXPathContextPtr ctx;
	xmlXPathObjectPtr obj;
	xmlChar *xpath;

	ctx = xmlXPathNewContext(pct);

	/* If the product is in the form of IDENT:TYPE=VALUE, it identifies the
	 * primary key of a product instance.
	 *
	 * Otherwise, it is simply the XML ID of a product instance.
	 */
	if (match_pattern(BAD_CAST product, BAD_CAST "[^:]+:(prodattr|condition)=[^|~]+")) {
		char *prod, *ident, *type, *value;

		prod  = strdup(product);

		ident = strtok(prod, ":");
		type  = strtok(NULL, "=");
		value = strtok(NULL, "");

		xmlXPathRegisterVariable(ctx, BAD_CAST "ident", xmlXPathNewCString(ident));
		xmlXPathRegisterVariable(ctx, BAD_CAST "type" , xmlXPathNewCString(type));
		xmlXPathRegisterVariable(ctx, BAD_CAST "value", xmlXPathNewCString(value));
		xpath = BAD_CAST "//product[assign[@applicPropertyIdent=$ident and @applicPropertyType=$type and @applicPropertyValue=$value]]/assign";

		free(prod);
	} else {
		xmlXPathRegisterVariable(ctx, BAD_CAST "id", xmlXPathNewCString(product));
		xpath = BAD_CAST "//product[@id=$id]/assign";
	}

	obj = xmlXPathEvalExpression(BAD_CAST xpath, ctx);

	if (xmlXPathNodeSetIsEmpty(obj->nodesetval)) {
		if (verbosity > QUIET) {
			fprintf(stderr, S_NO_PRODUCT, product, pctfname);
		}
	} else {
		int i;

		for (i = 0; i < obj->nodesetval->nodeNr; ++i) {
			xmlChar *ident, *type, *value;

			ident = xmlGetProp(obj->nodesetval->nodeTab[i],
				BAD_CAST "applicPropertyIdent");
			type  = xmlGetProp(obj->nodesetval->nodeTab[i],
				BAD_CAST "applicPropertyType");
			value = xmlGetProp(obj->nodesetval->nodeTab[i],
				BAD_CAST "applicPropertyValue");

			define_applic(defs, napplics, ident, type, value, true, true);

			xmlFree(ident);
			xmlFree(type);
			xmlFree(value);
		}
	}

	xmlXPathFreeObject(obj);
	xmlXPathFreeContext(ctx);
}

/* Remove the extended identification from the instance. */
static void strip_extension(xmlDocPtr doc)
{
	xmlNodePtr ext;

	ext = first_xpath_node(doc, NULL, BAD_CAST "//identExtension");

	xmlUnlinkNode(ext);
	xmlFreeNode(ext);
}

/* Flatten alts elements. */
static void flatten_alts(xmlDocPtr doc, bool fix_alts_refs)
{
	char *params[3];

	params[0] = "fix-alts-refs";
	params[1] = fix_alts_refs ? "true()" : "false()";
	params[2] = NULL;

	transform_doc(doc, xsl_flatten_alts_xsl, xsl_flatten_alts_xsl_len, (const char **) params);
}

/* Removes invalid empty sections in a PM after all references have
 * been filtered out.
 */
static void remove_empty_pmentries(xmlDocPtr doc)
{
	transform_doc(doc, ___common_remove_empty_pmentries_xsl, ___common_remove_empty_pmentries_xsl_len, NULL);
}

/* Fix certain elements automatically after filtering. */
static void autocomplete(xmlDocPtr doc)
{
	transform_doc(doc, xsl_autocomplete_xsl, xsl_autocomplete_xsl_len, NULL);
}

/* Copy acronym definitions to all acronyms prior to filtering. */
static void fix_acronyms_pre(xmlDocPtr doc)
{
	transform_doc(doc, xsl_acronyms_pre_xsl, xsl_acronyms_pre_xsl_len, NULL);
}

/* Turn occurrences of acronyms after the first into acronymTerms that reference the first. */
static void fix_acronyms_post(xmlDocPtr doc)
{
	transform_doc(doc, xsl_acronyms_post_xsl, xsl_acronyms_post_xsl_len, NULL);
}

/* Insert a custom comment. */
static void insert_comment(xmlDocPtr doc, const char *text, const char *path)
{
	xmlNodePtr comment, pos;

	comment = xmlNewComment(BAD_CAST text);
	pos = first_xpath_node(doc, NULL, BAD_CAST path);

	if (!pos)
		return;

	if (pos->children) {
		xmlAddPrevSibling(pos->children, comment);
	} else {
		xmlAddChild(pos, comment);
	}
}

/* Read an applicability assign in the form of ident:type=value */
static void read_applic(xmlNodePtr defs, int *napplics, char *s)
{
	char *ident, *type, *value;

	if (!match_pattern(BAD_CAST s, BAD_CAST "[^:]+:(prodattr|condition)=[^|~]+")) {
		if (verbosity > QUIET) {
			fprintf(stderr, S_BAD_ASSIGN, s);
		}
		exit(EXIT_BAD_APPLIC);
	}

	ident = strtok(s, ":");
	type  = strtok(NULL, "=");
	value = strtok(NULL, "");

	define_applic(defs, napplics, BAD_CAST ident, BAD_CAST type, BAD_CAST value, false, true);
}

/* Set the remarks for the object */
static void set_remarks(xmlDocPtr doc, const char *s)
{
	xmlNodePtr status, remarks;

	status = first_xpath_node(doc, NULL, BAD_CAST
		"//dmStatus|"
		"//pmStatus|"
		"//commentStatus|"
		"//dmlStatus|"
		"//status|"
		"//pmstatus|"
		"//cstatus|"
		"//dml[dmlc]");

	if (!status) {
		return;
	}

	remarks = first_xpath_node(doc, status, BAD_CAST "//remarks");

	if (remarks) {
		xmlNodePtr cur, next;
		cur = remarks->children;
		while (cur) {
			next = cur->next;
			xmlUnlinkNode(cur);
			xmlFreeNode(cur);
			cur = next;
		}
	} else {
		remarks = xmlNewChild(status, NULL, BAD_CAST "remarks", NULL);
	}

	if (xmlStrcmp(status->parent->name, BAD_CAST "identAndStatusSection") == 0) {
		xmlNewChild(remarks, NULL, BAD_CAST "simplePara", BAD_CAST s);
	} else {
		xmlNewChild(remarks, NULL, BAD_CAST "p", BAD_CAST s);
	}
}

/* Return whether objects with the given classification code should be
 * instantiated. */
static bool valid_object(xmlDocPtr doc, const char *path, const char *codes)
{
	xmlNodePtr obj;
	xmlChar *code;
	bool has;
	if (!(obj = first_xpath_node(doc, NULL, BAD_CAST path))) {
		return true;
	}
	code = xmlNodeGetContent(obj);
	has = strstr(codes, (char *) code);
	xmlFree(code);
	return has;
}

/* Remove elements that have an attribute whose value does not match a given
 * set of valid values. */
static void filter_elements_by_att(xmlDocPtr doc, const char *att, const char *codes)
{
	xmlXPathContextPtr ctx;
	xmlXPathObjectPtr obj;
	xmlChar xpath[256];

	ctx = xmlXPathNewContext(doc);

	xmlStrPrintf(xpath, 256, "//content//*[@%s]", att);
	obj = xmlXPathEvalExpression(xpath, ctx);

	if (!xmlXPathNodeSetIsEmpty(obj->nodesetval)) {
		int i;
		for (i = 0; i < obj->nodesetval->nodeNr; ++i) {
			xmlChar *val;

			val = xmlGetProp(obj->nodesetval->nodeTab[i], BAD_CAST att);

			if (!strstr(codes, (char *) val)) {
				xmlUnlinkNode(obj->nodesetval->nodeTab[i]);
				xmlFreeNode(obj->nodesetval->nodeTab[i]);
				obj->nodesetval->nodeTab[i] = NULL;
			}

			xmlFree(val);
		}
	}

	xmlXPathFreeObject(obj);
	xmlXPathFreeContext(ctx);
}

/* Determine if an object is "deleted". */
static bool is_deleted(xmlDocPtr doc)
{
	xmlChar *isstype;
	bool is;

	isstype = first_xpath_value(doc, NULL, BAD_CAST "//dmStatus/@issueType|//dmaddres/issno/@type|//pmStatus/@issueType|//pmaddres/issno/@type|//commentStatus/@issueType|//dmlStatus/@issueType|//scormContentPackageStatus/@issueType");
	is = xmlStrcmp(isstype, BAD_CAST "deleted") == 0;
	xmlFree(isstype);

	return is;
}

/* Determine whether or not to create an instance based on the object's:
 * - Applicability
 * - Skill level
 * - Security classification
 * - Issue type
 */
static bool create_instance(xmlDocPtr doc, xmlNodePtr defs, const char *skills, const char *securities, bool delete)
{
	if (!check_wholedm_applic(doc, defs)) {
		return false;
	}

	if (securities && !valid_object(doc, "//dmStatus/security/@securityClassification", securities)) {
		return false;
	}

	if (skills && !valid_object(doc, "//dmStatus/skillLevel/@skillLevelCode", skills)) {
		return false;
	}

	if (delete && is_deleted(doc)) {
		return false;
	}

	return true;
}

/* Set the skill level code of the instance. */
static void set_skill(xmlDocPtr doc, const char *skill)
{
	xmlNodePtr skill_level;

	if (xmlStrcmp(xmlDocGetRootElement(doc)->name, BAD_CAST "dmodule") != 0) {
		return;
	}

	skill_level = first_xpath_node(doc, NULL, BAD_CAST "//skillLevel");

	if (!skill_level) {
		xmlNodePtr node;
		node = first_xpath_node(doc, NULL, BAD_CAST
			"("
			"//qualityAssurance|"
			"//systemBreakdownCode|"
			"//functionalItemCode|"
			"//dmStatus/functionalItemRef"
			")[last()]");
		skill_level = xmlNewNode(NULL, BAD_CAST "skillLevel");
		xmlAddNextSibling(node, skill_level);
	}

	xmlSetProp(skill_level, BAD_CAST "skillLevelCode", BAD_CAST skill);
}

/* Find a data module filename in the current directory based on the dmRefIdent
 * element. */
static bool find_dmod_fname(char *dst, xmlNodePtr dmRefIdent, bool ignore_iss)
{
	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 = first_xpath_node(NULL, dmRefIdent, BAD_CAST "dmCode|avee");
	issueInfo = first_xpath_node(NULL, dmRefIdent, BAD_CAST "issueInfo|issno");
	language = first_xpath_node(NULL, dmRefIdent, BAD_CAST "language");

	model_ident_code     = (char *) first_xpath_value(NULL, dmCode, BAD_CAST "modelic|@modelIdentCode");
	system_diff_code     = (char *) first_xpath_value(NULL, dmCode, BAD_CAST "sdc|@systemDiffCode");
	system_code          = (char *) first_xpath_value(NULL, dmCode, BAD_CAST "chapnum|@systemCode");
	sub_system_code      = (char *) first_xpath_value(NULL, dmCode, BAD_CAST "section|@subSystemCode");
	sub_sub_system_code  = (char *) first_xpath_value(NULL, dmCode, BAD_CAST "subsect|@subSubSystemCode");
	assy_code            = (char *) first_xpath_value(NULL, dmCode, BAD_CAST "subject|@assyCode");
	disassy_code         = (char *) first_xpath_value(NULL, dmCode, BAD_CAST "discode|@disassyCode");
	disassy_code_variant = (char *) first_xpath_value(NULL, dmCode, BAD_CAST "discodev|@disassyCodeVariant");
	info_code            = (char *) first_xpath_value(NULL, dmCode, BAD_CAST "incode|@infoCode");
	info_code_variant    = (char *) first_xpath_value(NULL, dmCode, BAD_CAST "incodev|@infoCodeVariant");
	item_location_code   = (char *) first_xpath_value(NULL, dmCode, BAD_CAST "itemloc|@itemLocationCode");
	learn_code           = (char *) first_xpath_value(NULL, dmCode, BAD_CAST "@learnCode");
	learn_event_code     = (char *) first_xpath_value(NULL, dmCode, BAD_CAST "@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 (!ignore_iss && issueInfo) {
			char *issue_number;
			char *in_work;
			char iss[8];

			issue_number = (char *) first_xpath_value(NULL, issueInfo, BAD_CAST "@issno|@issueNumber");
			in_work      = (char *) first_xpath_value(NULL, issueInfo, BAD_CAST "@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, BAD_CAST "@language|@languageIsoCode");
		country_iso_code  = (char *) first_xpath_value(NULL, language, BAD_CAST "@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 > QUIET) {
		fprintf(stderr, S_MISSING_REF_DM, code);
	}
	return false;
}

/* Find a PM filename in the current directory based on the pmRefIdent
 * element. */
static bool find_pm_fname(char *dst, xmlNodePtr pmRefIdent, bool ignore_iss)
{
	char *model_ident_code;
	char *pm_issuer;
	char *pm_number;
	char *pm_volume;
	char code[64];
	xmlNodePtr pmCode, issueInfo, language;

	pmCode = first_xpath_node(NULL, pmRefIdent, BAD_CAST "pmCode|pmc");
	issueInfo = first_xpath_node(NULL, pmRefIdent, BAD_CAST "issueInfo|issno");
	language = first_xpath_node(NULL, pmRefIdent, BAD_CAST "language");

	model_ident_code = (char *) first_xpath_value(NULL, pmCode, BAD_CAST "modelic|@modelIdentCode");
	pm_issuer        = (char *) first_xpath_value(NULL, pmCode, BAD_CAST "pmissuer|@pmIssuer");
	pm_number        = (char *) first_xpath_value(NULL, pmCode, BAD_CAST "pmnumber|@pmNumber");
	pm_volume        = (char *) first_xpath_value(NULL, pmCode, BAD_CAST "pmvolume|@pmVolume");

	snprintf(code, 64, "PMC-%s-%s-%s-%s",
		model_ident_code,
		pm_issuer,
		pm_number,
		pm_volume);

	xmlFree(model_ident_code);
	xmlFree(pm_issuer);
	xmlFree(pm_number);
	xmlFree(pm_volume);

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

			issue_number = (char *) first_xpath_value(NULL, issueInfo, BAD_CAST "@issno|@issueNumber");
			in_work      = (char *) first_xpath_value(NULL, issueInfo, BAD_CAST "@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, BAD_CAST "@language|@languageIsoCode");
		country_iso_code  = (char *) first_xpath_value(NULL, language, BAD_CAST "@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_pm, recursive_search)) {
		return true;
	}

	if (verbosity > QUIET) {
		fprintf(stderr, S_MISSING_REF_DM, code);
	}
	return false;
}

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

/* Find the filename of a referenced CCT data module. */
static bool find_cct_fname(char *dst, xmlDocPtr act)
{
	xmlNodePtr cctref;
	cctref = first_xpath_node(act, NULL, BAD_CAST "//condCrossRefTableRef/dmRef/dmRefIdent|//cctref/refdm");
	return cctref && find_dmod_fname(dst, cctref, false);
}

/* Find the filename of a referenced PCT data module via the ACT. */
static bool find_pct_fname(char *dst, xmlDocPtr act)
{
	xmlNodePtr pctref;
	pctref = first_xpath_node(act, NULL, BAD_CAST "//productCrossRefTableRef/dmRef/dmRefIdent|//pctref/refdm");
	return pctref && find_dmod_fname(dst, pctref, false);
}

/* Unset all applicability assigned on a per-DM basis. */
static void clear_perdm_applic(xmlNodePtr defs, int *napplics)
{
	xmlNodePtr cur;
	cur = defs->children;
	while (cur) {
		xmlNodePtr next;
		next = cur->next;
		if (xmlHasProp(cur, BAD_CAST "perDm")) {
			xmlUnlinkNode(cur);
			xmlFreeNode(cur);
			--(*napplics);
		}
		cur = next;
	}
}

/* Callback for clean_entities.
 *
 * The name parameter of the xmlHashScanner type was changed to be const in
 * newer versions of libxml2.
 *
 * Older versions, and the publically documented API, have it as non-const.
 */
#if LIBXML_VERSION < 20908
static void clean_entities_callback(void *payload, void *data, xmlChar *name)
#else
static void clean_entities_callback(void *payload, void *data, const xmlChar *name)
#endif
{
	xmlEntityPtr e = (xmlEntityPtr) payload;
	xmlChar *xpath;

	xpath = xmlStrdup(BAD_CAST "//@*[.='");
	xpath = xmlStrcat(xpath, e->name);
	xpath = xmlStrcat(xpath, BAD_CAST "']");

	if (e->etype == XML_EXTERNAL_GENERAL_UNPARSED_ENTITY && !first_xpath_node(e->doc, NULL, xpath)) {
		xmlUnlinkNode((xmlNodePtr) e);
		xmlFreeEntity(e);
	}
}

/* Remove unused external entities after filtering. */
static void clean_entities(xmlDocPtr doc)
{
	if (doc->intSubset) {
		xmlHashScan(doc->intSubset->entities, clean_entities_callback, NULL);
	}
}

/* Find the source object for an instance.
 * -1  Object does not identify a source.
 *  0  Object identifies a source and it was found.
 *  1  Object identifies a source but it couldn't be found.
 */
static int find_source(char *src, xmlDocPtr *doc)
{
	xmlNodePtr sdi;
	bool found = false;

	if (!(*doc = read_xml_doc(src))) {
		return -1;
	}

	if (!(sdi = first_xpath_node(*doc, NULL, BAD_CAST "//sourceDmIdent|//sourcePmIdent|//srcdmaddres"))) {
		return -1;
	}

	if (xmlStrcmp(sdi->name, BAD_CAST "sourceDmIdent") == 0 || xmlStrcmp(sdi->name, BAD_CAST "srcdmaddres") == 0) {
		found = find_dmod_fname(src, sdi, true);
	} else if (xmlStrcmp(sdi->name, BAD_CAST "sourcePmIdent") == 0) {
		found = find_pm_fname(src, sdi, true);
	}

	return !found;
}

static void load_applic_from_inst(xmlNodePtr defs, xmlDocPtr doc)
{
	xmlXPathContextPtr ctx;
	xmlXPathObjectPtr obj;

	ctx = xmlXPathNewContext(doc);
	obj = xmlXPathEvalExpression(BAD_CAST "//identAndStatusSection//applic[1]//assert", ctx);

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

		for (i = 0; i < obj->nodesetval->nodeNr; ++i) {
			xmlChar *ident, *type, *value;

			ident = first_xpath_value(doc, obj->nodesetval->nodeTab[i], BAD_CAST "@applicPropertyIdent");
			type  = first_xpath_value(doc, obj->nodesetval->nodeTab[i], BAD_CAST "@applicPropertyType");
			value = first_xpath_value(doc, obj->nodesetval->nodeTab[i], BAD_CAST "@applicPropertyValues");

			/* userdefined = false so that user-defined assertions override those in the instance. */
			define_applic(defs, NULL, ident, type, value, true, false);

			xmlFree(ident);
			xmlFree(type);
			xmlFree(value);
		}
	}

	xmlXPathFreeObject(obj);
	xmlXPathFreeContext(ctx);
}

static void load_skill_from_inst(xmlDocPtr doc, char **skill_codes)
{
	char *skill;

	if ((skill = (char *) first_xpath_value(doc, NULL, BAD_CAST "//skillLevel/@skillLevelCode|//skill/@skill"))) {
		free(*skill_codes);
		*skill_codes = skill;
	}
}

static void load_sec_from_inst(xmlDocPtr doc, char **sec_classes)
{
	char *sec;

	if ((sec = (char *) first_xpath_value(doc, NULL, BAD_CAST "//security/@securityClassification|//security/@class"))) {
		free(*sec_classes);
		*sec_classes = sec;
	}
}

static void add_cirs_from_inst(xmlDocPtr doc, xmlNodePtr cirs)
{
	xmlXPathContextPtr ctx;
	xmlXPathObjectPtr obj;

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

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

		for (i = 0; i < obj->nodesetval->nodeNr; ++i) {
			char path[PATH_MAX];

			if (find_dmod_fname(path, obj->nodesetval->nodeTab[i], true)) {
				xmlNewChild(cirs, NULL, BAD_CAST "cir", BAD_CAST path);
			}

			xmlUnlinkNode(obj->nodesetval->nodeTab[i]);
			xmlFreeNode(obj->nodesetval->nodeTab[i]);
			obj->nodesetval->nodeTab[i] = NULL;
		}
	}

	xmlXPathFreeObject(obj);
	xmlXPathFreeContext(ctx);
}

/* Load all other metadata settings from the instance. */
static void load_metadata_from_inst(xmlDocPtr doc,
	char *extension,
	char *code,
	char *lang,
	char *issinfo,
	bool incr_iss,
	char **techname,
	char **infoname,
	xmlChar **infonamevar,
	bool *no_infoname,
	char **isstype,
	char *security,
	char **orig,
	bool *setorig,
	char **skill,
	char **remarks,
	bool *new_applic,
	char *new_display_text)
{
	xmlNodePtr node;

	if ((node = first_xpath_node(doc, NULL, EXTENSION_XPATH))) {
		xmlChar *p, *c;
		p = first_xpath_value(doc, node, BAD_CAST "@extensionProducer|dmeproducer");
		c = first_xpath_value(doc, node, BAD_CAST "@extensionCode|dmecode");
		sprintf(extension, "%s-%s", (char *) p, (char *) c);
	}

	if ((node = first_xpath_node(doc, NULL, CODE_XPATH))) {
		if (xmlStrcmp(node->name, BAD_CAST "dmCode") == 0 || xmlStrcmp(node->name, BAD_CAST "avee") == 0) {
			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;

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

			sprintf(code, "%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);
		} else if (xmlStrcmp(node->name, BAD_CAST "pmCode") == 0 || xmlStrcmp(node->name, BAD_CAST "pmc") == 0) {
			char *model_ident_code;
			char *pm_issuer;
			char *pm_number;
			char *pm_volume;

			model_ident_code = (char *) first_xpath_value(NULL, node, BAD_CAST "modelic|@modelIdentCode");
			pm_issuer        = (char *) first_xpath_value(NULL, node, BAD_CAST "pmissuer|@pmIssuer");
			pm_number        = (char *) first_xpath_value(NULL, node, BAD_CAST "pmnumber|@pmNumber");
			pm_volume        = (char *) first_xpath_value(NULL, node, BAD_CAST "pmvolume|@pmVolume");

			sprintf(code, "%s-%s-%s-%s",
				model_ident_code,
				pm_issuer,
				pm_number,
				pm_volume);

			xmlFree(model_ident_code);
			xmlFree(pm_issuer);
			xmlFree(pm_number);
			xmlFree(pm_volume);
		}
	}

	if ((node = first_xpath_node(doc, NULL, LANGUAGE_XPATH))) {
		xmlChar *l, *c;
		l = first_xpath_value(doc, node, BAD_CAST "@languageIsoCode|@language");
		c = first_xpath_value(doc, node, BAD_CAST "@countryIsoCode|@country");
		sprintf(lang, "%s-%s", (char *) l, (char *) c);
		xmlFree(l);
		xmlFree(c);
	}

	if ((node = first_xpath_node(doc, NULL, ISSUE_INFO_XPATH))) {
		char *i, *w;
		i = (char *) first_xpath_value(doc, node, BAD_CAST "@issueNumber|@issno");
		w = (char *) first_xpath_value(doc, node, BAD_CAST "@inWork|@inwork");

		if (!w) {
			w = strdup("00");
		}

		if (incr_iss) {
			int inwork_i;
			inwork_i = atoi(w);
			free(w);
			w = malloc(32);
			snprintf(w, 32, "%.2d", inwork_i + 1);
		}

		sprintf(issinfo, "%s-%s", i, w);

		xmlFree(i);
		xmlFree(w);
	}

	if ((node = first_xpath_node(doc, NULL, BAD_CAST "//dmAddressItems/dmTitle/techName|//dmaddres/dmtitle/techname|//pmAddressItems/pmTitle|//pmaddres/pmtitle"))) {
		free(*techname);
		*techname = (char *) xmlNodeGetContent(node);
	}

	if ((node = first_xpath_node(doc, NULL, BAD_CAST "//dmAddressItems/dmTitle/infoName|//dmaddres/dmtitle/infoname"))) {
		free(*infoname);
		*infoname = (char *) xmlNodeGetContent(node);
		*no_infoname = false;
	} else {
		*no_infoname = true;
	}

	if ((node = first_xpath_node(doc, NULL, BAD_CAST "//dmAddressItems/dmTitle/infoNameVariant"))) {
		*infonamevar = xmlNodeGetContent(node);
	}

	if ((node = first_xpath_node(doc, NULL, BAD_CAST "//dmStatus/@issueType|//dmaddres/issno/@type|//pmStatus/@issueType|//pmaddres/issno/@type"))) {
		xmlChar *t;
		t = xmlNodeGetContent(node);
		free(*isstype);
		*isstype = (char *) t;
	}

	if ((node = first_xpath_node(doc, NULL, BAD_CAST "//dmStatus/security|//status/security|//pmStatus/security|//pmstatus/security"))) {
		xmlChar *s;
		s = first_xpath_value(doc, node, BAD_CAST "@securityClassification|@class");
		strcpy(security, (char *) s);
		xmlFree(s);
	}

	if ((node = first_xpath_node(doc, NULL, BAD_CAST "//dmStatus/originator|//pmStatus/originator"))) {
		char *c, *n;
		*setorig = true;
		c = (char *) first_xpath_value(doc, node, BAD_CAST "@enterpriseCode");
		n = (char *) first_xpath_value(doc, node, BAD_CAST "enterpriseName");
		free(*orig);
		if (c && n) {
			*orig = malloc(strlen(c) + strlen(n) + 2);
			sprintf(*orig, "%s/%s", c, n);
		} else if (c) {
			*orig = malloc(strlen(c) + 1);
			sprintf(*orig, "%s", c);
		} else if (n) {
			*orig = malloc(strlen(n) + 3);
			sprintf(*orig, "-/%s", n);
		} else {
			*setorig = false;
		}
		xmlFree(c);
		xmlFree(n);
	} else {
		*setorig = false;
	}

	if ((node = first_xpath_node(doc, NULL, BAD_CAST "//dmStatus/skillLevel"))) {
		xmlChar *c;
		c = first_xpath_value(doc, node, BAD_CAST "@skillLevelCode");
		free(*skill);
		*skill = (char *) c;
	}

	if ((node = first_xpath_node(doc, NULL, BAD_CAST "//dmStatus/remarks|//pmStatus/remarks"))) {
		xmlChar *p;
		p = first_xpath_value(doc, node, BAD_CAST "simplePara");
		free(*remarks);
		*remarks = (char *) p;
	}

	if ((node = first_xpath_node(doc, NULL, BAD_CAST "//dmStatus/applic/displayText|//pmStatus/applic/displayText"))) {
		xmlChar *d;
		d = first_xpath_value(doc, node, BAD_CAST "simplePara");
		strcpy(new_display_text, (char *) d);
		xmlFree(d);
	}
}

/* Create an applicability annotation in a container. */
static xmlNodePtr add_container_applic(xmlNodePtr rag, xmlDocPtr doc, const xmlChar *id)
{
	xmlNodePtr app;

	if (!(app = first_xpath_node(doc, NULL, BAD_CAST "//applic"))) {
		return NULL;
	}

	xmlSetProp(app, BAD_CAST "id", id);

	return xmlAddChild(rag, xmlCopyNode(app, 1));
}

/* Copy the applicability of the referenced DMs of a container into the
 * container itself as inline annotations.
 */
static xmlNodePtr add_container_applics(xmlDocPtr doc, xmlNodePtr content, xmlNodePtr container)
{
	xmlNodePtr rag, refs;
	xmlXPathContextPtr ctx;
	xmlXPathObjectPtr obj;

	rag = xmlNewNode(NULL, BAD_CAST "referencedApplicGroup");

	/* Insert the referencedApplicGroup element appropriately. */
	if ((refs = first_xpath_node(doc, content, BAD_CAST "refs"))) {
		xmlAddNextSibling(refs, rag);
	} else {
		add_first_child(content, rag);
	}

	ctx = xmlXPathNewContext(doc);
	xmlXPathSetContextNode(container, ctx);
	obj = xmlXPathEvalExpression(BAD_CAST "refs/dmRef/dmRefIdent", ctx);

	if (!xmlXPathNodeSetIsEmpty(obj->nodesetval)) {
		int i, n = 1;

		for (i = 0; i < obj->nodesetval->nodeNr; ++i) {
			char path[PATH_MAX];

			if (find_dmod_fname(path, obj->nodesetval->nodeTab[i], false)) {
				xmlDocPtr doc;
				xmlChar id[32];

				if (!(doc = read_xml_doc(path))) {
					continue;
				}

				/* Give each new annotation a sequential ID. */
				xmlStrPrintf(id, 32, "app-%.4d", n);

				if (add_container_applic(rag, doc, id)) {
					xmlSetProp(obj->nodesetval->nodeTab[i]->parent, BAD_CAST "applicRefId", id);
					++n;
				}

				xmlFreeDoc(doc);
			}
		}
	}

	xmlXPathFreeObject(obj);
	xmlXPathFreeContext(ctx);

	return rag;
}

/* Replace a reference to a container with the appropriate reference within
 * the container for the given applicability. */
static xmlNodePtr resolve_container_ref(xmlNodePtr refident, xmlDocPtr doc, const char *path, xmlNodePtr defs)
{
	xmlNodePtr root, content, container, rag, ref;
	xmlXPathContextPtr ctx;
	xmlXPathObjectPtr obj;

	root = xmlDocGetRootElement(doc);

	if (!(content = first_xpath_node(doc, root, BAD_CAST "//content"))) {
		return NULL;
	}

	/* Referenced DM is not a container. */
	if (!(container = first_xpath_node(doc, content, BAD_CAST "container"))) {
		return NULL;
	}

	/* If container does not contain inline annotations, copy them from
	 * the referenced DMs. */
	if (!(rag = first_xpath_node(doc, content, BAD_CAST "//referencedApplicGroup"))) {
		if (!(rag = add_container_applics(doc, content, container))) {
			return NULL;
		}
	}

	/* Filter the container. */
	strip_applic(defs, rag, root);

	ctx = xmlXPathNewContext(doc);
	xmlXPathSetContextNode(container, ctx);

	obj = xmlXPathEvalExpression(BAD_CAST "refs/dmRef", ctx);

	/* If the container does not have exactly one ref after filtering, it
	 * should not be resolved. */
	if (xmlXPathNodeSetIsEmpty(obj->nodesetval) || obj->nodesetval->nodeNr > 1) {
		ref = NULL;
	} else {
		ref = obj->nodesetval->nodeTab[0];
	}

	xmlXPathFreeObject(obj);
	xmlXPathFreeContext(ctx);

	/* Replace the ref to the container in the source. */
	if (ref) {
		xmlNodePtr old, new;

		old = refident->parent;
		new = xmlCopyNode(ref, 1);

		xmlUnsetProp(new, BAD_CAST "applicRefId");

		xmlAddNextSibling(old, new);

		xmlUnlinkNode(old);
		xmlFreeNode(old);
	} else if (verbosity > QUIET) {
		fprintf(stderr, S_RESOLVE_CONTAINER, path);
	}

	return ref;
}

/* Resolve references to containers, replacing them with the appropriate
 * reference from within the container for the given applicability.
 */
static void resolve_containers(xmlDocPtr doc, xmlNodePtr defs)
{
	xmlXPathContextPtr ctx;
	xmlXPathObjectPtr obj;

	ctx = xmlXPathNewContext(doc);
	obj = xmlXPathEvalExpression(BAD_CAST "//dmRef/dmRefIdent", ctx);

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

		for (i = 0; i < obj->nodesetval->nodeNr; ++i) {
			char path[PATH_MAX];

			if (find_dmod_fname(path, obj->nodesetval->nodeTab[i], false)) {
				xmlDocPtr doc;

				if (!(doc = read_xml_doc(path))) {
					continue;
				}

				if (resolve_container_ref(obj->nodesetval->nodeTab[i], doc, path, defs)) {
					obj->nodesetval->nodeTab[i] = NULL;
				}

				xmlFreeDoc(doc);
			}
		}
	}

	xmlXPathFreeObject(obj);
	xmlXPathFreeContext(ctx);
}

static void add_ct_prop_vals(xmlDocPtr act, xmlDocPtr cct, const xmlChar *id, const xmlChar *type, xmlNodePtr p, xmlNodePtr defs)
{
	xmlXPathContextPtr ctx;
	xmlXPathObjectPtr obj;
	xmlNodePtr prop_desc = NULL;
	xmlNodePtr prop_vals = NULL;

	if (act && xmlStrcmp(type, BAD_CAST "prodattr") == 0) {
		ctx = xmlXPathNewContext(act);
		xmlXPathRegisterVariable(ctx, BAD_CAST "id", xmlXPathNewString(id));
		obj = xmlXPathEvalExpression(BAD_CAST "//productAttribute[@id=$id]|//prodattr[@id=$id]", ctx);

		if (!xmlXPathNodeSetIsEmpty(obj->nodesetval)) {
			prop_desc = obj->nodesetval->nodeTab[0];
			prop_vals = prop_desc;
		}
	} else if (cct && xmlStrcmp(type, BAD_CAST "condition") == 0) {
		xmlChar *condtype;

		ctx = xmlXPathNewContext(cct);
		xmlXPathRegisterVariable(ctx, BAD_CAST "id", xmlXPathNewString(id));
		obj = xmlXPathEvalExpression(BAD_CAST "//cond[@id=$id]/@condTypeRefId|//condition[@id=$id]/@condtyperef", ctx);

		if (xmlXPathNodeSetIsEmpty(obj->nodesetval)) {
			fprintf(stderr, S_NO_CT_PROP, (char *) type, (char *) id);
			return;
		}

		prop_desc = obj->nodesetval->nodeTab[0]->parent;

		condtype = xmlNodeGetContent(obj->nodesetval->nodeTab[0]);
		xmlXPathFreeObject(obj);
		xmlXPathRegisterVariable(ctx, BAD_CAST "id", xmlXPathNewString(condtype));
		xmlFree(condtype);

		obj = xmlXPathEvalExpression(BAD_CAST "//condType[@id=$id]|//conditiontype[@id=$id]", ctx);

		if (!xmlXPathNodeSetIsEmpty(obj->nodesetval)) {
			prop_vals = obj->nodesetval->nodeTab[0];
		}
	} else {
		fprintf(stderr, S_NO_CT, (char *) id, (char *) type, xmlStrcmp(type, BAD_CAST "prodattr") == 0 ? "ACT" : "CCT");
		return;
	}

	xmlXPathFreeObject(obj);

	if (prop_desc && prop_vals) {
		xmlChar *s;

		/* Copy information about the property from ACT/CCT. */
		if ((s = first_xpath_value(NULL, prop_vals, BAD_CAST "@valuePattern|@pattern"))) {
			xmlSetProp(p, BAD_CAST "pattern", s);
			xmlFree(s);
		}

		if ((s = first_xpath_value(NULL, prop_desc, BAD_CAST "name"))) {
			xmlNewTextChild(p, p->ns, BAD_CAST "name", s);
			xmlFree(s);
		}

		if ((s = first_xpath_value(NULL, prop_desc, BAD_CAST "descr|description"))) {
			xmlNewTextChild(p, p->ns, BAD_CAST "descr", s);
			xmlFree(s);
		}

		if ((s = first_xpath_value(NULL, prop_desc, BAD_CAST "displayName|displayname"))) {
			xmlNewTextChild(p, p->ns, BAD_CAST "displayName", s);
			xmlFree(s);
		}

		xmlXPathSetContextNode(prop_vals, ctx);
		obj = xmlXPathEvalExpression(BAD_CAST "enumeration|enum", ctx);

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

			for (i = 0; i < obj->nodesetval->nodeNr; ++i) {
				xmlChar *v, *l, *c = NULL;

				v = first_xpath_value(NULL, obj->nodesetval->nodeTab[i],
					BAD_CAST "@applicPropertyValues|@actvalues");
				l = first_xpath_value(NULL, obj->nodesetval->nodeTab[i],
					BAD_CAST "@enumerationLabel");

				while ((c = BAD_CAST strtok(c ? NULL : (char *) v, "|"))) {
					xmlNodePtr cur;
					bool add = true;

					for (cur = p->children; cur && add; cur = cur->next) {
						xmlChar *cv;
						cv = xmlNodeGetContent(cur);
						add = xmlStrcmp(c, cv) != 0;
						xmlFree(cv);
					}

					if (defs) {
						add &= is_applic(defs, (char *) id, (char *) type, (char *) c, true);
					}

					if (add) {
						xmlNodePtr n;

						n = xmlNewTextChild(p, NULL, BAD_CAST "value", c);

						if (l) {
							xmlSetProp(n, BAD_CAST "label", l);
						}
					}
				}

				xmlFree(v);
				xmlFree(l);
			}
		}

		xmlXPathFreeObject(obj);
	} else {
		fprintf(stderr, S_NO_CT_PROP, (char *) type, (char *) id);
	}

	xmlXPathFreeContext(ctx);
}

/* Add a property used in an object to the properties report. */
static void add_prop(xmlNodePtr object, xmlNodePtr assert, enum listprops listprops, xmlDocPtr act, xmlDocPtr cct, xmlNodePtr defs)
{
	xmlChar *i, *t;
	xmlNodePtr p = NULL, cur;

	i = first_xpath_value(NULL, assert, BAD_CAST "@applicPropertyIdent|@actidref");
	t = first_xpath_value(NULL, assert, BAD_CAST "@applicPropertyType|@actreftype");

	for (cur = object->children; cur && !p; cur = cur->next) {
		xmlChar *ci, *ct;

		ci = first_xpath_value(NULL, cur, BAD_CAST "@ident");
		ct = first_xpath_value(NULL, cur, BAD_CAST "@type");

		if (xmlStrcmp(i, ci) == 0 && xmlStrcmp(t, ct) == 0) {
			p = cur;
		}

		xmlFree(ci);
		xmlFree(ct);
	}

	if (!p) {
		p = xmlNewChild(object, NULL, BAD_CAST "property", NULL);
		xmlSetProp(p, BAD_CAST "ident", i);
		xmlSetProp(p, BAD_CAST "type", t);

		if (listprops != STANDALONE) {
			add_ct_prop_vals(act, cct, i, t, p, defs);
		}
	}

	if (listprops == STANDALONE) {
		xmlChar *v, *c = NULL;

		v = first_xpath_value(NULL, assert, BAD_CAST "@applicPropertyValues|@actvalues");

		while ((c = BAD_CAST strtok(c ? NULL : (char *) v, "|"))) {
			xmlNodePtr cur;
			bool add = true;

			for (cur = p->children; cur && add; cur = cur->next) {
				xmlChar *cv;

				cv = xmlNodeGetContent(cur);

				add = xmlStrcmp(c, cv) != 0;

				xmlFree(cv);
			}

			if (add) {
				xmlNewTextChild(p, NULL, BAD_CAST "value", c);
			}
		}

		xmlFree(v);
	}

	xmlFree(i);
	xmlFree(t);
}

/* Determine whether a product instance is applicable to an object. */
static bool product_is_applic(xmlNodePtr product, xmlNodePtr defs)
{
	xmlNodePtr evaluate, cur;
	bool is;

	evaluate = xmlNewNode(NULL, BAD_CAST "evaluate");
	xmlSetProp(evaluate, BAD_CAST "andOr", BAD_CAST "and");

	for (cur = product->children; cur; cur = cur->next) {
		xmlChar *i, *t, *v;
		xmlNodePtr a;

		if (cur->type != XML_ELEMENT_NODE) {
			continue;
		}

		i = first_xpath_value(NULL, cur, BAD_CAST "@applicPropertyIdent|@actidref");
		t = first_xpath_value(NULL, cur, BAD_CAST "@applicPropertyType|@actreftype");
		v = first_xpath_value(NULL, cur, BAD_CAST "@applicPropertyValue|@actvalue");

		a = xmlNewNode(evaluate->ns, BAD_CAST "assert");
		xmlSetProp(a, BAD_CAST "applicPropertyIdent", i);
		xmlSetProp(a, BAD_CAST "applicPropertyType", t);
		xmlSetProp(a, BAD_CAST "applicPropertyValues", v);

		xmlAddChild(evaluate, a);

		xmlFree(i);
		xmlFree(t);
		xmlFree(v);
	}

	is = eval_evaluate(defs, evaluate, true);

	xmlFreeNode(evaluate);

	return is;
}

/* Add a PCT product instance to the properties report. */
static void add_product(xmlNodePtr object, xmlNodePtr product, xmlNodePtr defs)
{
	xmlNodePtr p, cur;
	xmlChar *id;

	if (defs && !product_is_applic(product, defs)) {
		return;
	}

	p = xmlNewNode(NULL, BAD_CAST "product");

	if ((id = xmlGetProp(product, BAD_CAST "id"))) {
		xmlSetProp(p, BAD_CAST "ident", id);
		xmlFree(id);
	}

	for (cur = product->children; cur; cur = cur->next) {
		xmlChar *i, *t, *v;
		xmlNodePtr a;

		if (cur->type != XML_ELEMENT_NODE) {
			continue;
		}

		i = first_xpath_value(NULL, cur, BAD_CAST "@applicPropertyIdent|@actidref");
		t = first_xpath_value(NULL, cur, BAD_CAST "@applicPropertyType|@actreftype");
		v = first_xpath_value(NULL, cur, BAD_CAST "@applicPropertyValue|@actvalue");

		a = xmlNewNode(p->ns, BAD_CAST "assign");
		xmlSetProp(a, BAD_CAST "applicPropertyIdent", i);
		xmlSetProp(a, BAD_CAST "applicPropertyType", t);
		xmlSetProp(a, BAD_CAST "applicPropertyValue", v);

		xmlAddChild(p, a);

		xmlFree(i);
		xmlFree(t);
		xmlFree(v);
	}

	xmlAddChild(object, p);
}

/* Add the properties used in an object to the properties report. */
static void add_props(xmlNodePtr report, const char *path, enum listprops listprops, const char *useract, const char *usercct, const char *userpct)
{
	xmlDocPtr doc, act = NULL, cct = NULL, pct = NULL;
	xmlXPathContextPtr ctx;
	xmlXPathObjectPtr obj;
	xmlNodePtr object, defs;

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

	if (listprops == APPLIC) {
		defs = xmlNewNode(NULL, BAD_CAST "applic");
		load_applic_from_inst(defs, doc);
	} else {
		defs = NULL;
	}

	object = xmlNewChild(report, NULL, BAD_CAST "object", NULL);
	xmlSetProp(object, BAD_CAST "path", BAD_CAST path);

	if (listprops != STANDALONE) {
		char fname[PATH_MAX];

		if (useract) {
			if ((act = read_xml_doc(useract))) {
				xmlSetProp(object, BAD_CAST "act", BAD_CAST useract);
			} else if (verbosity > QUIET) {
				fprintf(stderr, S_MISSING_ACT, useract);
			}
		} else if (find_act_fname(fname, doc)) {
			if ((act = read_xml_doc(fname))) {
				xmlSetProp(object, BAD_CAST "act", BAD_CAST fname);
			} else if (verbosity > QUIET) {
				fprintf(stderr, S_MISSING_ACT, fname);
			}
		}

		if (usercct) {
			if ((cct = read_xml_doc(usercct))) {
				xmlSetProp(object, BAD_CAST "cct", BAD_CAST usercct);
			} else if (verbosity > QUIET) {
				fprintf(stderr, S_MISSING_CCT, usercct);
			}
		} else if (act && find_cct_fname(fname, act)) {
			if ((cct = read_xml_doc(fname))) {
				xmlSetProp(object, BAD_CAST "cct", BAD_CAST fname);
			} else if (verbosity > QUIET) {
				fprintf(stderr, S_MISSING_CCT, fname);
			}
		}

		if (userpct) {
			if ((pct = read_xml_doc(userpct))) {
				xmlSetProp(object, BAD_CAST "pct", BAD_CAST userpct);
			} else if (verbosity > QUIET) {
				fprintf(stderr, S_MISSING_PCT, userpct);
			}
		} else if (act && find_pct_fname(fname, act)) {
			if ((pct = read_xml_doc(fname))) {
				xmlSetProp(object, BAD_CAST "pct", BAD_CAST fname);
			} else if (verbosity > QUIET) {
				fprintf(stderr, S_MISSING_PCT, fname);
			}
		}
	}

	/* Add properties from DM, ACT and/or CCT. */
	ctx = xmlXPathNewContext(doc);

	/* Use assertions from the whole object applic in standalone mode. */
	if (listprops == STANDALONE) {
		obj = xmlXPathEvalExpression(BAD_CAST "//assert", ctx);
	/* Otherwise, only use assertions from inline annotations. */
	} else {
		obj = xmlXPathEvalExpression(BAD_CAST "(//content|//inlineapplics)//assert", ctx);
	}

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

		for (i = 0; i < obj->nodesetval->nodeNr; ++i) {
			add_prop(object, obj->nodesetval->nodeTab[i], listprops, act, cct, defs);
		}
	}

	xmlXPathFreeObject(obj);
	xmlXPathFreeContext(ctx);

	/* Add products from PCT. */
	if (pct) {
		ctx = xmlXPathNewContext(pct);
		obj = xmlXPathEval(BAD_CAST "//product", ctx);

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

			for (i = 0; i < obj->nodesetval->nodeNr; ++i) {
				add_product(object, obj->nodesetval->nodeTab[i], defs);
			}
		}

		xmlXPathFreeObject(obj);
		xmlXPathFreeContext(ctx);
	}

	xmlFreeNode(defs);

	xmlFreeDoc(act);
	xmlFreeDoc(cct);
	xmlFreeDoc(pct);
	xmlFreeDoc(doc);
}

/* Add the properties used in objects in a list to the properties report. */
static void add_props_list(xmlNodePtr report, const char *fname, enum listprops listprops, const char *useract, const char *usercct, const char *userpct)
{
	FILE *f;
	char path[PATH_MAX];

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

	while (fgets(path, PATH_MAX, f)) {
		strtok(path, "\t\r\n");
		add_props(report, path, listprops, useract, usercct, userpct);
	}

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

/* Remove an applicability definition from a set. */
static void undefine_applic(xmlNodePtr def, const xmlChar *vals)
{
	xmlChar *cur_val;

	cur_val = xmlGetProp(def, BAD_CAST "applicPropertyValues");

	if (cur_val) {
		if (is_in_set((char *) cur_val, (char *) vals)) {
			xmlUnlinkNode(def);
			xmlFreeNode(def);
		}
	} else {
		xmlNodePtr cur;
		bool match = false;

		for (cur = def->children; cur && !match; cur = cur->next) {
			xmlChar *v;

			v = xmlNodeGetContent(cur);

			if (is_in_set((char *) v, (char *) vals)) {
				xmlUnlinkNode(cur);
				xmlFreeNode(cur);
				match = true;
			}

			xmlFree(v);
		}

		if (!def->children) {
			xmlUnlinkNode(def);
			xmlFreeNode(def);
		}
	}

	xmlFree(cur_val);
}

/* Find an applicability definition in a set. */
static xmlNodePtr get_applic_def(xmlNodePtr defs, const xmlChar *id, const xmlChar *type)
{
	xmlNodePtr cur, node = NULL;

	for (cur = defs->children; cur && !node; cur = cur->next) {
		xmlChar *cur_id, *cur_type;

		cur_id   = xmlGetProp(cur, BAD_CAST "applicPropertyIdent");
		cur_type = xmlGetProp(cur, BAD_CAST "applicPropertyType");

		if (xmlStrcmp(cur_id, id) == 0 && xmlStrcmp(cur_type, type) == 0) {
			node = cur;
		}

		xmlFree(cur_id);
		xmlFree(cur_type);
	}

	return node;
}

/* Determine whether an annotation is a superset of the user-defined applicability. */
static bool annotation_is_superset(xmlNodePtr defs, xmlNodePtr applic, bool simpl)
{
	xmlXPathContextPtr ctx;
	xmlXPathObjectPtr obj;
	xmlNodePtr defscopy, app;
	bool result;

	defscopy = xmlCopyNode(defs, 1);
	app = xmlCopyNode(applic, 1);

	if (simpl) {
		simpl_applic(defscopy, app, true);
		simpl_applic_evals(app);
	}

	ctx = xmlXPathNewContext(NULL);
	xmlXPathSetContextNode(app, ctx);

	obj = xmlXPathEvalExpression(BAD_CAST ".//assert", ctx);

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

		for (i = 0; i < obj->nodesetval->nodeNr; ++i) {
			xmlNodePtr a;
			xmlChar *id, *type;

			id   = first_xpath_value(NULL, obj->nodesetval->nodeTab[i], BAD_CAST "@applicPropertyIdent|@actidref");
			type = first_xpath_value(NULL, obj->nodesetval->nodeTab[i], BAD_CAST "@applicPropertyType|@actreftype");

			if ((a = get_applic_def(defscopy, id, type)) && eval_assert(defscopy, obj->nodesetval->nodeTab[i], true)) {
				xmlChar *vals, *op;

				vals = first_xpath_value(NULL, obj->nodesetval->nodeTab[i],
					BAD_CAST "@applicPropertyValues|@actvalues");
				op   = first_xpath_value(NULL, obj->nodesetval->nodeTab[i],
					BAD_CAST "parent::evaluate/@andOr|parent::evaluate/@operator");

				if (vals && op) {
					/* Do not remove assertions from AND evaluations,
					 * unless they are unambiguously true.
					 */
					if (xmlStrcmp(op, BAD_CAST "and") != 0 || eval_assert(defscopy, obj->nodesetval->nodeTab[i], false)) {
						xmlUnlinkNode(obj->nodesetval->nodeTab[i]);
						xmlFreeNode(obj->nodesetval->nodeTab[i]);
						obj->nodesetval->nodeTab[i] = NULL;

						undefine_applic(a, vals);
					}
				}

				xmlFree(vals);
				xmlFree(op);
			}

			xmlFree(id);
			xmlFree(type);
		}
	}

	xmlXPathFreeObject(obj);

	obj = xmlXPathEvalExpression(BAD_CAST ".//assert", ctx);

	if (xmlXPathNodeSetIsEmpty(obj->nodesetval)) {
		result = eval_applic_stmt(defscopy, applic, true);
	} else {
		result = eval_applic_stmt(defscopy, app, false);
	}

	xmlXPathFreeObject(obj);
	xmlXPathFreeContext(ctx);

	xmlFreeNode(app);
	xmlFreeNode(defscopy);

	return result;
}

/* Remove annotations which are supersets of the user-defined applicability. */
static xmlNodePtr rem_supersets(xmlNodePtr defs, xmlNodePtr referencedApplicGroup, xmlNodePtr root, bool simpl)
{
	xmlXPathContextPtr ctx;
	xmlXPathObjectPtr obj;

	ctx = xmlXPathNewContext(referencedApplicGroup->doc);
	xmlXPathSetContextNode(referencedApplicGroup, ctx);

	obj = xmlXPathEvalExpression(BAD_CAST "applic", ctx);

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

		for (i = 0; i < obj->nodesetval->nodeNr; ++i) {
			if (annotation_is_superset(defs, obj->nodesetval->nodeTab[i], simpl)) {
				xmlUnlinkNode(obj->nodesetval->nodeTab[i]);
				xmlFreeNode(obj->nodesetval->nodeTab[i]);
				obj->nodesetval->nodeTab[i] = NULL;
			}
		}
	}

	xmlXPathFreeObject(obj);
	xmlXPathFreeContext(ctx);

	clean_applic(referencedApplicGroup, root);

	if (xmlChildElementCount(referencedApplicGroup) == 0) {
		xmlUnlinkNode(referencedApplicGroup);
		xmlFreeNode(referencedApplicGroup);
		return NULL;
	}

	return referencedApplicGroup;
}

/* List of CSDB objects. */
struct objects {
	char (*paths)[PATH_MAX];
	unsigned count;
	unsigned max;
};

/* Initialize a list of CSDB objects. */
static void init_objects(struct objects *objects)
{
	objects->paths = malloc(PATH_MAX);
	objects->max = 1;
	objects->count = 0;
}

/* Free a list of CSDB objects. */
static void free_objects(struct objects *objects)
{
	free(objects->paths);
}

/* Add a CSDB object to a list. */
static void add_object(struct objects *objects, const char *path)
{
	if (objects->count == objects->max) {
		if (!(objects->paths = realloc(objects->paths, (objects->max *= 2) * PATH_MAX))) {
			if (verbosity > QUIET) {
				fprintf(stderr, E_MAX_OBJECTS);
			}
			exit(EXIT_MAX_OBJECTS);
		}
	}

	strcpy(objects->paths[(objects->count)++], path);
}

/* Find CIRs in directories and add them to the list. */
static void find_cirs(struct objects *cirs, const char *spath)
{
	DIR *dir;
	struct dirent *cur;
	char fpath[PATH_MAX], cpath[PATH_MAX];

	if (verbosity >= DEBUG) {
		fprintf(stderr, I_FIND_CIR, spath);
	}

	if (!(dir = opendir(spath))) {
		return;
	}

	/* Clean up the directory string. */
	if (strcmp(spath, ".") == 0) {
		strcpy(fpath, "");
	} else if (spath[strlen(spath) - 1] != '/') {
		strcpy(fpath, spath);
		strcat(fpath, "/");
	} else {
		strcpy(fpath, spath);
	}

	/* Search for CIRs. */
	while ((cur = readdir(dir))) {
		strcpy(cpath, fpath);
		strcat(cpath, cur->d_name);

		if (recursive_search && isdir(cpath, true)) {
			find_cirs(cirs, cpath);
		} else if (is_dm(cur->d_name) && is_cir(cpath, false)) {
			if (verbosity >= DEBUG) {
				fprintf(stderr, I_FIND_CIR_FOUND, cpath);
			}
			add_object(cirs, cpath);
		}
	}

	closedir(dir);
}

/* Use only the latest issue of a CIR. */
static void extract_latest_cirs(struct objects *cirs)
{
	struct objects latest;

	qsort(cirs->paths, cirs->count, PATH_MAX, compare_basename);

	latest.paths = malloc(cirs->count * PATH_MAX);
	latest.count = extract_latest_csdb_objects(latest.paths, cirs->paths, cirs->count);

	free(cirs->paths);
	cirs->paths = latest.paths;
	cirs->count = latest.count;
}

static void auto_add_cirs(xmlNodePtr cirs)
{
	struct objects files;
	int i;

	init_objects(&files);

	find_cirs(&files, search_dir);
	extract_latest_cirs(&files);

	for (i = 0; i < files.count; ++i) {
		xmlNewChild(cirs, NULL, BAD_CAST "cir", BAD_CAST files.paths[i]);

		if (verbosity >= DEBUG) {
			fprintf(stderr, I_FIND_CIR_ADD, files.paths[i]);
		}
	}

	free_objects(&files);
}

#ifdef LIBS1KD
#define s1kdApplicability xmlNodePtr
typedef enum {
	S1KD_FILTER_DEFAULT,
	S1KD_FILTER_REDUCE,
	S1KD_FILTER_SIMPLIFY,
	S1KD_FILTER_PRUNE
} s1kdFilterMode;

s1kdApplicability s1kdNewApplicability(void)
{
	return xmlNewNode(NULL, BAD_CAST "applic");
}

void s1kdFreeApplicability(s1kdApplicability app)
{
	xmlFreeNode(app);
}

void s1kdAssign(s1kdApplicability app, const xmlChar *ident, const xmlChar *type, const xmlChar *value)
{
	xmlNodePtr a;
	a = xmlNewChild(app, NULL, BAD_CAST "assert", NULL);
	xmlSetProp(a, BAD_CAST "applicPropertyIdent", ident);
	xmlSetProp(a, BAD_CAST "applicPropertyType", type);
	xmlSetProp(a, BAD_CAST "applicPropertyValues", value);
}

xmlDocPtr s1kdDocFilter(const xmlDocPtr doc, s1kdApplicability app, s1kdFilterMode mode)
{
	xmlDocPtr out;
	xmlNodePtr root, referencedApplicGroup;

	out = xmlCopyDoc(doc, 1);

	if (app == NULL || xmlChildElementCount(app) == 0) {
		return out;
	}

	root = xmlDocGetRootElement(out);
	referencedApplicGroup = first_xpath_node(out, NULL, BAD_CAST "//referencedApplicGroup");

	if (xmlChildElementCount(referencedApplicGroup) == 0) {
		return out;
	}

	strip_applic(app, referencedApplicGroup, root);

	if (mode >= S1KD_FILTER_REDUCE) {
		clean_applic_stmts(app, referencedApplicGroup, mode < S1KD_FILTER_PRUNE);

		if (xmlChildElementCount(referencedApplicGroup) == 0) {
			xmlUnlinkNode(referencedApplicGroup);
			xmlFreeNode(referencedApplicGroup);
			referencedApplicGroup = NULL;
		}

		clean_applic(referencedApplicGroup, root);

		if (mode >= S1KD_FILTER_SIMPLIFY && referencedApplicGroup) {
			referencedApplicGroup = simpl_applic_clean(app, referencedApplicGroup, mode == S1KD_FILTER_PRUNE);
		}

		if (mode != S1KD_FILTER_PRUNE && referencedApplicGroup) {
			referencedApplicGroup = rem_supersets(app, referencedApplicGroup, root, mode < S1KD_FILTER_SIMPLIFY);
		}
	}

	return out;
}

int s1kdFilter(const char *object_xml, int object_size, s1kdApplicability app, s1kdFilterMode mode, char **result_xml, int *result_size)
{
	xmlDocPtr doc, res;

	if ((doc = read_xml_mem(object_xml, object_size)) == NULL) {
		return 1;
	}
	if ((res = s1kdDocFilter(doc, app, mode)) == NULL) {
		return 1;
	}
	xmlFreeDoc(doc);

	if (result_xml && result_size) {
		xmlDocDumpMemory(res, (xmlChar **) result_xml, result_size);
	}

	xmlFreeDoc(res);

	return 0;
}
#else
/* Print a usage message */
static void show_help(void)
{
	puts("Usage: " PROG_NAME " [options] [<object>...]");
	puts("");
	puts("Options:");
	puts("  -A, --simplify                    Simplify and reduce applicability annotations.");
	puts("  -a, --reduce                      Remove applicability annotations which are unambiguously valid or invalid.");
	puts("  -C, --comment <comment>           Add an XML comment to the top of the instance.");
	puts("  -c, --code <DMC>                  The new code of the instance.");
	puts("  -D, --dump <CIR>                  Dump default XSLT for resolving CIR references.");
	puts("  -d, --dir <dir>                   Directory to start searching for referenced data modules in.");
	puts("  -E, --no-extension                Remove extension from instance.");
	puts("  -e, --extension <ext>             Specify an extension on the instance code (DME/PME).");
	puts("  -F, --flatten-alts                Flatten alts elements.");
	puts("  -f, --overwrite                   Force overwriting of files.");
	puts("  -G, --custom-orig <NCAGE/name>    Use custom NCAGE/name for originator.");
	puts("  -g, --set-orig                    Set originator of the instance to identify this tool.");
	puts("  -H, --list-properties <method>    List the applicability properties used in objects.");
	puts("  -h, -?, --help                    Show this help/usage message.");
	puts("  -I, --date <date>                 Set the issue date of the instance (- for current date).");
	puts("  -i, --infoname <infoName>         Give the data module instance a different infoName.");
	puts("  -J, --clean-display-text          Remove display text from simplified annotations (-A).");
	puts("  -j, --clean-ents                  Remove unused external entities (such as ICNs)");
	puts("  -K, --skill-levels <levels>       Filter on the specified skill levels.");
	puts("  -k, --skill <level>               Set the skill level of the instance.");
	puts("  -L, --list                        Treat input as a list of objects.");
	puts("  -l, --language <lang>             Specify the language of the instance.");
	puts("  -M, --fix-acronyms                Ensure acronyms remain valid after filtering.");
	puts("  -m, --remarks <remarks>           Set the remarks for the instance.");
	puts("  -N, --omit-issue                  Omit issue/inwork numbers from automatic filename.");
	puts("  -n, --issue <iss>                 Set the issue and inwork numbers of the instance.");
	puts("  -O, --outdir <dir>                Output instance in dir, automatically named.");
	puts("  -o, --out <file>                  Output instance to file instead of stdout.");
	puts("  -P, --pct <PCT>                   PCT file to read products from.");
	puts("  -p, --product <product>           ID/primary key of a product in the PCT to filter on.");
	puts("  -Q, --resolve-containers          Resolve references to containers.");
	puts("  -q, --quiet                       Quiet mode.");
	puts("  -R, --cir <CIR>                   Resolve externalized items using the given CIR.");
	puts("  -r, --recursive                   Search for referenced data modules recursively.");
	puts("  -S, --no-source-ident             Do not include <sourceDmIdent>/<sourcePmIdent>.");
	puts("  -s, --assign <applic>             An assign in the form of <ident>:<type>=<value>");
	puts("  -T, --tag                         Tag non-applicable elements instead of removing them.");
	puts("  -t, --techname <techName>         Give the instance a different techName/pmTitle.");
	puts("  -U, --security-classes <classes>  Filter on the specified security classes.");
	puts("  -u, --security <sec>              Set the security classification of the instance.");
	puts("  -V, --infoname-variant <variant>  Give the instance a different info name variant.");
	puts("  -v, --verbose                     Verbose output.");
	puts("  -W, --set-applic                  Overwrite whole object applicability.");
	puts("  -w, --whole-objects               Check the status of the whole object.");
	puts("  -X, --comment-xpath <xpath>       XPath where the -C comment will be inserted.");
	puts("  -x, --xsl <XSL>                   Use custom XSLT to resolve CIR references.");
	puts("  -Y, --applic <text>               Set applic for DM with text as the display text.");
	puts("  -y, --update-applic               Set applic for DM based on the user-defined defs.");
	puts("  -Z, --add-required                Fix certain elements automatically after filtering.");
	puts("  -z, --issue-type <type>           Set the issue type of the instance.");
	puts("  -1, --act <file>                  Specify custom ACT.");
	puts("  -2, --cct <file>                  Specify custom CCT.");
	puts("  -3, --no-repository-ident         Do not include <repositorySourceDmIdent>.");
	puts("  -4, --flatten-alts-refs           Flatten alts elements and adjust cross-references to them.");
	puts("  -5, --print                       Print the file name of the instance when -O is used.");
	puts("  -6, --clean-annotations           Remove unused applicability annotations.");
	puts("  -7, --dry-run                     Do not write anything out.");
	puts("  -8, --reapply                     Reapply the source object's applicability.");
	puts("  -9, --prune                       Simplify by removing only false assertions.");
	puts("  -0, --print-non-applic            Print the file names of objects which are not applicable.");
	puts("  -@, --update-instances            Update existing instance objects from their source.");
	puts("  -%, --read-only                   Make instances read-only.");
	puts("  -!, --no-infoname                 Do not include an infoName for the instance.");
	puts("  -~, --dependencies                Add CCT dependencies to annotations.");
	puts("  -^, --remove-deleted              Remove deleted objects/elements.");
	puts("  --version                         Show version information.");
	puts("  <object>...                       Source CSDB object(s)");
	LIBXML2_PARSE_LONGOPT_HELP
}

/* Print version information */
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)
{
	xmlNodePtr referencedApplicGroup;

	int i, c;

	/* User-defined applicability */
	xmlNodePtr applicability;

	/* Number of user-defined applicability definitions. */
	int napplics = 0;

	char code[256] = "";
	char out[PATH_MAX] = "-";
	bool clean = false;
	bool simpl = false;
	char *tech = NULL;
	char *info = NULL;
	xmlChar *info_name_variant = NULL;
	bool autoname = false;
	char dir[PATH_MAX] = "";
	bool new_applic = false;
	char new_display_text[256] = "";
	char comment_text[256] = "";
	char comment_path[256] = "/";
	char extension[256] = "";
	char language[256] = "";
	bool add_source_ident = true;
	bool add_rep_ident = true;
	bool force_overwrite = false;
	bool use_stdin = false;
	char issinfo[16] = "";
	bool incr_iss = false;
	char secu[4] = "";
	bool wholedm = false;
	char *useract = NULL;
	char *usercct = NULL;
	char *userpct = NULL;
	xmlDocPtr act = NULL;
	xmlDocPtr cct = NULL;
	xmlDocPtr pct = NULL;
	char product[64] = "";
	bool load_applic_per_dm = false;
	bool dmlist = false;
	FILE *list = NULL;
	char issdate[16] = "";
	char issdate_year[5];
	char issdate_month[3];
	char issdate_day[3];
	char *isstype = NULL;
	bool stripext = false;
	bool setorig = false;
	char *origspec = NULL;
	bool flat_alts = false;
	bool fix_alts_refs = false;
	char *remarks = NULL;
	char *skill_codes = NULL;
	char *sec_classes = NULL;
	char *skill = NULL;
	bool combine_applic = true;
	bool clean_ents = false;
	bool autocomp = false;
	bool use_stdout = true;
	bool update_inst = false;
	bool lock = false;
	bool no_info_name = false;
	bool add_deps = false;
	bool print_fnames = false;
	bool rslvcntrs = false;
	bool rem_unused = false;
	bool re_applic = false;
	bool remtrue = true;
	bool find_cir = false;
	bool write_files = true;
	bool print_non_applic = false;
	bool delete = false;
	bool fix_acronyms = false;

	xmlNodePtr cirs, cir;
	xmlDocPtr def_cir_xsl = NULL;

	xmlDocPtr props_report = NULL;
	enum listprops listprops = STANDALONE;

	const char *sopts = "AaC:c:D:d:Ee:FfG:gh?I:i:JjK:k:Ll:Mm:Nn:O:o:P:p:QqR:rSs:Tt:U:u:V:vWwX:x:Y:yZz:@%!1:2:34567890~H:^";
	struct option lopts[] = {
		{"version"            , no_argument      , 0, 0},
		{"help"               , no_argument      , 0, 'h'},
		{"reduce"             , no_argument      , 0, 'a'},
		{"simplify"           , no_argument      , 0, 'A'},
		{"code"               , required_argument, 0, 'c'},
		{"comment"            , required_argument, 0, 'C'},
		{"dump"               , required_argument, 0, 'D'},
		{"dir"                , required_argument, 0, 'd'},
		{"no-extension"       , no_argument      , 0, 'E'},
		{"extension"          , required_argument, 0, 'e'},
		{"flatten-alts"       , no_argument      , 0, 'F'},
		{"overwrite"          , no_argument      , 0, 'f'},
		{"set-orig"           , no_argument      , 0, 'g'},
		{"custom-orig"        , required_argument, 0, 'G'},
		{"infoname"           , required_argument, 0, 'i'},
		{"date"               , required_argument, 0, 'I'},
		{"clean-display-text" , no_argument      , 0, 'J'},
		{"clean-ents"         , no_argument      , 0, 'j'},
		{"skill-levels"       , required_argument, 0, 'K'},
		{"skill"              , required_argument, 0, 'k'},
		{"list"               , no_argument      , 0, 'L'},
		{"language"           , required_argument, 0, 'l'},
		{"fix-acronyms"       , no_argument      , 0, 'M'},
		{"remarks"            , required_argument, 0, 'm'},
		{"omit-issue"         , no_argument      , 0, 'N'},
		{"issue"              , required_argument, 0, 'n'},
		{"outdir"             , required_argument, 0, 'O'},
		{"out"                , required_argument, 0, 'o'},
		{"pct"                , required_argument, 0, 'P'},
		{"product"            , required_argument, 0, 'p'},
		{"quiet"              , no_argument      , 0, 'q'},
		{"cir"                , required_argument, 0, 'R'},
		{"recursive"          , no_argument      , 0, 'r'},
		{"no-source-ident"    , no_argument      , 0, 'S'},
		{"assign"             , required_argument, 0, 's'},
		{"tag"                , no_argument      , 0, 'T'},
		{"techname"           , required_argument, 0, 't'},
		{"security-classes"   , required_argument, 0, 'U'},
		{"security"           , required_argument, 0, 'u'},
		{"print"              , no_argument      , 0, '5'},
		{"verbose"            , no_argument      , 0, 'v'},
		{"set-applic"         , no_argument      , 0, 'W'},
		{"whole-objects"      , no_argument      , 0, 'w'},
		{"comment-xpath"      , required_argument, 0, 'X'},
		{"xsl"                , required_argument, 0, 'x'},
		{"applic"             , required_argument, 0, 'Y'},
		{"update-applic"      , no_argument      , 0 ,'y'},
		{"add-required"       , no_argument      , 0, 'Z'},
		{"issue-type"         , required_argument, 0, 'z'},
		{"update-instances"   , no_argument      , 0, '@'},
		{"read-only"          , no_argument      , 0, '%'},
		{"no-infoname"        , no_argument      , 0, '!'},
		{"act"                , required_argument, 0, '1'},
		{"cct"                , required_argument, 0, '2'},
		{"dependencies"       , no_argument      , 0, '~'},
		{"resolve-containers" , no_argument      , 0, 'Q'},
		{"no-repository-ident", no_argument      , 0, '3'},
		{"flatten-alts-refs"  , no_argument      , 0, '4'},
		{"list-properties"    , required_argument, 0, 'H'},
		{"infoname-variant"   , required_argument, 0, 'V'},
		{"clean-annotations"  , no_argument      , 0, '6'},
		{"dry-run"            , no_argument      , 0, '7'},
		{"reapply"            , no_argument      , 0, '8'},
		{"prune"              , no_argument      , 0, '9'},
		{"print-non-applic"   , no_argument      , 0, '0'},
		{"remove-deleted"     , no_argument      , 0, '^'},
		LIBXML2_PARSE_LONGOPT_DEFS
		{0, 0, 0, 0}
	};
	int loptind = 0;

	int err = EXIT_SUCCESS;

	exsltRegisterAll();

	opterr = 1;

	cirs = xmlNewNode(NULL, BAD_CAST "cirs");

	applicability = xmlNewNode(NULL, BAD_CAST "applic");

	search_dir = strdup(".");

	while ((c = getopt_long(argc, argv, sopts, lopts, &loptind)) != -1) {
		switch (c) {
			case 0:
				if (strcmp(lopts[loptind].name, "version") == 0) {
					show_version();
					goto cleanup;
				}
				LIBXML2_PARSE_LONGOPT_HANDLE(lopts, loptind, optarg)
				break;
			case 'a':
				clean = true;
				break;
			case 'A':
				simpl = true;
				break;
			case 'c':
				strncpy(code, optarg, 255);
				break;
			case 'C':
				strncpy(comment_text, optarg, 255);
				break;
			case 'D':
				dump_cir_xsl(optarg);
				goto cleanup;
			case 'd':
				free(search_dir); search_dir = strdup(optarg);
				break;
			case 'E':
				stripext = true;
				break;
			case 'e':
				strncpy(extension, optarg, 255);
				break;
			case 'F':
				flat_alts = true;
				break;
			case 'f':
				force_overwrite = true;
				break;
			case 'g':
				setorig = true;
				break;
			case 'G':
				setorig = true; origspec = strdup(optarg);
				break;
			case 'i':
				info = strdup(optarg);
				break;
			case 'I':
				strncpy(issdate, optarg, 15);
				break;
			case 'J':
				clean_disp_text = true;
				break;
			case 'j':
				clean_ents = true;
				break;
			case 'K':
				skill_codes = strdup(optarg);
				break;
			case 'k':
				skill = strdup(optarg);
				break;
			case 'L':
				dmlist = true;
				break;
			case 'l':
				strncpy(language, optarg, 255);
				break;
			case 'M':
				fix_acronyms = true;
				break;
			case 'm':
				free(remarks);
				remarks = strdup(optarg);
				break;
			case 'N':
				no_issue = true;
				break;
			case 'n':
				if (strcmp(optarg, "+") == 0) {
					incr_iss = true;
				} else {
					strncpy(issinfo, optarg, 15);
				}
				break;
			case 'O':
				autoname = true;
				strncpy(dir, optarg, PATH_MAX - 1);
				use_stdout = false;
				break;
			case 'o':
				strncpy(out, optarg, PATH_MAX - 1);
				use_stdout = false;
				break;
			case '1':
				useract = strdup(optarg);
				break;
			case '2':
				usercct = strdup(optarg);
				break;
			case 'P':
				userpct = strdup(optarg);
				break;
			case 'p':
				strncpy(product, optarg, 63);
				break;
			case 'Q':
				rslvcntrs = true;
				break;
			case 'q':
				--verbosity;
				break;
			case 'R':
				if (strcmp(optarg, "*") == 0) {
					find_cir = true;
				} else {
					xmlNewChild(cirs, NULL, BAD_CAST "cir", BAD_CAST optarg);
				}
				break;
			case 'r':
				recursive_search = true;
				break;
			case 'S':
				add_source_ident = false;
				break;
			case 's':
				read_applic(applicability, &napplics, optarg);
				break;
			case 'T':
				tag_non_applic = true;
				break;
			case 't':
				tech = strdup(optarg);
				break;
			case 'U':
				sec_classes = strdup(optarg);
				break;
			case 'u':
				strncpy(secu, optarg, 2);
				break;
			case 'V':
				info_name_variant = xmlStrdup(BAD_CAST optarg);
				break;
			case 'v':
				++verbosity;
				break;
			case 'W':
				new_applic = true; combine_applic = false;
				break;
			case 'w':
				wholedm = true;
				break;
			case 'X':
				strncpy(comment_path, optarg, 255);
				break;
			case 'x':
				if (cirs->last) {
					xmlSetProp(cirs->last, BAD_CAST "xsl", BAD_CAST optarg);
				} else if (!def_cir_xsl) {
					def_cir_xsl = read_xml_doc(optarg);
				}
				break;
			case 'y':
				new_applic = true;
				break;
			case 'Y':
				new_applic = true; strncpy(new_display_text, optarg, 255);
				break;
			case 'Z':
				autocomp = true;
				break;
			case 'z':
				isstype = strdup(optarg);
				break;
			case '@':
				update_inst = true;
				load_applic_per_dm = true;
				new_applic = true;
				break;
			case '%':
				lock = true;
				break;
			case '!':
				no_info_name = true;
				break;
			case '~':
				add_deps = true;
				break;
			case '3':
				add_rep_ident = false;
				break;
			case '4':
				flat_alts = true;
				fix_alts_refs = true;
				break;
			case '5':
				print_fnames = true;
				break;
			case '6':
				rem_unused = true;
				break;
			case '7':
				write_files = false;
				force_overwrite = true;
				break;
			case '8':
				re_applic = true;
				break;
			case '9':
				simpl = true;
				remtrue = false;
				break;
			case '0':
				print_non_applic = true;
				wholedm = true;
				break;
			case 'H':
				if (!props_report) {
					props_report = xmlNewDoc(BAD_CAST "1.0");

					if (strcmp(optarg, "all") == 0) {
						listprops = ALL;
					} else if (strcmp(optarg, "applic") == 0) {
						listprops = APPLIC;
					}
				}
				break;
			case '^':
				delete = true;
				break;
			case 'h':
			case '?':
				show_help();
				goto cleanup;
		}
	}

	/* Create a report of all applicability properties. */
	if (props_report) {
		xmlNodePtr properties;

		properties = xmlNewNode(NULL, BAD_CAST "properties");
		xmlDocSetRootElement(props_report, properties);

		switch (listprops) {
			case STANDALONE:
				xmlSetProp(properties, BAD_CAST "method", BAD_CAST "standalone");
				break;
			case ALL:
				xmlSetProp(properties, BAD_CAST "method", BAD_CAST "all");
				break;
			case APPLIC:
				xmlSetProp(properties, BAD_CAST "method", BAD_CAST "applic");
				break;
		}

		if (optind < argc) {
			int i;

			for (i = optind; i < argc; ++i) {
				if (dmlist) {
					add_props_list(properties, argv[i], listprops, useract, usercct, userpct);
				} else {
					add_props(properties, argv[i], listprops, useract, usercct, userpct);
				}
			}
		} else if (dmlist) {
			add_props_list(properties, NULL, listprops, useract, usercct, userpct);
		} else {
			add_props(properties, "-", listprops, useract, usercct, userpct);
		}

		transform_doc(props_report, xsl_sort_props_xsl, xsl_sort_props_xsl_len, NULL);

		save_xml_doc(props_report, "-");

		goto cleanup;
	}

	/* Except when in update mode, only add a source ident by default if
	 * any of the following are changed in the instance:
	 *
	 * - extension
	 * - code
	 * - issue info
	 * - language
	 */
	if (!update_inst && strcmp(extension, "") == 0 && strcmp(code, "") == 0 && strcmp(issinfo, "") == 0 && strcmp(language, "") == 0) {
		add_source_ident = false;
	}

	if (find_cir) {
		auto_add_cirs(cirs);
	}

	if (optind >= argc) {
		if (dmlist) {
			list = stdin;
		} else {
			use_stdin = true;
		}
	}

	/* If the -O option is given, create the directory if it does not
	 * exist.
	 *
	 * Fail if an existing non-directory file is specified.
	 *
	 * Ignore if the -7 option is given and no files will be written.
	 */
	if (autoname && write_files) {
		if (access(dir, F_OK) == -1) {
			int err;

			#ifdef _WIN32
				err = mkdir(dir);
			#else
				err = mkdir(dir, S_IRWXU);
			#endif

			if (err) {
				if (verbosity > QUIET) {
					fprintf(stderr, S_MKDIR_FAILED, dir);
				}
				exit(EXIT_BAD_ARG);
			}
		} else if (!isdir(dir, false)) {
			if (verbosity > QUIET) {
				fprintf(stderr, S_NOT_DIR, dir);
			}
			exit(EXIT_BAD_ARG);
		}
	}

	if (useract) {
		if (!(act = read_xml_doc(useract))) {
			if (verbosity > QUIET) {
				fprintf(stderr, S_MISSING_ACT, useract);
			}
			exit(EXIT_MISSING_FILE);
		}
	}
	if (usercct) {
		if (!(cct = read_xml_doc(usercct))) {
			if (verbosity > QUIET) {
				fprintf(stderr, S_MISSING_CCT, usercct);
			}
			exit(EXIT_MISSING_FILE);
		}
	}
	if (userpct) {
		if (!(pct = read_xml_doc(userpct))) {
			if (verbosity > QUIET) {
				fprintf(stderr, S_MISSING_PCT, userpct);
			}
			exit(EXIT_MISSING_FILE);
		}
	}

	if (strcmp(product, "") != 0) {
		/* If a PCT filename is specified with -P, use that for all data
		 * modules and ignore their individual ACT->PCT refs. */
		if (pct) {
			load_applic_from_pct(applicability, &napplics, pct, userpct, product);
		/* Otherwise the PCT must be loaded separately for each data
		 * module, since they may reference different ones. */
		} else {
			load_applic_per_dm = true;
		}
	}

	/* Determine the issue date from the -I option. */
	if (strcmp(issdate, "-") == 0) {
		time_t now;
		struct tm *local;

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

		if (snprintf(issdate_year, 5, "%d", local->tm_year + 1900) < 0 ||
		    snprintf(issdate_month, 3, "%.2d", local->tm_mon + 1) < 0 ||
		    snprintf(issdate_day, 3, "%.2d", local->tm_mday) < 0)
			exit(EXIT_BAD_DATE);
	} else if (strcmp(issdate, "") != 0) {
		if (sscanf(issdate, "%4s-%2s-%2s", issdate_year, issdate_month, issdate_day) != 3) {
			if (verbosity > QUIET) {
				fprintf(stderr, S_BAD_DATE, issdate);
			}
			exit(EXIT_BAD_DATE);
		}
	}

	i = optind;

	while (1) {
		xmlDocPtr doc;
		char src[PATH_MAX] = "";
		char *inst_src = NULL;

		if (dmlist) {
			if (!list && !(list = fopen(argv[i++], "r"))) {
				if (verbosity > QUIET) {
					fprintf(stderr, S_MISSING_LIST, argv[i - 1]);
				}
				exit(EXIT_MISSING_FILE);
			}

			if (!fgets(src, PATH_MAX - 1, list)) {
				fclose(list);
				list = NULL;

				if (i < argc) {
					continue;
				} else {
					break;
				}
			}

			strtok(src, "\t\r\n");
		} else if (i < argc) {
			strcpy(src, argv[i++]);
		} else if (use_stdin) {
			strcpy(src, "-");
		} else {
			break;
		}

		if (!use_stdin && access(src, F_OK) == -1) {
			if (verbosity > QUIET) {
				fprintf(stderr, S_MISSING_OBJECT, src);
			}
			exit(EXIT_MISSING_FILE);
		}

		/* Get the source from the sourceDmIdent/sourcePmIdent of the object. */
		if (update_inst) {
			int e;
			xmlDocPtr inst = NULL;

			inst_src = strdup(src);

			if ((e = find_source(src, &inst))) {
				if (e == 1) {
					if (verbosity > QUIET) {
						fprintf(stderr, S_MISSING_SOURCE, src);
					}
					err = EXIT_MISSING_SOURCE;
				}
				xmlFreeDoc(inst);
				free(inst_src);

				if (use_stdin) {
					break;
				} else {
					continue;
				}
			}

			if (verbosity >= VERBOSE) {
				fprintf(stderr, I_UPDATE_INST, inst_src, src);
			}

			load_applic_from_inst(applicability, inst);
			load_skill_from_inst(inst, &skill_codes);
			load_sec_from_inst(inst, &sec_classes);
			load_metadata_from_inst(inst,
				extension,
				code,
				language,
				issinfo,
				incr_iss,
				&tech,
				&info,
				&info_name_variant,
				&no_info_name,
				&isstype,
				secu,
				&origspec,
				&setorig,
				&skill,
				&remarks,
				&new_applic,
				new_display_text);
			add_cirs_from_inst(inst, cirs);

			xmlFreeDoc(inst);
		}

		if ((doc = read_xml_doc(src))) {
			if (verbosity >= VERBOSE && !update_inst) {
				if (autoname) {
					fprintf(stderr, I_CUSTOMIZE_DIR, src, dir);
				} else {
					fprintf(stderr, I_CUSTOMIZE, src);
				}
			}

			/* Load the ACT to find the CCT and/or PCT. */
			if (!useract && ((add_deps && !usercct) || (strcmp(product, "") != 0 && !userpct))) {
				char fname[PATH_MAX];
				if (find_act_fname(fname, doc)) {
					act = read_xml_doc(fname);
				}
			}

			/* Add dependency tests from the CCT. */
			if (add_deps) {
				if (usercct) {
					add_cct_depends(doc, cct, NULL);
				} else if (act) {
					char fname[PATH_MAX];
					if (find_cct_fname(fname, act)) {
						if ((cct = read_xml_doc(fname))) {
							add_cct_depends(doc, cct, NULL);
							xmlFreeDoc(cct);
						}
					}
				}
			}

			/* Load the applic assigns from the PCT data module referenced
			 * by the ACT data module referenced by this data module.
			 */
			if (!userpct && act && strcmp(product, "") != 0) {
				char fname[PATH_MAX];
				if (find_pct_fname(fname, act)) {
					if ((pct = read_xml_doc(fname))) {
						load_applic_from_pct(applicability, &napplics, pct, fname, product);
						xmlFreeDoc(pct);
					}
				}
			}

			if (act && !useract) {
				xmlFreeDoc(act);
				act = NULL;
			}

			if (re_applic) {
				load_applic_from_inst(applicability, doc);
			}

			if (!wholedm || create_instance(doc, applicability, skill_codes, sec_classes, delete)) {
				bool ispm;
				xmlNodePtr root;

				if (fix_acronyms) {
					fix_acronyms_pre(doc);
				}

				if (add_source_ident) {
					add_source(doc);
				}

				/* Updating an instance, so reset the source
				 * to the instance in case we want to
				 * overwrite it.
				 */
				if (update_inst) {
					strcpy(src, inst_src);
					free(inst_src);
				}

				root = xmlDocGetRootElement(doc);
				ispm = xmlStrcmp(root->name, BAD_CAST "pm") == 0;

				for (cir = cirs->children; cir; cir = cir->next) {
					char *cirdocfname = (char *) xmlNodeGetContent(cir);
					char *cirxsl = (char *) xmlGetProp(cir, BAD_CAST "xsl");

					if (access(cirdocfname, F_OK) == -1) {
						if (verbosity > QUIET) {
							fprintf(stderr, S_MISSING_CIR, cirdocfname);
						}
						continue;
					}

					if (ispm) {
						root = undepend_cir(doc, applicability, cirdocfname, false, cirxsl, def_cir_xsl);
					} else {
						root = undepend_cir(doc, applicability, cirdocfname, add_rep_ident, cirxsl, def_cir_xsl);
					}

					xmlFree(cirdocfname);
					xmlFree(cirxsl);
				}

				referencedApplicGroup = first_xpath_node(doc, NULL,
					BAD_CAST "//referencedApplicGroup|//inlineapplics");

				/* In -Q mode, add inline applicability to container DMs. */
				if (rslvcntrs && !referencedApplicGroup) {
					xmlNodePtr content;
					if ((content = first_xpath_node(doc, root, BAD_CAST "//content"))) {
						xmlNodePtr container;
						if ((container = first_xpath_node(doc, content, BAD_CAST "container"))) {
							referencedApplicGroup = add_container_applics(doc, content, container);
						}
					}
				}

				if (referencedApplicGroup) {
					if (applicability->children) {
						strip_applic(applicability, referencedApplicGroup, root);

						if (clean || simpl) {
							clean_applic_stmts(applicability, referencedApplicGroup, remtrue);

							if (xmlChildElementCount(referencedApplicGroup) == 0) {
								xmlUnlinkNode(referencedApplicGroup);
								xmlFreeNode(referencedApplicGroup);
								referencedApplicGroup = NULL;
							}

							clean_applic(referencedApplicGroup, root);

							if (simpl && referencedApplicGroup) {
								referencedApplicGroup = simpl_applic_clean(applicability, referencedApplicGroup, remtrue);
							}

							if (remtrue && referencedApplicGroup) {
								referencedApplicGroup = rem_supersets(applicability, referencedApplicGroup, root, !simpl);
							}
						}
					}

					if (rem_unused && referencedApplicGroup) {
						referencedApplicGroup = rem_unused_annotations(doc, referencedApplicGroup);
					}
				}

				/* Remove elements whose securityClassification is not
				 * in the given list. */
				if (sec_classes) {
					filter_elements_by_att(doc, "securityClassification", sec_classes);
				}
				/* Remove elements whose skillLevelCode is not in the
				 * given list. */
				if (skill_codes) {
					filter_elements_by_att(doc, "skillLevelCode", skill_codes);
				}
				/* Remove elements marked as "delete". */
				if (delete) {
					rem_delete_elems(doc);
				}

				if (strcmp(extension, "") != 0) {
					set_extd(doc, extension);
				}

				if (stripext) {
					strip_extension(doc);
				}

				if (strcmp(code, "") != 0) {
					set_code(doc, code);
				}

				set_title(doc, tech, info, info_name_variant, no_info_name);

				if (strcmp(language, "") != 0) {
					set_lang(doc, language);
				}

				if (new_applic && napplics > 0) {
					/* Simplify the whole object applic before
					 * adding the user-defined applicability, to
					 * remove duplicate information.
					 *
					 * If overwriting the applic instead of merging
					 * it, there's no need to do this.
					 */
					if (combine_applic) {
						simpl_whole_applic(applicability, doc, remtrue);
					}

					set_applic(doc, applicability, napplics, new_display_text, combine_applic);
				}

				if (strcmp(issinfo, "") != 0) {
					set_issue(doc, issinfo, incr_iss);
				}

				if (strcmp(issdate, "") != 0) {
					set_issue_date(doc, issdate_year, issdate_month, issdate_day);
				}

				if (isstype) {
					set_issue_type(doc, isstype);
				}

				if (strcmp(secu, "") != 0) {
					set_security(doc, secu);
				}

				if (setorig) {
					set_orig(doc, origspec);
				}

				if (skill) {
					set_skill(doc, skill);
				}

				if (remarks) {
					set_remarks(doc, remarks);
				}

				if (strcmp(comment_text, "") != 0) {
					insert_comment(doc, comment_text, comment_path);
				}

				if (ispm) {
					remove_empty_pmentries(doc);
				}

				if (flat_alts) {
					flatten_alts(doc, fix_alts_refs);
				}

				if (clean_ents) {
					clean_entities(doc);
				}

				if (autocomp) {
					autocomplete(doc);
				}

				if (fix_acronyms) {
					fix_acronyms_post(doc);
				}

				/* Resolve references to containers. */
				if (rslvcntrs) {
					resolve_containers(doc, applicability);
				}

				if (use_stdout && force_overwrite) {
					strcpy(out, src);
				} else if (autoname && !auto_name(out, src, doc, dir, no_issue)) {
					if (verbosity > QUIET) {
						fprintf(stderr, S_BAD_TYPE);
					}
					exit(EXIT_BAD_XML);
				}

				if (!use_stdout && access(out, F_OK) == 0 && !force_overwrite) {
					if (verbosity > QUIET) {
						fprintf(stderr, S_FILE_EXISTS, out);
					}
				} else {
					if (write_files) {
						save_xml_doc(doc, out);

						if (lock) {
							mkreadonly(out);
						}
					}

					if (print_fnames) {
						puts(out);
					}
				}
			} else {
				if (verbosity >= VERBOSE) {
					fprintf(stderr, I_NON_APPLIC, src);
				}

				if (print_non_applic) {
					puts(src);
				}
			}

			/* The ACT/PCT may be different for the next DM, so these
			 * assigns must be cleared. Those directly set with -s will
			 * carry over. */
			if (load_applic_per_dm) {
				clear_perdm_applic(applicability, &napplics);
			}

			xmlFreeDoc(doc);
		} else if (autoname) { /* Copy the non-XML object to the directory. */
			char *base;

			if (verbosity >= VERBOSE) {
				fprintf(stderr, I_COPY, src, dir);
			}

			base = basename(src);
			if (snprintf(out, PATH_MAX, "%s/%s", dir, base) < 0) {
				exit(EXIT_BAD_ARG);
			}

			if (access(out, F_OK) == 0 && !force_overwrite) {
				if (verbosity > QUIET) {
					fprintf(stderr, S_FILE_EXISTS, out);
				}
			} else {
				if (write_files) {
					copy(src, out);

					if (lock) {
						mkreadonly(out);
					}
				}

				if (print_fnames) {
					puts(out);
				}
			}
		} else {
			if (verbosity > QUIET) {
				fprintf(stderr, S_BAD_XML, use_stdin ? "stdin" : src);
			}
			exit(EXIT_BAD_XML);
		}

		if (use_stdin) {
			break;
		}
	}

cleanup:
	if (useract) {
		xmlFreeDoc(act);
		free(useract);
	}
	if (usercct) {
		xmlFreeDoc(cct);
		free(usercct);
	}
	if (userpct) {
		xmlFreeDoc(pct);
		free(userpct);
	}

	free(origspec);
	free(remarks);
	free(skill);
	free(skill_codes);
	free(sec_classes);
	free(search_dir);
	free(tech);
	free(info);
	free(isstype);
	xmlFree(info_name_variant);
	xmlFreeNode(cirs);
	xmlFreeDoc(def_cir_xsl);
	xmlFreeNode(applicability);
	xmlFreeDoc(props_report);
	xsltCleanupGlobals();
	xmlCleanupParser();

	return err;
}
#endif


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