#include <stdio.h>
#include <unistd.h>
#include <getopt.h>
#include <string.h>
#include <stdbool.h>
#include <ctype.h>
#include <libgen.h>
#include <sys/stat.h>
#include <libxml/tree.h>
#include <libxml/xpath.h>
#include <libxml/debugXML.h>
#include <libxml/xpathInternals.h>
#include "s1kd_tools.h"

#define PROG_NAME "s1kd-refs"
#define VERSION "4.17.2"

#define ERR_PREFIX PROG_NAME ": ERROR: "
#define SUCC_PREFIX PROG_NAME ": SUCCESS: "
#define FAIL_PREFIX PROG_NAME ": FAILURE: "
#define INF_PREFIX PROG_NAME ": INFO: "

#define E_BAD_LIST ERR_PREFIX "Could not read list: %s\n"
#define E_OUT_OF_MEMORY ERR_PREFIX "Too many files in recursive listing.\n"
#define E_BAD_STDIN ERR_PREFIX "stdin does not contain valid XML.\n"
#define E_BAD_CSN_CODE ERR_PREFIX "Invalid non-chapterized IPD SNS: %s\n"

#define S_UNMATCHED SUCC_PREFIX "No unmatched references in %s\n"
#define F_UNMATCHED FAIL_PREFIX "Unmatched references in %s\n"

#define I_WHEREUSED INF_PREFIX "Searching for references to %s...\n"
#define I_UPDATE_REF INF_PREFIX "%s: Updating reference %s to match %s...\n"

#define EXIT_UNMATCHED_REF 1
#define EXIT_OUT_OF_MEMORY 2
#define EXIT_BAD_STDIN 3
#define EXIT_BAD_CSN_CODE 4

/* List only references found in the content section. */
static bool contentOnly = false;

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

/* Assume objects were created with the -N option. */
static bool noIssue = false;

/* Show unmatched references instead of an error. */
static bool showUnmatched = false;

/* Show references which are matched in the filesystem. */
static bool showMatched = true;

/* Recurse in to child directories. */
static bool recursive = false;

/* Directory to start search in. */
static char *directory;

/* Ignore issue info when matching. */
static bool ignoreIss = false;

/* Include the source object as a reference. */
static bool listSrc = false;

/* List references in matched objects recursively. */
static bool listRecursively = false;

/* Update the address information of references. */
static bool updateRefs = false;

/* Update the ident and address info from the latest matched issue. */
static bool updateRefIdent = false;

/* Overwrite updated input objects. */
static bool overwriteUpdated = false;

/* Remove unmatched references from the input objects. */
static bool tagUnmatched = false;

/* Command string to execute with the -e option. */
static char *execStr = NULL;

/* Non-chapterized IPD SNS. */
static bool nonChapIpdSns = false;
static char nonChapIpdSystemCode[4] = "";
static char nonChapIpdSubSystemCode[2] = "";
static char nonChapIpdSubSubSystemCode[2] = "";
static char nonChapIpdAssyCode[5] = "";

/* Figure number variant format string. */
static xmlChar *figNumVarFormat;

/* When listing references recursively, keep track of files which have already
* been listed to avoid loops.
*/
static char (*listedFiles)[PATH_MAX] = NULL;
static int numListedFiles = 0;
static long unsigned maxListedFiles = 1;

/* Possible objects to list references to. */
#define SHOW_COM 0x0001 /* Comments */
#define SHOW_DMC 0x0002 /* Data modules */
#define SHOW_ICN 0x0004 /* ICNs */
#define SHOW_PMC 0x0008 /* Publication modules */
#define SHOW_EPR 0x0010 /* External publications */
#define SHOW_HOT 0x0020 /* Hotspots */
#define SHOW_FRG 0x0040 /* Fragments */
#define SHOW_DML 0x0080 /* DMLs */
#define SHOW_SMC 0x0100 /* SCORM content packages */
#define SHOW_SRC 0x0200 /* Source ident */
#define SHOW_REP 0x0400 /* Repository source ident */
#define SHOW_IPD 0x0800 /* IPD data modules */
#define SHOW_CSN 0x1000 /* CSN items */

/* All possible objects. */
#define SHOW_ALL \
       SHOW_COM | \
       SHOW_DMC | \
       SHOW_ICN | \
       SHOW_PMC | \
       SHOW_EPR | \
       SHOW_HOT | \
       SHOW_FRG | \
       SHOW_DML | \
       SHOW_SMC | \
       SHOW_SRC | \
       SHOW_REP | \
       SHOW_IPD | \
       SHOW_CSN

/* All objects relevant to -w mode. */
#define SHOW_WHERE_USED \
       SHOW_COM | \
       SHOW_DMC | \
       SHOW_PMC | \
       SHOW_DML | \
       SHOW_SMC | \
       SHOW_ICN | \
       SHOW_SRC | \
       SHOW_REP | \
       SHOW_IPD | \
       SHOW_EPR

/* Write valid CSDB objects to stdout. */
static bool outputTree = false;

/* External pub list. */
static xmlDocPtr externalPubs = NULL;

/* Allow matching of filenames which only start with the code.
*
* If this is false, then the filenames must match the exact code up to the
* extension (last .)
*
* For example, with loose matching a code of ABC would match a file named
* ABC_001.PDF, while without loose matching it will not.
*/
static bool looseMatch = true;

/* XPath for matching hotspots. */
#define DEFAULT_HOTSPOT_XPATH BAD_CAST "/X3D//*[@DEF=$id]|//*[@id=$id]"
static xmlChar *hotspotXPath = NULL;
static xmlNodePtr hotspotNs = NULL;

/* Delimiter for the format string. */
#define FMTSTR_DELIM '%'
/* Custom format for printed references. */
static char *printFormat = NULL;

/* Remove elements marked as "delete". */
static bool remDelete = false;

/* Return the first node matching an XPath expression. */
static xmlNodePtr firstXPathNode(xmlDocPtr doc, xmlNodePtr root, const xmlChar *path)
{
       xmlNodePtr node;

       xmlXPathContextPtr ctx;
       xmlXPathObjectPtr obj;

       if (!doc && root)
               doc = root->doc;

       if (!doc)
               return NULL;

       ctx = xmlXPathNewContext(doc);

       if (root)
               ctx->node = root;

       obj = xmlXPathEvalExpression(BAD_CAST path, ctx);

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

       xmlXPathFreeObject(obj);
       xmlXPathFreeContext(ctx);

       return node;
}

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

/* Process and print info based on a format string. */
static void processFormatStr(FILE *f, xmlNodePtr node, const char *src, const char *ref, const char *fname)
{
       int i;

       for (i = 0; printFormat[i]; ++i) {
               if (printFormat[i] == FMTSTR_DELIM) {
                       if (printFormat[i + 1] == FMTSTR_DELIM) {
                               fputc(FMTSTR_DELIM, f);
                               ++i;
                       } else {
                               const char *k, *e;
                               int n;

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

                               if (strncmp(k, "src", n) == 0) {
                                       fprintf(f, "%s", src);
                               } else if (strncmp(k, "ref", n) == 0) {
                                       fprintf(f, "%s", ref);
                               } else if (strncmp(k, "file", n) == 0 && fname) {
                                       fprintf(f, "%s", fname);
                               } else if (strncmp(k, "line", n) == 0) {
                                       fprintf(f, "%ld", xmlGetLineNo(node));
                               } else if (strncmp(k, "xpath", n) == 0) {
                                       xmlChar *xpath = xpath_of(node);
                                       fprintf(f, "%s", (char *) xpath);
                                       xmlFree(xpath);
                               }

                               i += n + 1;
                       }
               } else if (printFormat[i] == '\\') {
                       switch (printFormat[i + 1]) {
                               case 'n': fputc('\n', f); i++; break;
                               case 't': fputc('\t', f); i++; break;
                               case '0': fputc('\0', f); i++; break;
                               default:  fputc(printFormat[i], f);
                       }
               } else {
                       fputc(printFormat[i], f);
               }
       }

       fputc('\n', f);
}

/* Print a reference which is matched in the filesystem. */
static void printMatched(xmlNodePtr node, const char *src, const char *ref, const char *fname)
{
       puts(ref);
}
static void printMatchedSrc(xmlNodePtr node, const char *src, const char *ref, const char *fname)
{
       printf("%s: %s\n", src, ref);
}
static void printMatchedSrcLine(xmlNodePtr node, const char *src, const char *ref, const char *fname)
{
       printf("%s (%ld): %s\n", src, xmlGetLineNo(node), ref);
}
static void printMatchedXml(xmlNodePtr node, const char *src, const char *ref, const char *fname)
{
       xmlChar *s, *r, *f, *xpath;
       xmlDocPtr doc;

       if (!node) {
               return;
       }

       s = xmlEncodeEntitiesReentrant(node->doc, BAD_CAST src);
       r = xmlEncodeEntitiesReentrant(node->doc, BAD_CAST ref);
       f = xmlEncodeEntitiesReentrant(node->doc, BAD_CAST fname);
       xpath = xpath_of(node);

       printf("<found>");

       printf("<ref>");
       doc = xmlNewDoc(BAD_CAST "1.0");
       if (node->type == XML_ATTRIBUTE_NODE) {
               xmlDocSetRootElement(doc, xmlCopyNode(node->parent, 1));
       } else {
               xmlDocSetRootElement(doc, xmlCopyNode(node, 1));
       }
       xmlShellPrintNode(xmlDocGetRootElement(doc));
       xmlFreeDoc(doc);
       printf("</ref>");

       printf("<source line=\"%ld\" xpath=\"%s\">%s</source>", xmlGetLineNo(node), xpath, s);
       printf("<code>%s</code>", r);
       if (f) {
               printf("<filename>%s</filename>", f);
       }

       printf("</found>");

       xmlFree(s);
       xmlFree(r);
       xmlFree(xpath);
}
static void printMatchedWhereUsed(xmlNodePtr node, const char *src, const char *ref, const char *fname)
{
       printf("%s\n", src);
}
static void printMatchedCustom(xmlNodePtr node, const char *src, const char *ref, const char *fname)
{
       processFormatStr(stdout, node, src, ref, fname);
}

static void execMatched(xmlNodePtr node, const char *src, const char *ref, const char *fname)
{
       execfile(execStr, fname);
}

/* Print an error for references which are unmatched. */
static void printUnmatched(xmlNodePtr node, const char *src, const char *ref, const char *fname)
{
       fprintf(stderr, ERR_PREFIX "Unmatched reference: %s\n", ref);
}
static void printUnmatchedSrc(xmlNodePtr node, const char *src, const char *ref, const char *fname)
{
       fprintf(stderr, ERR_PREFIX "%s: Unmatched reference: %s\n", src, ref);
}
static void printUnmatchedSrcLine(xmlNodePtr node, const char *src, const char *ref, const char *fname)
{
       fprintf(stderr, ERR_PREFIX "%s (%ld): Unmatched reference: %s\n", src, xmlGetLineNo(node), ref);
}
static void printUnmatchedXml(xmlNodePtr node, const char *src, const char *ref, const char *fname)
{
       xmlChar *s, *r, *f, *xpath;
       xmlDocPtr doc;

       s = xmlEncodeEntitiesReentrant(node->doc, BAD_CAST src);
       r = xmlEncodeEntitiesReentrant(node->doc, BAD_CAST ref);
       f = xmlEncodeEntitiesReentrant(node->doc, BAD_CAST fname);
       xpath = xpath_of(node);

       printf("<missing>");

       printf("<ref>");
       doc = xmlNewDoc(BAD_CAST "1.0");
       if (node->type == XML_ATTRIBUTE_NODE) {
               xmlDocSetRootElement(doc, xmlCopyNode(node->parent, 1));
       } else {
               xmlDocSetRootElement(doc, xmlCopyNode(node, 1));
       }
       xmlShellPrintNode(xmlDocGetRootElement(doc));
       xmlFreeDoc(doc);
       printf("</ref>");

       printf("<source line=\"%ld\" xpath=\"%s\">%s</source>", xmlGetLineNo(node), xpath, s);
       printf("<code>%s</code>", r);
       if (f) {
               printf("<filename>%s</filename>", f);
       }

       printf("</missing>");

       xmlFree(s);
       xmlFree(r);
       xmlFree(xpath);
}
static void printUnmatchedCustom(xmlNodePtr node, const char *src, const char *ref, const char *fname)
{
       fputs(ERR_PREFIX "Unmatched reference: ", stderr);
       processFormatStr(stderr, node, src, ref, fname);
}

