#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