#include #include #include #include #include #include #include #include #include #include #include #include #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(""); printf(""); 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(""); printf("%s", xmlGetLineNo(node), xpath, s); printf("%s", r); if (f) { printf("%s", f); } printf(""); 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(""); printf(""); 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(""); printf("%s", xmlGetLineNo(node), xpath, s); printf("%s", r); if (f) { printf("%s", f); } printf(""); 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 ] [-d ] [-e ] [-J ...] [-j ] [-k ] [-t ] [-3 ] [...]"); puts(""); puts("Options:"); puts(" -a, --all Print unmatched codes."); puts(" -B, --ipd List IPD references."); puts(" -b, --ipd-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 Execute 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 Register a namespace for the hotspot XPath."); puts(" -j, --hotspot-xpath XPath to use for matching hotspots (-H)."); puts(" -K, --csn List CSN references."); puts(" -k, --ipd-dcv 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 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 Use custom .externalpubs file."); puts(" -^, --remove-deleted List refs with elements marked as \"delete\" removed."); puts(" --version Show version information."); puts(" 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(""); printf(""); } /* 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("\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; }