static void (*printMatchedFn)(xmlNodePtr, const char *, const char *, const char *) = printMatched;
static void (*printUnmatchedFn)(xmlNodePtr, const char *, const char*, const char *) = printUnmatched;

static bool exact_match(char *dst, const char *code)
{
       char *s, *base;
       bool match;

       s = strdup(dst);
       base = basename(s);

       match = strrchr(base, '.') - base == strlen(code);

       free(s);

       return match;
}

/* Match a code to a file name. */
static bool find_object_fname(char *dst, const char *dir, const char *code, bool recursive)
{
       return find_csdb_object(dst, dir, code, NULL, recursive) && (looseMatch || exact_match(dst, code));
}

/* Tag unmatched references in the source object. */
static void tagUnmatchedRef(xmlNodePtr ref)
{
       add_first_child(ref, xmlNewPI(BAD_CAST "unmatched", NULL));
}

/* Get the DMC as a string from a dmRef. */
static void getDmCode(char *dst, xmlNodePtr dmRef)
{
       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;

       xmlNodePtr identExtension, dmCode, issueInfo, language;

       identExtension = firstXPathNode(NULL, dmRef, BAD_CAST "dmRefIdent/identExtension|dmcextension");
       dmCode = firstXPathNode(NULL, dmRef, BAD_CAST "dmRefIdent/dmCode|dmc/avee|avee");

       if (ignoreIss) {
               issueInfo = NULL;
       } else {
               issueInfo = firstXPathNode(NULL, dmRef, BAD_CAST "dmRefIdent/issueInfo|issno");
       }

       language = firstXPathNode(NULL, dmRef, BAD_CAST "dmRefIdent/language|language");

       strcpy(dst, "");

       if (identExtension) {
               char *extensionProducer, *extensionCode;

               extensionProducer = (char *) firstXPathValue(NULL, identExtension, BAD_CAST "@extensionProducer|dmeproducer");
               extensionCode     = (char *) firstXPathValue(NULL, identExtension, BAD_CAST "@extensionCode|dmecode");

               strcat(dst, "DME-");

               strcat(dst, extensionProducer);
               strcat(dst, "-");
               strcat(dst, extensionCode);
               strcat(dst, "-");

               xmlFree(extensionProducer);
               xmlFree(extensionCode);
       } else {
               strcat(dst, "DMC-");
       }

       modelIdentCode     = (char *) firstXPathValue(NULL, dmCode, BAD_CAST "@modelIdentCode|modelic");
       systemDiffCode     = (char *) firstXPathValue(NULL, dmCode, BAD_CAST "@systemDiffCode|sdc");
       systemCode         = (char *) firstXPathValue(NULL, dmCode, BAD_CAST "@systemCode|chapnum");
       subSystemCode      = (char *) firstXPathValue(NULL, dmCode, BAD_CAST "@subSystemCode|section");
       subSubSystemCode   = (char *) firstXPathValue(NULL, dmCode, BAD_CAST "@subSubSystemCode|subsect");
       assyCode           = (char *) firstXPathValue(NULL, dmCode, BAD_CAST "@assyCode|subject");
       disassyCode        = (char *) firstXPathValue(NULL, dmCode, BAD_CAST "@disassyCode|discode");
       disassyCodeVariant = (char *) firstXPathValue(NULL, dmCode, BAD_CAST "@disassyCodeVariant|discodev");
       infoCode           = (char *) firstXPathValue(NULL, dmCode, BAD_CAST "@infoCode|incode");
       infoCodeVariant    = (char *) firstXPathValue(NULL, dmCode, BAD_CAST "@infoCodeVariant|incodev");
       itemLocationCode   = (char *) firstXPathValue(NULL, dmCode, BAD_CAST "@itemLocationCode|itemloc");
       learnCode          = (char *) firstXPathValue(NULL, dmCode, BAD_CAST "@learnCode");
       learnEventCode     = (char *) firstXPathValue(NULL, dmCode, BAD_CAST "@learnEventCode");

       if (modelIdentCode) {
               strcat(dst, modelIdentCode);
               strcat(dst, "-");
               strcat(dst, systemDiffCode);
               strcat(dst, "-");
               strcat(dst, systemCode);
               strcat(dst, "-");
               strcat(dst, subSystemCode);
               strcat(dst, subSubSystemCode);
               strcat(dst, "-");
               strcat(dst, assyCode);
               strcat(dst, "-");
               strcat(dst, disassyCode);
               strcat(dst, disassyCodeVariant);
               strcat(dst, "-");
               strcat(dst, infoCode);
               strcat(dst, infoCodeVariant);
               strcat(dst, "-");
               strcat(dst, itemLocationCode);

               if (learnCode) {
                       strcat(dst, "-");
                       strcat(dst, learnCode);
                       strcat(dst, learnEventCode);
               }
       }

       xmlFree(modelIdentCode);
       xmlFree(systemDiffCode);
       xmlFree(systemCode);
       xmlFree(subSystemCode);
       xmlFree(subSubSystemCode);
       xmlFree(assyCode);
       xmlFree(disassyCode);
       xmlFree(disassyCodeVariant);
       xmlFree(infoCode);
       xmlFree(infoCodeVariant);
       xmlFree(itemLocationCode);
       xmlFree(learnCode);
       xmlFree(learnEventCode);

       if (!noIssue) {
               if (issueInfo) {
                       char *issueNumber, *inWork;

                       issueNumber = (char *) firstXPathValue(NULL, issueInfo, BAD_CAST "@issueNumber|@issno");
                       inWork      = (char *) firstXPathValue(NULL, issueInfo, BAD_CAST "@inWork|@inwork");

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

                       strcat(dst, "_");
                       strcat(dst, issueNumber);
                       strcat(dst, "-");
                       strcat(dst, inWork);

                       xmlFree(issueNumber);
                       xmlFree(inWork);
               } else if (language) {
                       strcat(dst, "_\?\?\?-\?\?");
               }
       }

       if (language) {
               char *languageIsoCode, *countryIsoCode;

               languageIsoCode = (char *) firstXPathValue(NULL, language, BAD_CAST "@languageIsoCode|@language");
               countryIsoCode  = (char *) firstXPathValue(NULL, language, BAD_CAST "@countryIsoCode|@country");

               uppercase(languageIsoCode);

               strcat(dst, "_");
               strcat(dst, languageIsoCode);
               strcat(dst, "-");
               strcat(dst, countryIsoCode);

               xmlFree(languageIsoCode);
               xmlFree(countryIsoCode);
       }
}

/* Get the PMC as a string from a pmRef. */
static void getPmCode(char *dst, xmlNodePtr pmRef)
{
       xmlNodePtr identExtension, pmCode, issueInfo, language;

       char *modelIdentCode;
       char *pmIssuer;
       char *pmNumber;
       char *pmVolume;

       identExtension = firstXPathNode(NULL, pmRef, BAD_CAST "pmRefIdent/identExtension");
       pmCode         = firstXPathNode(NULL, pmRef, BAD_CAST "pmRefIdent/pmCode|pmc");

       if (ignoreIss) {
               issueInfo = NULL;
       } else {
               issueInfo = firstXPathNode(NULL, pmRef, BAD_CAST "pmRefIdent/issueInfo|issno");
       }

       language = firstXPathNode(NULL, pmRef, BAD_CAST "pmRefIdent/language|language");

       strcpy(dst, "");

       if (identExtension) {
               char *extensionProducer, *extensionCode;

               extensionProducer = (char *) xmlGetProp(identExtension, BAD_CAST "extensionProducer");
               extensionCode     = (char *) xmlGetProp(identExtension, BAD_CAST "extensionCode");

               strcat(dst, "PME-");

               strcat(dst, extensionProducer);
               strcat(dst, "-");
               strcat(dst, extensionCode);
               strcat(dst, "-");

               xmlFree(extensionProducer);
               xmlFree(extensionCode);
       } else {
               strcat(dst, "PMC-");
       }

       modelIdentCode = (char *) firstXPathValue(NULL, pmCode, BAD_CAST "@modelIdentCode|modelic");
       pmIssuer       = (char *) firstXPathValue(NULL, pmCode, BAD_CAST "@pmIssuer|pmissuer");
       pmNumber       = (char *) firstXPathValue(NULL, pmCode, BAD_CAST "@pmNumber|pmnumber");
       pmVolume       = (char *) firstXPathValue(NULL, pmCode, BAD_CAST "@pmVolume|pmvolume");

       strcat(dst, modelIdentCode);
       strcat(dst, "-");
       strcat(dst, pmIssuer);
       strcat(dst, "-");
       strcat(dst, pmNumber);
       strcat(dst, "-");
       strcat(dst, pmVolume);

       xmlFree(modelIdentCode);
       xmlFree(pmIssuer);
       xmlFree(pmNumber);
       xmlFree(pmVolume);

       if (!noIssue) {
               if (issueInfo) {
                       char *issueNumber, *inWork;

                       issueNumber = (char *) firstXPathValue(NULL, issueInfo, BAD_CAST "@issueNumber|@issno");
                       inWork      = (char *) firstXPathValue(NULL, issueInfo, BAD_CAST "@inWork|@inwork");

                       strcat(dst, "_");
                       strcat(dst, issueNumber);
                       strcat(dst, "-");
                       strcat(dst, inWork);

                       xmlFree(issueNumber);
                       xmlFree(inWork);
               } else if (language) {
                       strcat(dst, "_\?\?\?-\?\?");
               }
       }

       if (language) {
               char *languageIsoCode, *countryIsoCode;

               languageIsoCode = (char *) firstXPathValue(NULL, language, BAD_CAST "@languageIsoCode|@language");
               countryIsoCode  = (char *) firstXPathValue(NULL, language, BAD_CAST "@countryIsoCode|@country");

               uppercase(languageIsoCode);

               strcat(dst, "_");
               strcat(dst, languageIsoCode);
               strcat(dst, "-");
               strcat(dst, countryIsoCode);

               xmlFree(languageIsoCode);
               xmlFree(countryIsoCode);
       }
}

/* Get the code of the source DM or PM. */
static void getSourceIdent(char *dst, xmlNodePtr sourceIdent)
{
       xmlDocPtr refdoc;
       xmlNodePtr ref, ident;

       refdoc = xmlNewDoc(BAD_CAST "1.0");
       ident = xmlCopyNode(sourceIdent, 1);

       if (xmlStrcmp(sourceIdent->name, BAD_CAST "sourcePmIdent") == 0) {
               ref = xmlNewNode(NULL, BAD_CAST "pmRef");
               xmlDocSetRootElement(refdoc, ref);
               xmlNodeSetName(ident, BAD_CAST "pmRefIdent");
               ident = xmlAddChild(ref, ident);

               getPmCode(dst, ref);
       } else {
               ref = xmlNewNode(NULL, BAD_CAST "dmRef");
               xmlDocSetRootElement(refdoc, ref);
               xmlNodeSetName(ident, BAD_CAST "dmRefIdent");
               ident = xmlAddChild(ref, ident);

               getDmCode(dst, ref);
       }

       xmlFreeDoc(refdoc);
}

/* Get the SMC as a string from a scormContentPackageRef. */
static void getSmcCode(char *dst, xmlNodePtr smcRef)
{
       xmlNodePtr identExtension, smcCode, issueInfo, language;

       char *modelIdentCode;
       char *smcIssuer;
       char *smcNumber;
       char *smcVolume;

       identExtension = firstXPathNode(NULL, smcRef, BAD_CAST "scormContentPackageRefIdent/identExtension");
       smcCode        = firstXPathNode(NULL, smcRef, BAD_CAST "scormContentPackageRefIdent/scormContentPackageCode");

       if (ignoreIss) {
               issueInfo = NULL;
       } else {
               issueInfo = firstXPathNode(NULL, smcRef, BAD_CAST "scormContentPackageRefIdent/issueInfo");
       }

       language = firstXPathNode(NULL, smcRef, BAD_CAST "scormContentPackageRefIdent/language");

       strcpy(dst, "");

       if (identExtension) {
               char *extensionProducer, *extensionCode;

               extensionProducer = (char *) xmlGetProp(identExtension, BAD_CAST "extensionProducer");
               extensionCode     = (char *) xmlGetProp(identExtension, BAD_CAST "extensionCode");

               strcat(dst, "SME-");

               if (extensionProducer && extensionCode) {
                       strcat(dst, extensionProducer);
                       strcat(dst, "-");
                       strcat(dst, extensionCode);
                       strcat(dst, "-");
               }

               xmlFree(extensionProducer);
               xmlFree(extensionCode);
       } else {
               strcat(dst, "SMC-");
       }

       modelIdentCode = (char *) xmlGetProp(smcCode, BAD_CAST "modelIdentCode");
       smcIssuer      = (char *) xmlGetProp(smcCode, BAD_CAST "scormContentPackageIssuer");
       smcNumber      = (char *) xmlGetProp(smcCode, BAD_CAST "scormContentPackageNumber");
       smcVolume      = (char *) xmlGetProp(smcCode, BAD_CAST "scormContentPackageVolume");

       if (modelIdentCode && smcIssuer && smcNumber && smcVolume) {
               strcat(dst, modelIdentCode);
               strcat(dst, "-");
               strcat(dst, smcIssuer);
               strcat(dst, "-");
               strcat(dst, smcNumber);
               strcat(dst, "-");
               strcat(dst, smcVolume);
       }

       xmlFree(modelIdentCode);
       xmlFree(smcIssuer);
       xmlFree(smcNumber);
       xmlFree(smcVolume);

       if (!noIssue) {
               if (issueInfo) {
                       char *issueNumber, *inWork;

                       issueNumber = (char *) xmlGetProp(issueInfo, BAD_CAST "issueNumber");
                       inWork      = (char *) xmlGetProp(issueInfo, BAD_CAST "inWork");

                       if (issueNumber && inWork) {
                               strcat(dst, "_");
                               strcat(dst, issueNumber);
                               strcat(dst, "-");
                               strcat(dst, inWork);
                       }

                       xmlFree(issueNumber);
                       xmlFree(inWork);
               } else if (language) {
                       strcat(dst, "_\?\?\?-\?\?");
               }
       }

       if (language) {
               char *languageIsoCode, *countryIsoCode;

               languageIsoCode = (char *) xmlGetProp(language, BAD_CAST "languageIsoCode");
               countryIsoCode  = (char *) xmlGetProp(language, BAD_CAST "countryIsoCode");

               if (languageIsoCode && countryIsoCode) {
                       uppercase(languageIsoCode);

                       strcat(dst, "_");
                       strcat(dst, languageIsoCode);
                       strcat(dst, "-");
                       strcat(dst, countryIsoCode);
               }

               xmlFree(languageIsoCode);
               xmlFree(countryIsoCode);
       }
}

/* Get the ICN as a string from an ICN reference. */
static void getICN(char *dst, xmlNodePtr ref)
{
       char *icn;
       icn = (char *) xmlGetProp(ref, BAD_CAST "infoEntityRefIdent");
       strcpy(dst, icn);
       xmlFree(icn);
}

/* Get the ICN as a string from an ICN entity reference. */
static void getICNAttr(char *dst, xmlNodePtr ref)
{
       xmlChar *icn;
       xmlEntityPtr ent;
       icn = xmlNodeGetContent(ref);
       if ((ent = xmlGetDocEntity(ref->doc, icn)) && ent->URI) {
               char uri[PATH_MAX], *base;
               strcpy(uri, (char *) ent->URI);
               base = basename(uri);
               strcpy(dst, base);
       } else {
               strcpy(dst, (char *) icn);
       }

       /* Remove issue number when not doing a full match. */
       if (ignoreIss) {
               char *e = strrchr(dst, '-');
               char *s = e - 3;

               if (e && s >= dst) {
                       *s = 0;
               }
       }

       xmlFree(icn);
}

/* Match each hotspot against the ICN. */
static int matchHotspot(xmlNodePtr ref, xmlDocPtr doc, const char *code, const char *fname, const char *src)
{
       xmlChar *apsid;
       xmlXPathContextPtr ctx;
       xmlXPathObjectPtr obj;
       xmlNodePtr node;
       char *s;
       int err = doc == NULL;

       apsid = xmlNodeGetContent(ref);

       if (doc) {
               xmlNodePtr cur;

               ctx = xmlXPathNewContext(doc);

               /* Register namespaces for the hotspot XPath. */
               for (cur = hotspotNs->children; cur; cur = cur->next) {
                       xmlChar *prefix, *uri;

                       prefix = xmlGetProp(cur, BAD_CAST "prefix");
                       uri = xmlGetProp(cur, BAD_CAST "uri");

                       xmlXPathRegisterNs(ctx, prefix, uri);

                       xmlFree(prefix);
                       xmlFree(uri);
               }

               xmlXPathRegisterVariable(ctx, BAD_CAST "id", xmlXPathNewString(apsid));
               obj = xmlXPathEvalExpression(hotspotXPath, ctx);

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

               xmlXPathFreeObject(obj);
               xmlXPathFreeContext(ctx);

               if (node) {
                       if (showMatched && !tagUnmatched) {
                               s = malloc(strlen(fname) + strlen((char *) apsid) + 2);
                               strcpy(s, fname);
                               strcat(s, "#");
                               strcat(s, (char *) apsid);
                               printMatchedFn(ref, src, s, fname);
                               free(s);
                       }
               } else {
                       ++err;
               }
       }

       if (err) {
               s = malloc(strlen(code) + strlen((char *) apsid) + 2);
               strcpy(s, code);
               strcat(s, "#");
               strcat(s, (char *) apsid);

               if (tagUnmatched) {
                       tagUnmatchedRef(ref);
               } else if (showUnmatched) {
                       printMatchedFn(ref, src, s, fname);
               } else if (verbosity >= NORMAL) {
                       printUnmatchedFn(ref, src, s, fname);
               }

               free(s);
       }

       xmlFree(apsid);
       return err;
}

/* Match the hotspots for an XML-based ICN. */
static int getHotspots(xmlNodePtr ref, const char *src)
{
       xmlXPathContextPtr ctx;
       xmlXPathObjectPtr obj;
       int err = 0;

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

       /* Select all hotspots that have an APS ID, meaning they point to some
        * object in the ICN (vs. using coordinates).
        */
       obj = xmlXPathEvalExpression(BAD_CAST ".//hotspot/@applicationStructureIdent|.//hotspot/@apsid", ctx);

       if (!xmlXPathNodeSetIsEmpty(obj->nodesetval)) {
               xmlNodePtr icn;
               char code[PATH_MAX], fname[PATH_MAX];
               int i;
               xmlDocPtr doc;

               icn = firstXPathNode(ref->doc, ref, BAD_CAST "@infoEntityIdent|@boardno");

               getICNAttr(code, icn);

               if (find_object_fname(fname, directory, code, recursive)) {
                       doc = read_xml_doc(fname);
               } else {
                       doc = NULL;
               }

               if (remDelete) {
                       rem_delete_elems(doc);
               }

               for (i = 0; i < obj->nodesetval->nodeNr; ++i) {
                       err += matchHotspot(obj->nodesetval->nodeTab[i], doc, code, doc ? fname : code, src);
               }

               xmlFreeDoc(doc);
       }

       xmlXPathFreeObject(obj);
       xmlXPathFreeContext(ctx);

       return err;
}

/* Match a single referred fragment in another DM. */
static int matchFragment(xmlDocPtr doc, xmlNodePtr ref, const char *code, const char *fname, const char *src)
{
       xmlChar *id;
       xmlXPathContextPtr ctx;
       xmlXPathObjectPtr obj;
       xmlNodePtr node;
       char *s;
       int err = doc == NULL;

       id = xmlNodeGetContent(ref);

       if (doc) {
               ctx = xmlXPathNewContext(doc);
               xmlXPathRegisterVariable(ctx, BAD_CAST "id", xmlXPathNewString(id));
               obj = xmlXPathEvalExpression(BAD_CAST "//*[@id=$id]", ctx);

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

               xmlXPathFreeObject(obj);
               xmlXPathFreeContext(ctx);

               if (node) {
                       if (showMatched && !tagUnmatched) {
                               s = malloc(strlen(fname) + strlen((char *) id) + 2);
                               strcpy(s, fname);
                               strcat(s, "#");
                               strcat(s, (char *) id);
                               printMatchedFn(ref, src, s, fname);
                               free(s);
                       }
               } else {
                       ++err;
               }
       }

       if (err) {
               s = malloc(strlen(doc ? fname : code) + strlen((char *) id) + 2);
               strcpy(s, doc ? fname : code);
               strcat(s, "#");
               strcat(s, (char *) id);

               if (tagUnmatched) {
                       tagUnmatchedRef(ref);
               } else if (showUnmatched) {
                       printMatchedFn(ref, src, s, doc ? fname : NULL);
               } else if (verbosity >= NORMAL) {
                       printUnmatchedFn(ref, src, s, doc ? fname : NULL);
               }

               free(s);
       }

       xmlFree(id);
       return err;

}

/* Match the referred fragments in another DM. */
static int getFragment(xmlNodePtr ref, const char *src)
{
       xmlNodePtr dmref;
       char code[PATH_MAX], fname[PATH_MAX];
       xmlDocPtr doc;
       int err;

       dmref = firstXPathNode(ref->doc, ref, BAD_CAST "ancestor::dmRef");

       getDmCode(code, dmref);

       if (find_object_fname(fname, directory, code, recursive)) {
               doc = read_xml_doc(fname);
       } else {
               doc = NULL;
       }

       if (remDelete) {
               rem_delete_elems(doc);
       }

       err = matchFragment(doc, ref, code, doc ? fname : code, src);

       xmlFreeDoc(doc);

       return err;
}

/* Get the comment code as a string from a commentRef. */
static void getComCode(char *dst, xmlNodePtr ref)
{
       xmlNodePtr commentCode, language;

       char *modelIdentCode;
       char *senderIdent;
       char *yearOfDataIssue;
       char *seqNumber;
       char *commentType;

       commentCode = firstXPathNode(NULL, ref, BAD_CAST "commentRefIdent/commentCode");

       language = firstXPathNode(NULL, ref, BAD_CAST "commentRefIdent/language");

       modelIdentCode  = (char *) xmlGetProp(commentCode, BAD_CAST "modelIdentCode");
       senderIdent     = (char *) xmlGetProp(commentCode, BAD_CAST "senderIdent");
       yearOfDataIssue = (char *) xmlGetProp(commentCode, BAD_CAST "yearOfDataIssue");
       seqNumber       = (char *) xmlGetProp(commentCode, BAD_CAST "seqNumber");
       commentType     = (char *) xmlGetProp(commentCode, BAD_CAST "commentType");

       strcpy(dst, "COM-");
       strcat(dst, modelIdentCode);
       strcat(dst, "-");
       strcat(dst, senderIdent);
       strcat(dst, "-");
       strcat(dst, yearOfDataIssue);
       strcat(dst, "-");
       strcat(dst, seqNumber);
       strcat(dst, "-");
       strcat(dst, commentType);

       xmlFree(modelIdentCode);
       xmlFree(senderIdent);
       xmlFree(yearOfDataIssue);
       xmlFree(seqNumber);
       xmlFree(commentType);

       if (language) {
               char *languageIsoCode, *countryIsoCode;

               languageIsoCode = (char *) xmlGetProp(language, BAD_CAST "languageIsoCode");
               countryIsoCode  = (char *) xmlGetProp(language, BAD_CAST "countryIsoCode");

               uppercase(languageIsoCode);

               strcat(dst, "_");
               strcat(dst, languageIsoCode);
               strcat(dst, "-");
               strcat(dst, countryIsoCode);

               xmlFree(languageIsoCode);
               xmlFree(countryIsoCode);
       }
}

/* Get the DML code as a string from a dmlRef. */
static void getDmlCode(char *dst, xmlNodePtr ref)
{
       xmlNodePtr dmlCode, issueInfo;

       char *modelIdentCode;
       char *senderIdent;
       char *dmlType;
       char *yearOfDataIssue;
       char *seqNumber;

       dmlCode   = firstXPathNode(NULL, ref, BAD_CAST "dmlRefIdent/dmlCode");
       issueInfo = firstXPathNode(NULL, ref, BAD_CAST "dmlRefIdent/issueInfo");

       modelIdentCode  = (char *) xmlGetProp(dmlCode, BAD_CAST "modelIdentCode");
       senderIdent     = (char *) xmlGetProp(dmlCode, BAD_CAST "senderIdent");
       dmlType         = (char *) xmlGetProp(dmlCode, BAD_CAST "dmlType");
       yearOfDataIssue = (char *) xmlGetProp(dmlCode, BAD_CAST "yearOfDataIssue");
       seqNumber       = (char *) xmlGetProp(dmlCode, BAD_CAST "seqNumber");

       uppercase(dmlType);

       strcpy(dst, "DML-");
       strcat(dst, modelIdentCode);
       strcat(dst, "-");
       strcat(dst, senderIdent);
       strcat(dst, "-");
       strcat(dst, dmlType);
       strcat(dst, "-");
       strcat(dst, yearOfDataIssue);
       strcat(dst, "-");
       strcat(dst, seqNumber);

       xmlFree(modelIdentCode);
       xmlFree(senderIdent);
       xmlFree(dmlType);
       xmlFree(yearOfDataIssue);
       xmlFree(seqNumber);

       if (issueInfo) {
               char *issueNumber, *inWork;

               issueNumber = (char *) xmlGetProp(issueInfo, BAD_CAST "issueNumber");
               inWork      = (char *) xmlGetProp(issueInfo, BAD_CAST "inWork");

               strcat(dst, "_");
               strcat(dst, issueNumber);
               strcat(dst, "-");
               strcat(dst, inWork);

               xmlFree(issueNumber);
               xmlFree(inWork);
       }
}

/* Get the external pub code as a string from an externalPubRef. */
static void getExternalPubCode(char *dst, xmlNodePtr ref)
{
       xmlNodePtr externalPubCode;
       char *code;

       externalPubCode = firstXPathNode(NULL, ref,
               BAD_CAST "externalPubRefIdent/externalPubCode|externalPubRefIdent/externalPubTitle|pubcode");

       if (externalPubCode) {
               code = (char *) xmlNodeGetContent(externalPubCode);
       } else {
               code = (char *) xmlNodeGetContent(ref);
       }

       strcpy(dst, code);

       xmlFree(code);
}

/* Get filename from DDN item. */
static void getDispatchFileName(char *dst, xmlNodePtr ref)
{
       char *fname;
       fname = (char *) xmlNodeGetContent(ref);
       strcpy(dst, fname);
       xmlFree(fname);
}

/* Get the disassembly code variant pattern using the figure number variant and
* the specified format string. */
static xmlChar *formatFigNumVar(const xmlChar *figureNumberVariant)
{
       int i;
       xmlChar *disassyCodeVariant;

       disassyCodeVariant = xmlStrdup(figNumVarFormat);

       for (i = 0; disassyCodeVariant[i]; ++i) {
               switch (disassyCodeVariant[i]) {
                       case '%':
                               disassyCodeVariant[i] = figureNumberVariant[0];
                               break;
                       default:
                               break;
               }
       }

       return disassyCodeVariant;
}

/* Parse an old (< 4.1) style CSN reference.
*
* refcsn (2.0-3.0)/catalogSeqNumberValue (4.0) is a 13-16 digit code:
*
* 13:  YY|Y|Y|  YY|YY|Y|NNN|Y (2-character system, 2-character assembly)
* 14: YYY|Y|Y|  YY|YY|Y|NNN|Y (3-character system, 2-character assembly)
* 15:  YY|Y|Y|YYYY|YY|Y|NNN|Y (2-character system, 4-character assembly)
* 16: YYY|Y|Y|YYYY|YY|Y|NNN|Y (3-character system, 4-character assembly)
*
* Y = [A-Z0-9 ] (alphanumeric + space)
* N = [0-9]     (numeric)
*/
#define CSN_VALUE_PATTERN_16 "%3[A-Z0-9 ]%1[A-Z0-9 ]%1[A-Z0-9 ]%4[A-Z0-9 ]%2[A-Z0-9]%1[A-Z0-9 ]%3[0-9]%1[A-Z0-9 ]"
#define CSN_VALUE_PATTERN_15 "%2[A-Z0-9 ]%1[A-Z0-9 ]%1[A-Z0-9 ]%4[A-Z0-9 ]%2[A-Z0-9]%1[A-Z0-9 ]%3[0-9]%1[A-Z0-9 ]"
#define CSN_VALUE_PATTERN_14 "%3[A-Z0-9 ]%1[A-Z0-9 ]%1[A-Z0-9 ]%2[A-Z0-9 ]%2[A-Z0-9]%1[A-Z0-9 ]%3[0-9]%1[A-Z0-9 ]"
#define CSN_VALUE_PATTERN_13 "%2[A-Z0-9 ]%1[A-Z0-9 ]%1[A-Z0-9 ]%2[A-Z0-9 ]%2[A-Z0-9]%1[A-Z0-9 ]%3[0-9]%1[A-Z0-9 ]"

static int str_is_blank(const char *s) {
       int i;
       for (i = 0; s[i]; ++i) {
               if (!isspace((unsigned char) s[i])) {
                       return 0;
               }
       }
       return 1;
}

static void parseCsnValue(const xmlChar *csnValue,
       xmlChar **systemCode,
       xmlChar **subSystemCode,
       xmlChar **subSubSystemCode,
       xmlChar **assyCode,
       xmlChar **figureNumber,
       xmlChar **figureNumberVariant,
       xmlChar **item,
       xmlChar **itemVariant)
{
       char system[4];
       char subsys[2];
       char subsub[2];
       char assemb[5];
       char fignum[3];
       char figvar[2];
       char itemno[4];
       char itemva[2];
       const char *pattern;

       switch (xmlStrlen(csnValue)) {
               case 16: pattern = CSN_VALUE_PATTERN_16; break;
               case 15: pattern = CSN_VALUE_PATTERN_15; break;
               case 14: pattern = CSN_VALUE_PATTERN_14; break;
               case 13: pattern = CSN_VALUE_PATTERN_13; break;
               default: pattern = NULL; break;
       }

       if (pattern && sscanf((char *) csnValue, pattern,
                       system,
                       subsys,
                       subsub,
                       assemb,
                       fignum,
                       figvar,
                       itemno,
                       itemva) == 8) {
               *systemCode          = str_is_blank(system) ? NULL : xmlCharStrdup(system);
               *subSystemCode       = str_is_blank(subsys) ? NULL : xmlCharStrdup(subsys);
               *subSubSystemCode    = str_is_blank(subsub) ? NULL : xmlCharStrdup(subsub);
               *assyCode            = str_is_blank(assemb) ? NULL : xmlCharStrdup(assemb);
               *figureNumber        = xmlCharStrdup(fignum);
               *figureNumberVariant = str_is_blank(figvar) ? NULL : xmlCharStrdup(figvar);
               *item                = xmlCharStrdup(itemno);
               *itemVariant         = str_is_blank(itemva) ? NULL : xmlCharStrdup(itemva);
       } else {
               *systemCode          = NULL;
               *subSystemCode       = NULL;
               *subSubSystemCode    = NULL;
               *assyCode            = NULL;
               *figureNumber        = NULL;
               *figureNumberVariant = NULL;
               *item                = NULL;
               *itemVariant         = NULL;
       }
}

/* Get the code of a CSN ref, including IPD data module code, CSN and item number. */
static void getCsnCode(char *dst, xmlNodePtr ref, xmlChar **csnValue, xmlChar **item, xmlChar **itemVariant)
{
       xmlChar *modelIdentCode;
       xmlChar *systemDiffCode;
       xmlChar *systemCode;
       xmlChar *subSystemCode;
       xmlChar *subSubSystemCode;
       xmlChar *assyCode;
       xmlChar *figureNumber;
       xmlChar *figureNumberVariant;
       xmlChar *itemLocationCode;

       *csnValue = firstXPathValue(NULL, ref, BAD_CAST "@catalogSeqNumberValue|@refcsn");

       if (*csnValue) {
               modelIdentCode = NULL;
               systemDiffCode = NULL;
               itemLocationCode = NULL;

               parseCsnValue(*csnValue,
                       &systemCode,
                       &subSystemCode,
                       &subSubSystemCode,
                       &assyCode,
                       &figureNumber,
                       &figureNumberVariant,
                       item,
                       itemVariant);
       } else {
               modelIdentCode      = xmlGetProp(ref, BAD_CAST "modelIdentCode");
               systemDiffCode      = xmlGetProp(ref, BAD_CAST "systemDiffCode");
               systemCode          = xmlGetProp(ref, BAD_CAST "systemCode");
               subSystemCode       = xmlGetProp(ref, BAD_CAST "subSystemCode");
               subSubSystemCode    = xmlGetProp(ref, BAD_CAST "subSubSystemCode");
               assyCode            = xmlGetProp(ref, BAD_CAST "assyCode");
               figureNumber        = xmlGetProp(ref, BAD_CAST "figureNumber");
               figureNumberVariant = xmlGetProp(ref, BAD_CAST "figureNumberVariant");
               itemLocationCode    = xmlGetProp(ref, BAD_CAST "itemLocationCode");

               *item               = xmlGetProp(ref, BAD_CAST "item");
               *itemVariant        = xmlGetProp(ref, BAD_CAST "itemVariant");
       }

       /* Apply attributes to non-chapterized or old style CSN refs. */
       if (nonChapIpdSns || *csnValue) {
               xmlNodePtr dmCode = firstXPathNode(NULL, ref, BAD_CAST "ancestor::dmodule/identAndStatusSection/dmAddress/dmIdent/dmCode|ancestor::dmodule/idstatus/dmaddres/dmc/avee");

               if (dmCode) {
                       /* These attributes are always interpreted as relative to the current DM. */
                       if (!modelIdentCode) {
                               modelIdentCode = firstXPathValue(NULL, dmCode, BAD_CAST "@modelIdentCode|modelic");
                       }
                       if (!systemDiffCode) {
                               systemDiffCode = firstXPathValue(NULL, dmCode, BAD_CAST "@systemDiffCode|sdc");
                       }

                       /* Use wildcard for itemLocationCode if not given. */
                       if (!itemLocationCode) {
                               itemLocationCode = xmlCharStrdup("?");
                       }

                       /* If a non-chapterized IPD SNS is given, apply it. */
                       if (nonChapIpdSns) {
                               /* "-" indicates the SNS is also relative to the current DM. */
                               if (strcmp(nonChapIpdSystemCode, "-") == 0) {
                                       if (!systemCode) {
                                               systemCode = firstXPathValue(NULL, dmCode,
                                                       BAD_CAST "@systemCode|chapnum");
                                       }
                                       if (!subSystemCode) {
                                               subSystemCode = firstXPathValue(NULL, dmCode,
                                                       BAD_CAST "@subSystemCode|section");
                                       }
                                       if (!subSubSystemCode) {
                                               subSubSystemCode = firstXPathValue(NULL, dmCode,
                                                       BAD_CAST "@subSubSystemCode|subsect");
                                       }
                                       if (!assyCode) {
                                               assyCode = firstXPathValue(NULL, dmCode,
                                                       BAD_CAST "@assyCode|subject");
                                       }
                               /* Otherwise, construct the SNS from the given code. */
                               } else {
                                       if (!systemCode) {
                                               systemCode = xmlCharStrdup(nonChapIpdSystemCode);
                                       }
                                       if (!subSystemCode) {
                                               subSystemCode = xmlCharStrdup(nonChapIpdSubSystemCode);
                                       }
                                       if (!subSubSystemCode) {
                                               subSubSystemCode = xmlCharStrdup(nonChapIpdSubSubSystemCode);
                                       }
                                       if (!assyCode) {
                                               assyCode = xmlCharStrdup(nonChapIpdAssyCode);
                                       }
                               }
                       }
               }
       }

       /* If CSN is chapterized, attempt to match it to a DMC. */
       if (modelIdentCode && systemDiffCode && systemCode && subSystemCode && subSubSystemCode && assyCode && figureNumber) {
               xmlDocPtr tmp;
               xmlNodePtr dmRef, dmRefIdent, dmCode;
               xmlChar *disassyCodeVariant;

               tmp = xmlNewDoc(BAD_CAST "1.0");

               dmRef = xmlNewNode(NULL, BAD_CAST "dmRef");
               dmRefIdent = xmlNewChild(dmRef, NULL, BAD_CAST "dmRefIdent", NULL);
               dmCode = xmlNewChild(dmRefIdent, NULL, BAD_CAST "dmCode", NULL);

               xmlDocSetRootElement(tmp, dmRef);

               if (!figureNumberVariant) {
                       figureNumberVariant = xmlCharStrdup("0");
               }

               /* The figure number variant alone cannot fully determine the
                * disassembly code variant of the IPD data module (for example,
                * in projects where the disassembly code variant is more than 1
                * character). Therefore, the figNumVarFormat pattern is used to
                * construct the full disassemby code variant. */
               disassyCodeVariant = formatFigNumVar(figureNumberVariant);

               if (!itemLocationCode) {
                       itemLocationCode = xmlCharStrdup("?");
               }

               xmlSetProp(dmCode, BAD_CAST "modelIdentCode", modelIdentCode);
               xmlSetProp(dmCode, BAD_CAST "systemDiffCode", systemDiffCode);
               xmlSetProp(dmCode, BAD_CAST "systemCode", systemCode);
               xmlSetProp(dmCode, BAD_CAST "subSystemCode", subSystemCode);
               xmlSetProp(dmCode, BAD_CAST "subSubSystemCode", subSubSystemCode);
               xmlSetProp(dmCode, BAD_CAST "assyCode", assyCode);
               xmlSetProp(dmCode, BAD_CAST "disassyCode", figureNumber);
               xmlSetProp(dmCode, BAD_CAST "disassyCodeVariant", disassyCodeVariant);
               xmlSetProp(dmCode, BAD_CAST "infoCode", BAD_CAST "941");
               xmlSetProp(dmCode, BAD_CAST "infoCodeVariant", BAD_CAST "A");
               xmlSetProp(dmCode, BAD_CAST "itemLocationCode", itemLocationCode);

               getDmCode(dst, dmRef);

               xmlFree(disassyCodeVariant);
               xmlFreeDoc(tmp);
       /* Otherwise, just return a generic IPD figure name. */
       } else {
               strcpy(dst, "Fig ");
               if (figureNumber) {
                       strcat(dst, (char *) figureNumber);
               } else {
                       strcat(dst, "??");
               }
               if (figureNumberVariant) {
                       strcat(dst, (char *) figureNumberVariant);
               }
       }

       xmlFree(modelIdentCode);
       xmlFree(systemDiffCode);
       xmlFree(systemCode);
       xmlFree(subSystemCode);
       xmlFree(subSubSystemCode);
       xmlFree(assyCode);
       xmlFree(figureNumber);
       xmlFree(figureNumberVariant);
       xmlFree(itemLocationCode);
}

/* Get the code of an IPD data module only, discarding item number. */
static void getIpdCode(char *dst, xmlNodePtr ref)
{
       xmlChar *csn;
       xmlChar *item;
       xmlChar *itemVariant;

       getCsnCode(dst, ref, &csn, &item, &itemVariant);

       xmlFree(csn);
       xmlFree(item);
       xmlFree(itemVariant);
}

/* Match a CSN item in an IPD. */
static int matchCsnItem(xmlDocPtr doc, xmlNodePtr ref, xmlChar *csn,
       xmlChar *item, xmlChar *itemVariant, const char *code,
       const char *fname, const char *src)
{
       xmlChar *itemSeqNumberValue, *id;
       xmlXPathContextPtr ctx;
       xmlXPathObjectPtr obj;
       xmlNodePtr node;
       char *s;
       int err = doc == NULL;

       itemSeqNumberValue = firstXPathValue(NULL, ref, BAD_CAST "@itemSeqNumberValue|@refisn");

       id = xmlCharStrdup("Item ");
       id = xmlStrcat(id, item);
       id = xmlStrcat(id, itemVariant);
       if (itemSeqNumberValue) {
               id = xmlStrcat(id, BAD_CAST " ISN ");
               id = xmlStrcat(id, itemSeqNumberValue);
       }

       if (doc) {
               ctx = xmlXPathNewContext(doc);
               xmlXPathRegisterVariable(ctx, BAD_CAST "item", xmlXPathNewString(item));
               xmlXPathRegisterVariable(ctx, BAD_CAST "itemVariant", xmlXPathNewString(itemVariant));
               xmlXPathRegisterVariable(ctx, BAD_CAST "csn", xmlXPathNewString(csn));

               if (itemSeqNumberValue) {
                       xmlXPathRegisterVariable(ctx, BAD_CAST "isn", xmlXPathNewString(itemSeqNumberValue));
                       obj = xmlXPathEvalExpression(BAD_CAST
                               /* 4.1+ */ "//catalogSeqNumber[@item=$item and (not(@itemVariant) or not($itemVariant) or @itemVariant=$itemVariant)]/itemSeqNumber[@itemSeqNumberValue=$isn]|"
                               /* 4.0  */ "//catalogSeqNumber[@catalogSeqNumberValue=$csn]/itemSequenceNumber[@itemSeqNumberValue=$isn]|"
                               /* 3.0- */ "//csn[@csn=$csn]/isn[@isn=$isn]",
                               ctx);
               } else {
                       obj = xmlXPathEvalExpression(BAD_CAST
                               /* 4.1+ */ "//catalogSeqNumber[@item=$item and (not(@itemVariant) or not($itemVariant) or @itemVariant=$itemVariant)]|"
                               /* 4.0  */ "//catalogSeqNumber[@catalogSeqNumberValue=$csn]|"
                               /* 3.0- */ "//csn[@csn=$csn]",
                               ctx);
               }

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

               xmlXPathFreeObject(obj);
               xmlXPathFreeContext(ctx);

               if (node) {
                       if (showMatched && !tagUnmatched) {
                               s = malloc(strlen(fname) + strlen((char *) id) + 2);

                               strcpy(s, fname);
                               strcat(s, " ");
                               strcat(s, (char *) id);

                               printMatchedFn(ref, src, s, fname);

                               free(s);
                       }
               } else {
                       ++err;
               }
       }

       if (err) {
               s = malloc(strlen(doc ? fname : code) + strlen((char *) id) + 2);
               strcpy(s, doc ? fname : code);
               strcat(s, " ");
               strcat(s, (char *) id);

               if (tagUnmatched) {
                       tagUnmatchedRef(ref);
               } else if (showUnmatched) {
                       printMatchedFn(ref, src, s, doc ? fname : NULL);
               } else if (verbosity >= NORMAL) {
                       printUnmatchedFn(ref, src, s, doc ? fname : NULL);
               }

               free(s);
       }

       xmlFree(itemSeqNumberValue);
       xmlFree(id);

       return err;

}

/* Match the CSN items in another DM. */
static int getCsnItem(xmlNodePtr ref, const char *src)
{
       xmlNodePtr csnref;
       char code[PATH_MAX], fname[PATH_MAX];
       xmlDocPtr doc;
       int err;
       xmlChar *csn;
       xmlChar *item;
       xmlChar *itemVariant;

       csnref = ref->parent;

       getCsnCode(code, csnref, &csn, &item, &itemVariant);

       if (find_object_fname(fname, directory, code, recursive)) {
               doc = read_xml_doc(fname);
       } else {
               doc = NULL;
       }

       if (remDelete) {
               rem_delete_elems(doc);
       }

       err = matchCsnItem(doc, csnref, csn, item, itemVariant, code, doc ? fname : code, src);

       xmlFree(csn);
       xmlFree(item);
       xmlFree(itemVariant);

       xmlFreeDoc(doc);

       return err;
}

/* Update address items using the matched referenced object. */
static void updateRef(xmlNodePtr *refptr, const char *src, const char *code, const char *fname)
{
       xmlNodePtr ref = *refptr;

       if (verbosity >= DEBUG) {
               fprintf(stderr, I_UPDATE_REF, src, code, fname);
       }

       if (xmlStrcmp(ref->name, BAD_CAST "dmRef") == 0) {
               xmlDocPtr doc;
               xmlNodePtr dmRefAddressItems, dmTitle;
               xmlChar *techName, *infoName, *infoNameVariant;

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

               if (updateRefIdent) {
                       xmlNodePtr dmRefIdent, refIssueInfo, refLanguage, issueInfo, language;

                       dmRefIdent   = firstXPathNode(NULL, ref, BAD_CAST "dmRefIdent");
                       refIssueInfo = firstXPathNode(NULL, dmRefIdent, BAD_CAST "issueInfo");
                       refLanguage  = firstXPathNode(NULL, dmRefIdent, BAD_CAST "language");

                       issueInfo = xmlCopyNode(firstXPathNode(doc, NULL, BAD_CAST "//issueInfo|//issno"), 1);
                       language  = xmlCopyNode(firstXPathNode(doc, NULL, BAD_CAST "//language"), 1);

                       /* 4.x references a 3.0 DM */
                       if (xmlStrcmp(issueInfo->name, BAD_CAST "issno") == 0) {
                               xmlNodeSetName(issueInfo, BAD_CAST "issueInfo");
                               xmlNodeSetName((xmlNodePtr) xmlHasProp(issueInfo,
                                       BAD_CAST "issno"),
                                       BAD_CAST "issueNumber");
                               if (xmlHasProp(issueInfo, BAD_CAST "inwork")) {
                                       xmlNodeSetName((xmlNodePtr) xmlHasProp(issueInfo,
                                               BAD_CAST "inwork"),
                                               BAD_CAST "inWork");
                               } else {
                                       xmlSetProp(issueInfo, BAD_CAST "inWork", BAD_CAST "00");
                               }
                               xmlUnsetProp(issueInfo, BAD_CAST "type");
                               xmlNodeSetName((xmlNodePtr) xmlHasProp(language,
                                       BAD_CAST "language"),
                                       BAD_CAST "languageIsoCode");
                               xmlNodeSetName((xmlNodePtr) xmlHasProp(language,
                                       BAD_CAST "country"),
                                       BAD_CAST "countryIsoCode");
                       }

                       if (refIssueInfo) {
                               xmlUnlinkNode(refIssueInfo);
                               xmlFreeNode(refIssueInfo);
                       }
                       if (refLanguage) {
                               xmlUnlinkNode(refLanguage);
                               xmlFreeNode(refLanguage);
                       }

                       xmlAddChild(dmRefIdent, issueInfo);
                       xmlAddChild(dmRefIdent, language);
               }

               if ((dmRefAddressItems = firstXPathNode(NULL, ref, BAD_CAST "dmRefAddressItems"))) {
                       xmlUnlinkNode(dmRefAddressItems);
                       xmlFreeNode(dmRefAddressItems);
               }
               dmRefAddressItems = xmlNewChild(ref, NULL, BAD_CAST "dmRefAddressItems", NULL);

               techName = firstXPathValue(doc, NULL, BAD_CAST "//techName|//techname");
               infoName = firstXPathValue(doc, NULL, BAD_CAST "//infoName|//infoname");
               infoNameVariant = firstXPathValue(doc, NULL, BAD_CAST "//infoNameVariant");

               dmTitle = xmlNewChild(dmRefAddressItems, NULL, BAD_CAST "dmTitle", NULL);
               xmlNewTextChild(dmTitle, NULL, BAD_CAST "techName", techName);
               if (infoName) {
                       xmlNewTextChild(dmTitle, NULL, BAD_CAST "infoName", infoName);
               }
               if (infoNameVariant) {
                       xmlNewTextChild(dmTitle, NULL, BAD_CAST "infoNameVariant", infoNameVariant);
               }

               xmlFree(techName);
               xmlFree(infoName);

               if (updateRefIdent) {
                       xmlNodePtr issueDate;

                       issueDate = xmlCopyNode(firstXPathNode(doc, NULL, BAD_CAST "//issueDate|//issdate"), 1);

                       if (xmlStrcmp(issueDate->name, BAD_CAST "issdate")) {
                               xmlNodeSetName(issueDate, BAD_CAST "issueDate");
                       }

                       xmlAddChild(dmRefAddressItems, issueDate);
               }

               xmlFreeDoc(doc);
       } else if (xmlStrcmp(ref->name, BAD_CAST "pmRef") == 0) {
               xmlDocPtr doc;
               xmlNodePtr pmRefAddressItems;
               xmlChar *pmTitle;

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

               if (updateRefIdent) {
                       xmlNodePtr pmRefIdent, refIssueInfo, refLanguage, issueInfo, language;

                       pmRefIdent   = firstXPathNode(NULL, ref, BAD_CAST "pmRefIdent");
                       refIssueInfo = firstXPathNode(NULL, pmRefIdent, BAD_CAST "issueInfo");
                       refLanguage  = firstXPathNode(NULL, pmRefIdent, BAD_CAST "language");
                       issueInfo    = xmlCopyNode(firstXPathNode(doc, NULL, BAD_CAST "//issueInfo|//issno"), 1);
                       language     = xmlCopyNode(firstXPathNode(doc, NULL, BAD_CAST "//language"), 1);

                       /* 4.x references a 3.0 DM */
                       if (xmlStrcmp(issueInfo->name, BAD_CAST "issno") == 0) {
                               xmlNodeSetName(issueInfo, BAD_CAST "issueInfo");
                               xmlNodeSetName((xmlNodePtr) xmlHasProp(issueInfo,
                                       BAD_CAST "issno"),
                                       BAD_CAST "issueNumber");
                               if (xmlHasProp(issueInfo, BAD_CAST "inwork")) {
                                       xmlNodeSetName((xmlNodePtr) xmlHasProp(issueInfo,
                                               BAD_CAST "inwork"),
                                               BAD_CAST "inWork");
                               } else {
                                       xmlSetProp(issueInfo, BAD_CAST "inWork", BAD_CAST "00");
                               }
                               xmlUnsetProp(issueInfo, BAD_CAST "type");
                               xmlNodeSetName((xmlNodePtr) xmlHasProp(language,
                                       BAD_CAST "language"),
                                       BAD_CAST "languageIsoCode");
                               xmlNodeSetName((xmlNodePtr) xmlHasProp(language,
                                       BAD_CAST "country"),
                                       BAD_CAST "countryIsoCode");
                       }

                       if (refIssueInfo) {
                               xmlUnlinkNode(refIssueInfo);
                               xmlFreeNode(refIssueInfo);
                       }
                       if (refLanguage) {
                               xmlUnlinkNode(refLanguage);
                               xmlFreeNode(refLanguage);
                       }

                       xmlAddChild(pmRefIdent, issueInfo);
                       xmlAddChild(pmRefIdent, language);
               }

               if ((pmRefAddressItems = firstXPathNode(NULL, ref, BAD_CAST "pmRefAddressItems"))) {
                       xmlUnlinkNode(pmRefAddressItems);
                       xmlFreeNode(pmRefAddressItems);
               }
               pmRefAddressItems = xmlNewChild(ref, NULL, BAD_CAST "pmRefAddressItems", NULL);

               pmTitle = firstXPathValue(doc, NULL, BAD_CAST "//pmTitle|//pmtitle");

               xmlNewTextChild(pmRefAddressItems, NULL, BAD_CAST "pmTitle", pmTitle);

               xmlFree(pmTitle);

               if (updateRefIdent) {
                       xmlNodePtr issueDate;

                       issueDate = xmlCopyNode(firstXPathNode(doc, NULL, BAD_CAST "//issueDate|//issdate"), 1);

                       if (xmlStrcmp(issueDate->name, BAD_CAST "issdate")) {
                               xmlNodeSetName(issueDate, BAD_CAST "issueDate");
                       }

                       xmlAddChild(pmRefAddressItems, issueDate);
               }

               xmlFreeDoc(doc);
       } else if (xmlStrcmp(ref->name, BAD_CAST "refdm") == 0) {
               xmlDocPtr doc;
               xmlNodePtr oldtitle, newtitle;
               xmlChar *techname, *infoname;

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

               if (updateRefIdent) {
                       xmlNodePtr oldissno, newissno, oldlanguage, newlanguage;

                       newissno    = xmlCopyNode(firstXPathNode(doc, NULL, BAD_CAST "//issueInfo|//issno"), 1);
                       newlanguage = xmlCopyNode(firstXPathNode(doc, NULL, BAD_CAST "//language"), 1);

                       /* 3.0 references a 4.x DM */
                       if (xmlStrcmp(newissno->name, BAD_CAST "issueInfo") == 0) {
                               xmlNodeSetName(newissno, BAD_CAST "issno");
                               xmlNodeSetName((xmlNodePtr) xmlHasProp(newissno,
                                       BAD_CAST "issueNumber"),
                                       BAD_CAST "issno");
                               xmlNodeSetName((xmlNodePtr) xmlHasProp(newissno,
                                       BAD_CAST "inWork"),
                                       BAD_CAST "inwork");
                               xmlNodeSetName((xmlNodePtr) xmlHasProp(newlanguage,
                                       BAD_CAST "languageIsoCode"),
                                       BAD_CAST "language");
                               xmlNodeSetName((xmlNodePtr) xmlHasProp(newlanguage,
                                       BAD_CAST "countryIsoCode"),
                                       BAD_CAST "country");
                       }

                       if ((oldissno = firstXPathNode(NULL, ref, BAD_CAST "issno"))) {
                               newissno = xmlAddNextSibling(oldissno, newissno);
                       } else {
                               newissno = xmlAddNextSibling(firstXPathNode(NULL, ref, BAD_CAST "avee"), newissno);
                       }
                       xmlUnlinkNode(oldissno);
                       xmlFreeNode(oldissno);

                       if ((oldlanguage = firstXPathNode(NULL, ref, BAD_CAST "language"))) {
                               newlanguage = xmlAddNextSibling(oldlanguage, newlanguage);
                       } else {
                               newlanguage = xmlAddNextSibling(firstXPathNode(NULL, ref, BAD_CAST "issno"), newlanguage);
                       }
                       xmlUnlinkNode(oldlanguage);
                       xmlFreeNode(oldlanguage);
               }

               techname = firstXPathValue(doc, NULL, BAD_CAST "//techName|//techname");
               infoname = firstXPathValue(doc, NULL, BAD_CAST "//infoName|//infoname");

               newtitle = xmlNewNode(NULL, BAD_CAST "dmtitle");

               if ((oldtitle = firstXPathNode(NULL, ref, BAD_CAST "dmtitle"))) {
                       newtitle = xmlAddNextSibling(oldtitle, newtitle);
               } else {
                       newtitle = xmlAddNextSibling(firstXPathNode(NULL, ref, BAD_CAST "(avee|issno)[last()]"), newtitle);
               }

               xmlNewTextChild(newtitle, NULL, BAD_CAST "techname", techname);
               if (infoname) {
                       xmlNewTextChild(newtitle, NULL, BAD_CAST "infoname", infoname);
               }

               xmlFree(techname);
               xmlFree(infoname);

               xmlUnlinkNode(oldtitle);
               xmlFreeNode(oldtitle);
               xmlFreeDoc(doc);
       } else if (xmlStrcmp(ref->name, BAD_CAST "infoEntityIdent") == 0) {
               xmlChar *icn;
               xmlEntityPtr e;

               /* Remove old ICN entity. */
               icn = xmlNodeGetContent(ref);
               if ((e = xmlGetDocEntity(ref->doc, icn))) {
                       xmlUnlinkNode((xmlNodePtr) e);
                       xmlFreeEntity(e);
               }
               xmlFree(icn);

               /* Add new ICN entity. */
               e = add_icn(ref->doc, fname, false);
               xmlNodeSetContent(ref, e->name);
       } else if (xmlStrcmp(ref->name, BAD_CAST "externalPubRef") == 0) {
               xmlNodePtr new;
               xmlChar xpath[512];

               xmlStrPrintf(xpath, 512, "//externalPubRef[externalPubRefIdent/externalPubCode='%s']", code);

               if (!(new = firstXPathNode(externalPubs, NULL, xpath))) {
                       return;
               }

               xmlAddNextSibling(ref, xmlCopyNode(new, 1));

               xmlUnlinkNode(ref);
               xmlFreeNode(ref);
               *refptr = NULL;
       }
}

static int listReferences(const char *path, int show, const char *targetRef, int targetShow);
static int listWhereUsed(const char *path, int show);

/* Print a reference found in an object. */
static int printReference(xmlNodePtr *refptr, const char *src, int show, const char *targetRef, int targetShow)
{
       char code[PATH_MAX];
       char fname[PATH_MAX];
       xmlNodePtr ref = *refptr;

       if ((show & SHOW_DMC) == SHOW_DMC &&
           (xmlStrcmp(ref->name, BAD_CAST "dmRef") == 0 ||
            xmlStrcmp(ref->name, BAD_CAST "refdm") == 0 ||
            xmlStrcmp(ref->name, BAD_CAST "addresdm") == 0))
               getDmCode(code, ref);
       else if ((show & SHOW_PMC) == SHOW_PMC &&
                (xmlStrcmp(ref->name, BAD_CAST "pmRef") == 0 ||
                 xmlStrcmp(ref->name, BAD_CAST "refpm") == 0))
               getPmCode(code, ref);
       else if ((show & SHOW_SMC) == SHOW_SMC && xmlStrcmp(ref->name, BAD_CAST "scormContentPackageRef") == 0)
               getSmcCode(code, ref);
       else if ((show & SHOW_ICN) == SHOW_ICN &&
                (xmlStrcmp(ref->name, BAD_CAST "infoEntityRef") == 0))
               getICN(code, ref);
       else if ((show & SHOW_COM) == SHOW_COM && (xmlStrcmp(ref->name, BAD_CAST "commentRef") == 0))
               getComCode(code, ref);
       else if ((show & SHOW_DML) == SHOW_DML && (xmlStrcmp(ref->name, BAD_CAST "dmlRef") == 0))
               getDmlCode(code, ref);
       else if ((show & SHOW_ICN) == SHOW_ICN &&
                (xmlStrcmp(ref->name, BAD_CAST "infoEntityIdent") == 0 ||
                 xmlStrcmp(ref->name, BAD_CAST "boardno") == 0))
               getICNAttr(code, ref);
       else if ((show & SHOW_EPR) == SHOW_EPR &&
                (xmlStrcmp(ref->name, BAD_CAST "externalPubRef") == 0 ||
                 xmlStrcmp(ref->name, BAD_CAST "reftp") == 0))
               getExternalPubCode(code, ref);
       else if (xmlStrcmp(ref->name, BAD_CAST "dispatchFileName") == 0 ||
                xmlStrcmp(ref->name, BAD_CAST "ddnfilen") == 0)
               getDispatchFileName(code, ref);
       else if ((show & SHOW_SRC) == SHOW_SRC &&
               (xmlStrcmp(ref->name, BAD_CAST "sourceDmIdent") == 0 ||
                xmlStrcmp(ref->name, BAD_CAST "sourcePmIdent") == 0))
               getSourceIdent(code, ref);
       else if ((show & SHOW_REP) == SHOW_REP &&
               xmlStrcmp(ref->name, BAD_CAST "repositorySourceDmIdent") == 0)
               getSourceIdent(code, ref);
       else if ((show & SHOW_HOT) == SHOW_HOT &&
                xmlStrcmp(ref->name, BAD_CAST "graphic") == 0)
               return getHotspots(ref, src);
       else if ((show & SHOW_FRG) == SHOW_FRG &&
                (xmlStrcmp(ref->name, BAD_CAST "referredFragment") == 0 ||
                 xmlStrcmp(ref->name, BAD_CAST "target") == 0))
               return getFragment(ref, src);
       else if ((show & SHOW_IPD) == SHOW_IPD &&
                (xmlStrcmp(ref->name, BAD_CAST "catalogSeqNumberRef") == 0 ||
                 xmlStrcmp(ref->name, BAD_CAST "csnref") == 0))
               getIpdCode(code, ref);
       else if ((show & SHOW_CSN) == SHOW_CSN &&
                (xmlStrcmp(ref->name, BAD_CAST "item") == 0 ||
                 xmlStrcmp(ref->name, BAD_CAST "catalogSeqNumberValue") == 0 ||
                 xmlStrcmp(ref->name, BAD_CAST "refcsn") == 0))
               return getCsnItem(ref, src);
       else
               return 0;

       if (targetRef) {
               /* If looking for a particular ref in -w mode, skip any others. */
               if (!strnmatch(targetRef, code, strlen(code))) {
                       return 0;
               }

               /* Replace the code with the target ref so as to match that
                * specific object rather than the latest object with the same
                * code. */
               strcpy(code, targetRef);
       }

       if (find_object_fname(fname, directory, code, recursive)) {
               if (updateRefs) {
                       updateRef(refptr, src, code, fname);
               } else if (!tagUnmatched) {
                       if (showMatched) {
                               printMatchedFn(ref, src, fname, fname);
                       }

                       if (listRecursively) {
                               if (targetRef) {
                                       listWhereUsed(src, targetShow);
                               } else {
                                       listReferences(fname, show, NULL, 0);
                               }
                       }
               }
               return 0;
       } else if (tagUnmatched) {
               tagUnmatchedRef(ref);
       } else if (showUnmatched) {
               printMatchedFn(ref, src, code, NULL);
       } else if (verbosity >= NORMAL) {
               printUnmatchedFn(ref, src, code, NULL);
       }

       /* Update metadata for unmatched external pubs. */
       if (updateRefs && externalPubs && xmlStrcmp(ref->name, BAD_CAST "externalPubRef") == 0) {
               updateRef(refptr, src, code, fname);
       }

       return 1;
}

/* Check if a file has already been listed when listing recursively. */
static bool listedFile(const char *path)
{
       int i;
       for (i = 0; i < numListedFiles; ++i) {
               if (strcmp(listedFiles[i], path) == 0) {
                       return true;
               }
       }
       return false;
}

/* Add a file to the list of files already checked. */
static void addFile(const char *path)
{
       if (!listedFiles || numListedFiles == maxListedFiles) {
               if (!(listedFiles = realloc(listedFiles, (maxListedFiles *= 2) * PATH_MAX))) {
                       fprintf(stderr, E_OUT_OF_MEMORY);
                       exit(EXIT_OUT_OF_MEMORY);
               }
       }

       strcpy(listedFiles[numListedFiles++], path);
}

/* XPath to select all possible types of references. */
#define REFS_XPATH BAD_CAST \
       ".//dmRef|.//refdm|.//addresdm|" \
       ".//pmRef|.//refpm|" \
       ".//infoEntityRef|//@infoEntityIdent|//@boardno|" \
       ".//commentRef|" \
       ".//dmlRef|" \
       ".//externalPubRef|.//reftp|" \
       ".//dispatchFileName|.//ddnfilen|" \
       ".//graphic[hotspot]|" \
       ".//dmRef/@referredFragment|.//refdm/@target|" \
       ".//scormContentPackageRef|" \
       ".//sourceDmIdent|.//sourcePmIdent|.//repositorySourceDmIdent|" \
       ".//catalogSeqNumberRef|.//csnref|" \
       ".//catalogSeqNumberRef/@item|.//catalogSeqNumberRef/@catalogSeqNumberValue|.//@refcsn"

/* List all references in the given object. */
static int listReferences(const char *path, int show, const char *targetRef, int targetShow)
{
       xmlDocPtr doc;
       xmlXPathContextPtr ctx;
       xmlXPathObjectPtr obj;
       int unmatched = 0;
       xmlDocPtr validTree = NULL;

       /* In recursive mode, keep a record of which files have been listed
        * to avoid infinite loops.
        *
        * If this is invoked in -w mode (targetRef != NULL), don't update the
        * record, as that is handled by listWhereUsed.
        */
       if (listRecursively && targetRef == NULL) {
               if (listedFile(path)) {
                       return 0;
               }

               addFile(path);
       }

       if (listSrc) {
               printMatchedFn(NULL, path, path, path);
       }

       if (!(doc = read_xml_doc(path))) {
               if (strcmp(path, "-") == 0) {
                       fprintf(stderr, E_BAD_STDIN);
                       exit(EXIT_BAD_STDIN);
               }

               return 0;
       }

       /* Make a copy of the XML tree before performing extra
        * processing on it. */
       if (outputTree) {
               validTree = xmlCopyDoc(doc, 1);
       }

       /* Remove elements marked as "delete". */
       if (remDelete) {
               rem_delete_elems(doc);
       }

       ctx = xmlXPathNewContext(doc);

       if (contentOnly)
               ctx->node = firstXPathNode(doc, NULL,
                       BAD_CAST "//content|//dmlContent|//dml|//ddnContent|//delivlst");
       else
               ctx->node = xmlDocGetRootElement(doc);

       obj = xmlXPathEvalExpression(REFS_XPATH, ctx);

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

               for (i = 0; i < obj->nodesetval->nodeNr; ++i) {
                       unmatched += printReference(&(obj->nodesetval->nodeTab[i]), path, show, targetRef, targetShow);
               }
       }

       /* Write valid CSDB object to stdout. */
       if (outputTree) {
               if (unmatched == 0) {
                       save_xml_doc(validTree, "-");
               }
               xmlFreeDoc(validTree);
       }

       /* If the given object was modified by updating matched refs or
        * tagging unmatched refs, write the changes.
        */
       if (updateRefs || tagUnmatched) {
               if (overwriteUpdated) {
                       save_xml_doc(doc, path);
               } else {
                       save_xml_doc(doc, "-");
               }
       }

       xmlXPathFreeObject(obj);
       xmlXPathFreeContext(ctx);
       xmlFreeDoc(doc);

       if (verbosity >= VERBOSE && !targetRef) {
               fprintf(stderr, unmatched ? F_UNMATCHED : S_UNMATCHED, path);
       }

       return unmatched;
}

/* Parse a list of filenames as input. */
static int listReferencesInList(const char *path, int show)
{
       FILE *f;
       char line[PATH_MAX];
       int unmatched = 0;

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

       while (fgets(line, PATH_MAX, f)) {
               strtok(line, "\t\r\n");
               unmatched += listReferences(line, show, NULL, 0);
       }

       if (path) {
               fclose(f);
       }

       return unmatched;
}

/* Register a NS for the hotspot XPath expression. */
static void addHotspotNs(char *s)
{
       char *prefix, *uri;
       xmlNodePtr node;

       prefix = strtok(s, "=");
       uri = strtok(NULL, "");

       node = xmlNewChild(hotspotNs, NULL, BAD_CAST "ns", NULL);
       xmlSetProp(node, BAD_CAST "prefix", BAD_CAST prefix);
       xmlSetProp(node, BAD_CAST "uri", BAD_CAST uri);
}

/* Determine if an object is a type that may contain references to other
* objects. */
static bool isUsedTarget(const char *name, int show)
{
       return
               (optset(show, SHOW_COM) && is_com(name)) ||
               (optset(show, SHOW_DMC) && is_dm(name))  ||
               (optset(show, SHOW_DML) && is_dml(name)) ||
               (optset(show, SHOW_PMC) && is_pm(name))  ||
               (optset(show, SHOW_SMC) && is_smc(name));
}

/* Search objects in a given directory for references to a target object. */
static int findWhereUsed(const char *dpath, const char *ref, int show)
{
       DIR *dir;
       struct dirent *cur;
       char fpath[PATH_MAX], cpath[PATH_MAX];
       int unmatched = 0;

       if (!(dir = opendir(dpath))) {
               return 1;
       }

       if (strcmp(dpath, ".") == 0) {
               strcpy(fpath, "");
       } else if (dpath[strlen(dpath) - 1] != '/') {
               strcpy(fpath, dpath);
               strcat(fpath, "/");
       } else {
               strcpy(fpath, dpath);
       }

       while ((cur = readdir(dir))) {
               strcpy(cpath, fpath);
               strcat(cpath, cur->d_name);

               if (recursive && isdir(cpath, true)) {
                       unmatched += findWhereUsed(cpath, ref, show);
               } else if (isUsedTarget(cur->d_name, show)) {
                       unmatched += listReferences(cpath, SHOW_WHERE_USED, ref, show);
               }
       }

       closedir(dir);

       return unmatched;
}

/* List objects that reference a target object. */
static int listWhereUsed(const char *path, int show)
{
       char code[PATH_MAX] = "";
       xmlDocPtr doc;

       /* In recursive mode, keep a record of which objects have been listed
        * to avoid infinite loops. */
       if (listRecursively) {
               if (listedFile(path)) {
                       return 0;
               }

               addFile(path);
       }

       if (verbosity >= VERBOSE) {
               fprintf(stderr, I_WHEREUSED, path);
       }

       /* If the target object is an ICN, get the ICN from the file name. */
       if (is_icn(path)) {
               strcpy(code, path);
               strtok(code, ".");
       /* If the target object is an XML file, read the object and get the
        * appropriate code from the IDSTATUS section. */
       } else if ((doc = read_xml_doc(path))) {
               xmlDocPtr tmp;
               xmlNodePtr ident, node;

               if (remDelete) {
                       rem_delete_elems(doc);
               }

               ident = firstXPathNode(doc, NULL, BAD_CAST "//dmIdent|//pmIdent|//commentIdent|//dmlIdent|//scormContentPackageIdent");
               node  = xmlNewNode(NULL, BAD_CAST "ref");
               ident = xmlAddChild(node, xmlCopyNode(ident, 1));
               tmp   = xmlNewDoc(BAD_CAST "1.0");
               xmlDocSetRootElement(tmp, node);

               if (xmlStrcmp(ident->name, BAD_CAST "commentIdent") == 0) {
                       xmlNodeSetName(ident, BAD_CAST "commentRefIdent");
                       xmlNodeSetName(ident, BAD_CAST "commentRef");
                       getComCode(code, node);
               } else if (xmlStrcmp(ident->name, BAD_CAST "dmIdent") == 0) {
                       xmlNodeSetName(ident, BAD_CAST "dmRefIdent");
                       xmlNodeSetName(node , BAD_CAST "dmRef");
                       getDmCode(code, node);
               } else if (xmlStrcmp(ident->name, BAD_CAST "dmlIdent") == 0) {
                       xmlNodeSetName(ident, BAD_CAST "dmlRefIdent");
                       xmlNodeSetName(node , BAD_CAST "dmlRef");
                       getDmlCode(code, node);
               } else if (xmlStrcmp(ident->name, BAD_CAST "pmIdent") == 0) {
                       xmlNodeSetName(ident, BAD_CAST "pmRefIdent");
                       xmlNodeSetName(node , BAD_CAST "pmRef");
                       getPmCode(code, node);
               } else if (xmlStrcmp(ident->name, BAD_CAST "scormContentPackageIdent") == 0) {
                       xmlNodeSetName(ident, BAD_CAST "scormContentPackageRefIdent");
                       xmlNodeSetName(node , BAD_CAST "scormContentPackageRef");
                       getSmcCode(code, node);
               } else if (xmlStrcmp(ident->name, BAD_CAST "sourceDmIdent") == 0 ||
                          xmlStrcmp(ident->name, BAD_CAST "sourcePmIdent") == 0 ||
                          xmlStrcmp(ident->name, BAD_CAST "repositorySourceDmIdent") == 0) {
                       getSourceIdent(code, ident);
               }

               xmlFreeDoc(tmp);
               xmlFreeDoc(doc);
       /* Otherwise, interpret the path as a literal code. */
       } else {
               strcpy(code, path);
       }

       /* If no code could be determined, give up. */
       if (strcmp(code, "") == 0) {
               return 1;
       }

       return findWhereUsed(directory, code, show);
}

/* List objects that reference any of a list of target objects. */
static int listWhereUsedList(const char *path, int show)
{
       FILE *f;
       char line[PATH_MAX];
       int unmatched = 0;

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

       while (fgets(line, PATH_MAX, f)) {
               strtok(line, "\t\r\n");
               unmatched += listWhereUsed(line, show);
       }

       if (path) {
               fclose(f);
       }

       return unmatched;
}

/* Read a non-chapterized IPD SNS code. */
static void readnonChapIpdSns(const char *s)
{
       if (strcmp(s, "-") == 0) {
               strcpy(nonChapIpdSystemCode, s);
       } else {
               int n;

               n = sscanf(s, "%3[0-9A-Z]-%1[0-9A-Z]%1[0-9A-Z]-%4[0-9A-Z]",
                       nonChapIpdSystemCode,
                       nonChapIpdSubSystemCode,
                       nonChapIpdSubSubSystemCode,
                       nonChapIpdAssyCode);

               if (n != 4) {
                       fprintf(stderr, E_BAD_CSN_CODE, s);
                       exit(EXIT_BAD_CSN_CODE);
               }
       }
}

/* Display the usage message. */
static void show_help(void)
{
       puts("Usage: s1kd-refs [-aBCcDEFfGHIiKLlmNnoPqrSsTUuvwXxYZ^h?] [-b <SNS>] [-d <dir>] [-e <cmd>] [-J <ns=URL> ...] [-j <xpath>] [-k <pattern>] [-t <fmt>] [-3 <file>] [<object>...]");
       puts("");
       puts("Options:");
       puts("  -a, --all                    Print unmatched codes.");
       puts("  -B, --ipd                    List IPD references.");
       puts("  -b, --ipd-sns <SNS>          The SNS for non-chapterized IPDs.");
       puts("  -C, --com                    List comment references.");
       puts("  -c, --content                Only show references in content section.");
       puts("  -D, --dm                     List data module references.");
       puts("  -d, --dir                    Directory to search for matches in.");
       puts("  -E, --epr                    List external pub refs.");
       puts("  -e, --exec <cmd>             Execute <cmd> for each CSDB object matched.");
       puts("  -F, --overwrite              Overwrite updated (-U) or tagged (-X) objects.");
       puts("  -f, --filename               Print the source filename for each reference.");
       puts("  -G, --icn                    List ICN references.");
       puts("  -H, --hotspot                List hotspot matches in ICNs.");
       puts("  -h, -?, --help               Show help/usage message.");
       puts("  -I, --update-issue           Update references to point to the latest matched object.");
       puts("  -i, --ignore-issue           Ignore issue info when matching.");
       puts("  -J, --namespace <ns=URL>     Register a namespace for the hotspot XPath.");
       puts("  -j, --hotspot-xpath <xpath>  XPath to use for matching hotspots (-H).");
       puts("  -K, --csn                    List CSN references.");
       puts("  -k, --ipd-dcv <pattern>      Pattern for IPD disassembly code variant.");
       puts("  -L, --dml                    List DML references.");
       puts("  -l, --list                   Treat input as list of CSDB objects.");
       puts("  -m, --strict-match           Be more strict when matching filenames of objects.");
       puts("  -N, --omit-issue             Assume filenames omit issue info.");
       puts("  -n, --lineno                 Print the source filename and line number for each reference.");
       puts("  -o, --output-valid           Output valid CSDB objects to stdout.");
       puts("  -P, --pm                     List publication module references.");
       puts("  -q, --quiet                  Quiet mode.");
       puts("  -R, --recursively            List references in matched objects recursively.");
       puts("  -r, --recursive              Search for matches in directories recursively.");
       puts("  -S, --smc                    List SCORM content package references.");
       puts("  -s, --include-src            Include the source object as a reference.");
       puts("  -T, --fragment               List referred fragments in other DMs.");
       puts("  -t, --format <fmt>           The format to use when printing references.");
       puts("  -U, --update                 Update address items in matched references.");
       puts("  -u, --unmatched              Show only unmatched references.");
       puts("  -v, --verbose                Verbose output.");
       puts("  -w, --where-used             List places where an object is referenced.");
       puts("  -X, --tag-unmatched          Tag unmatched references.");
       puts("  -x, --xml                    Output XML report.");
       puts("  -Y, --repository             List repository source DMs.");
       puts("  -Z, --source                 List source DM or PM.");
       puts("  -3, --externalpubs <file>    Use custom .externalpubs file.");
       puts("  -^, --remove-deleted         List refs with elements marked as \"delete\" removed.");
       puts("  --version                    Show version information.");
       puts("  <object>                     CSDB object to list references in.");
       LIBXML2_PARSE_LONGOPT_HELP
}

/* Display version information. */
static void show_version(void)
{
       printf("%s (s1kd-tools) %s\n", PROG_NAME, VERSION);
       printf("Using libxml %s\n", xmlParserVersion);
}

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

       bool isList = false;
       bool xmlOutput = false;
       bool inclSrcFname = false;
       bool inclLineNum = false;
       char extpubsFname[PATH_MAX] = "";
       bool findUsed = false;

       /* Which types of object references will be listed. */
       int showObjects = 0;

       const char *sopts = "qcNaFfLlUuCDGPRrd:IinEXxSsove:mHj:J:Tt:3:wYZBKb:k:^h?";
       struct option lopts[] = {
               {"version"       , no_argument      , 0, 0},
               {"help"          , no_argument      , 0, 'h'},
               {"quiet"         , no_argument      , 0, 'q'},
               {"content"       , no_argument      , 0, 'c'},
               {"externalpubs"  , required_argument, 0, '3'},
               {"omit-issue"    , no_argument      , 0, 'N'},
               {"all"           , no_argument      , 0, 'a'},
               {"overwrite"     , no_argument      , 0, 'F'},
               {"filename"      , no_argument      , 0, 'f'},
               {"dml"           , no_argument      , 0, 'L'},
               {"list"          , no_argument      , 0, 'l'},
               {"update"        , no_argument      , 0, 'U'},
               {"unmatched"     , no_argument      , 0, 'u'},
               {"com"           , no_argument      , 0, 'C'},
               {"dm"            , no_argument      , 0, 'D'},
               {"icn"           , no_argument      , 0, 'G'},
               {"pm"            , no_argument      , 0, 'P'},
               {"recursively"   , no_argument      , 0, 'R'},
               {"recursive"     , no_argument      , 0, 'r'},
               {"dir"           , required_argument, 0, 'd'},
               {"update-issue"  , no_argument      , 0, 'I'},
               {"ignore-issue"  , no_argument      , 0, 'i'},
               {"lineno"        , no_argument      , 0, 'n'},
               {"epr"           , no_argument      , 0, 'E'},
               {"exec"          , required_argument, 0, 'e'},
               {"tag-unmatched" , no_argument      , 0, 'X'},
               {"xml"           , no_argument      , 0, 'x'},
               {"smc"           , no_argument      , 0, 'S'},
               {"include-src"   , no_argument      , 0, 's'},
               {"output-valid"  , no_argument      , 0, 'o'},
               {"verbose"       , no_argument      , 0, 'v'},
               {"strict-match"  , no_argument      , 0, 'm'},
               {"hotspot"       , no_argument      , 0, 'H'},
               {"hotspot-xpath" , required_argument, 0, 'j'},
               {"namespace"     , required_argument, 0, 'J'},
               {"fragment"      , no_argument      , 0, 'T'},
               {"format"        , required_argument, 0, 't'},
               {"where-used"    , no_argument      , 0, 'w'},
               {"repository"    , no_argument      , 0, 'Y'},
               {"source"        , no_argument      , 0, 'Z'},
               {"ipd"           , no_argument      , 0, 'B'},
               {"csn"           , no_argument      , 0, 'K'},
               {"ipd-sns"       , required_argument, 0, 'b'},
               {"ipd-dcv"       , required_argument, 0, 'k'},
               {"remove-deleted", no_argument      , 0, '^'},
               LIBXML2_PARSE_LONGOPT_DEFS
               {0, 0, 0, 0}
       };
       int loptind = 0;

       directory = strdup(".");
       hotspotXPath = xmlStrdup(DEFAULT_HOTSPOT_XPATH);
       hotspotNs = xmlNewNode(NULL, BAD_CAST "hotspotNs");

       figNumVarFormat = xmlCharStrdup("%");

       while ((i = getopt_long(argc, argv, sopts, lopts, &loptind)) != -1) {
               switch (i) {
                       case 0:
                               if (strcmp(lopts[loptind].name, "version") == 0) {
                                       show_version();
                                       return 0;
                               }
                               LIBXML2_PARSE_LONGOPT_HANDLE(lopts, loptind, optarg)
                               break;
                       case 'q':
                               --verbosity;
                               break;
                       case 'c':
                               contentOnly = true;
                               break;
                       case '3':
                               strncpy(extpubsFname, optarg, PATH_MAX - 1);
                               break;
                       case 'N':
                               noIssue = true;
                               break;
                       case 'a':
                               showUnmatched = true;
                               break;
                       case 'F':
                               overwriteUpdated = true;
                               break;
                       case 'f':
                               inclSrcFname = true;
                               break;
                       case 'H':
                               showObjects |= SHOW_HOT;
                               break;
                       case 'L':
                               showObjects |= SHOW_DML;
                               break;
                       case 'l':
                               isList = true;
                               break;
                       case 'U':
                               updateRefs = true;
                               break;
                       case 'u':
                               showMatched = false;
                               break;
                       case 'C':
                               showObjects |= SHOW_COM;
                               break;
                       case 'D':
                               showObjects |= SHOW_DMC;
                               break;
                       case 'G':
                               showObjects |= SHOW_ICN;
                               break;
                       case 'P':
                               showObjects |= SHOW_PMC;
                               break;
                       case 'R':
                               listRecursively = true;
                               break;
                       case 'r':
                               recursive = true;
                               break;
                       case 'd':
                               free(directory);
                               directory = strdup(optarg);
                               break;
                       case 'I':
                               updateRefs = true;
                               ignoreIss = true;
                               updateRefIdent = true;
                               break;
                       case 'i':
                               ignoreIss = true;
                               break;
                       case 'n':
                               inclSrcFname = true;
                               inclLineNum = true;
                               break;
                       case 'E':
                               showObjects |= SHOW_EPR;
                               break;
                       case 'X':
                               tagUnmatched = true;
                               break;
                       case 'x':
                               xmlOutput = true;
                               break;
                       case 'S':
                               showObjects |= SHOW_SMC;
                               break;
                       case 's':
                               listSrc = true;
                               break;
                       case 'o':
                               outputTree = true;
                               break;
                       case 'v':
                               ++verbosity;
                               break;
                       case 'm':
                               looseMatch = false;
                               break;
                       case 'j':
                               xmlFree(hotspotXPath);
                               hotspotXPath = xmlStrdup(BAD_CAST optarg);
                               break;
                       case 'J':
                               addHotspotNs(optarg);
                               break;
                       case 'T':
                               showObjects |= SHOW_FRG;
                               break;
                       case 't':
                               printFormat = strdup(optarg);
                               break;
                       case 'e':
                               execStr = strdup(optarg);
                               break;
                       case 'w':
                               findUsed = true;
                               printMatchedFn = printMatchedWhereUsed;
                               printUnmatchedFn = printUnmatchedSrc;
                               break;
                       case 'Y':
                               showObjects |= SHOW_REP;
                               break;
                       case 'Z':
                               showObjects |= SHOW_SRC;
                               break;
                       case 'B':
                               showObjects |= SHOW_IPD;
                               break;
                       case 'K':
                               showObjects |= SHOW_CSN;
                               break;
                       case 'b':
                               readnonChapIpdSns(optarg);
                               nonChapIpdSns = true;
                               break;
                       case 'k':
                               xmlFree(figNumVarFormat);
                               figNumVarFormat = xmlCharStrdup(optarg);
                               break;
                       case '^':
                               remDelete = true;
                               break;
                       case 'h':
                       case '?':
                               show_help();
                               return 0;
               }
       }

       /* If none of -CDEGHLPST are given, show all types of objects. */
       if (!showObjects) {
               showObjects = SHOW_ALL;
       }

       /* Load .externalpubs config file. */
       if (strcmp(extpubsFname, "") != 0 || find_config(extpubsFname, DEFAULT_EXTPUBS_FNAME)) {
               externalPubs = read_xml_doc(extpubsFname);
       }

       /* Print opening of XML report. */
       if (xmlOutput) {
               puts("<?xml version=\"1.0\"?>");
               printf("<results>");
       }

       /* Set the functions for printing matched/unmatched refs. */
       if (execStr) {
               printMatchedFn = execMatched;
       } else if (printFormat) {
               printMatchedFn   = printMatchedCustom;
               printUnmatchedFn = printUnmatchedCustom;
       } else if (xmlOutput) {
               printMatchedFn = printMatchedXml;
               printUnmatchedFn = printUnmatchedXml;
       } else if (inclSrcFname) {
               if (inclLineNum) {
                       printMatchedFn = printMatchedSrcLine;
                       printUnmatchedFn = printUnmatchedSrcLine;
               } else {
                       printMatchedFn = printMatchedSrc;
                       printUnmatchedFn = printUnmatchedSrc;
               }
       }

       if (optind < argc) {
               for (i = optind; i < argc; ++i) {
                       if (isList) {
                               if (findUsed) {
                                       unmatched += listWhereUsedList(argv[i], showObjects);
                               } else {
                                       unmatched += listReferencesInList(argv[i], showObjects);
                               }
                       } else {
                               if (findUsed) {
                                       unmatched += listWhereUsed(argv[i], showObjects);
                               } else {
                                       unmatched += listReferences(argv[i], showObjects, NULL, 0);
                               }
                       }
               }
       } else if (isList) {
               if (findUsed) {
                       unmatched += listWhereUsedList(NULL, showObjects);
               } else {
                       unmatched += listReferencesInList(NULL, showObjects);
               }
       } else {
               if (findUsed) {
                       unmatched += listWhereUsed("-", showObjects);
               } else {
                       unmatched += listReferences("-", showObjects, NULL, 0);
               }
       }

       if (xmlOutput) {
               printf("</results>\n");
       }

       free(directory);
       xmlFree(hotspotXPath);
       xmlFreeNode(hotspotNs);
       free(listedFiles);
       free(execStr);
       free(printFormat);
       xmlFree(figNumVarFormat);
       xmlFreeDoc(externalPubs);
       xmlCleanupParser();

       return unmatched > 0 ? EXIT_UNMATCHED_REF : EXIT_SUCCESS;
}