#include #include #include #include #include #include #include #include #include #include #include #include "s1kd_tools.h" #include "xsl.h" #define PROG_NAME "s1kd-flatten" #define VERSION "3.4.1" #define ERR_PREFIX PROG_NAME ": ERROR: " #define WRN_PREFIX PROG_NAME ": WARNING: " #define INF_PREFIX PROG_NAME ": INFO: " #define E_BAD_PM ERR_PREFIX "Bad publication module: %s\n" #define E_ENCODING_ERROR ERR_PREFIX "An encoding error occurred: %s (%d)\n" #define E_BAD_LIST ERR_PREFIX "Could not read list: %s\n" #define W_MISSING_REF WRN_PREFIX "Could not read referenced object: %s\n" #define I_INCLUDE INF_PREFIX "Including %s...\n" #define I_FOUND INF_PREFIX "Found %s\n" #define I_REMOVE INF_PREFIX "Removing %s...\n" #define I_SEARCH INF_PREFIX "Searching for %s in '%s' ...\n" #define I_REMDUPS INF_PREFIX "Removing duplicate references...\n" #define EXIT_BAD_PM 1 #define EXIT_ENCODING_ERROR 2 #define ENCODING_ERROR {\ fprintf(stderr, "An encoding error occurred: %s (%d)\n", __FILE__, __LINE__);\ exit(EXIT_ENCODING_ERROR);\ } static int xinclude = 0; static int no_issue = 0; static int ignore_iss = 0; static int use_pub_fmt = 0; static xmlDocPtr pub_doc = NULL; static xmlNodePtr pub; static xmlNodePtr search_paths; static char *search_dir; static int flatten_ref = 1; static int flatten_container = 0; static int recursive = 0; static int recursive_search = 0; static int remove_unresolved = 0; static int only_pm_refs = 0; static enum verbosity { QUIET, NORMAL, VERBOSE, DEBUG } verbosity = NORMAL; static void show_help(void) { puts("Usage: " PROG_NAME " [-d ] [-I ] [-cDfilmNPpqRruvxh?] [...]"); puts(""); puts("Options:"); puts(" -c, --containers Flatten referenced container data modules."); puts(" -D, --remove Remove unresolved references."); puts(" -d, --dir Directory to start search in."); puts(" -f, --overwrite Overwrite publication module."); puts(" -h, -?, --help Show help/usage message."); puts(" -I, --include Search for referenced objects."); puts(" -i, --ignore-issue Always match the latest issue of an object found."); puts(" -l, --list Treat input as a list of objects."); puts(" -m, --modify Modiy references without flattening them."); puts(" -N, --omit-issue Assume issue/inwork numbers are omitted."); puts(" -P, --only-pm-refs Only flatten PM refs."); puts(" -p, --simple Output a simple, flat XML file."); puts(" -q, --quiet Quiet mode."); puts(" -R, --recursively Recursively flatten referenced PMs."); puts(" -r, --recursive Search directories recursively."); puts(" -u, --unique Remove duplicate references."); puts(" -v, --verbose Verbose output."); puts(" -x, --use-xinclude Use XInclude references."); puts(" --version Show version information."); LIBXML2_PARSE_LONGOPT_HELP } static void show_version(void) { printf("%s (s1kd-tools) %s\n", PROG_NAME, VERSION); printf("Using libxml %s and libxslt %s\n", xmlParserVersion, xsltEngineVersion); } static xmlNodePtr find_child(xmlNodePtr parent, const char *child_name) { xmlNodePtr cur; for (cur = parent->children; cur; cur = cur->next) { if (strcmp((char *) cur->name, child_name) == 0) { return cur; } } return NULL; } static xmlNodePtr first_xpath_node(xmlDocPtr doc, xmlNodePtr node, const char *expr) { xmlXPathContextPtr ctx; xmlXPathObjectPtr obj; xmlNodePtr first; ctx = xmlXPathNewContext(doc ? doc : node->doc); ctx->node = node; obj = xmlXPathEvalExpression(BAD_CAST expr, ctx); first = xmlXPathNodeSetIsEmpty(obj->nodesetval) ? NULL : obj->nodesetval->nodeTab[0]; xmlXPathFreeObject(obj); xmlXPathFreeContext(ctx); return first; } static char *first_xpath_string(xmlDocPtr doc, xmlNodePtr node, const char *expr) { return (char *) xmlNodeGetContent(first_xpath_node(doc, node, expr)); } static void flatten_pm_entry(xmlNodePtr pm_entry, xmlNsPtr xiNs); static void flatten_pm_ref(xmlNodePtr pm_ref, xmlNsPtr xiNs) { xmlNodePtr pm_code; xmlNodePtr issue_info; xmlNodePtr language; char *model_ident_code; char *pm_issuer; char *pm_number; char *pm_volume; char *issue_number = NULL; char *in_work = NULL; char *language_iso_code = NULL; char *country_iso_code = NULL; char pmc[256]; char pm_fname[PATH_MAX]; char pm_fname_temp[PATH_MAX]; char fs_pm_fname[PATH_MAX] = ""; xmlNodePtr xi; xmlDocPtr doc; xmlNodePtr pm; bool found = false; xmlNodePtr cur; /* Skip PM refs if they do not need to be processed. */ if (!(flatten_ref || remove_unresolved || recursive)) { return; } pm_code = first_xpath_node(NULL, pm_ref, ".//pmCode|.//pmc"); issue_info = ignore_iss ? NULL : first_xpath_node(NULL, pm_ref, ".//issueInfo|.//issno"); language = first_xpath_node(NULL, pm_ref, ".//language"); model_ident_code = first_xpath_string(NULL, pm_code, "@modelIdentCode|modelic"); pm_issuer = first_xpath_string(NULL, pm_code, "@pmIssuer|pmissuer"); pm_number = first_xpath_string(NULL, pm_code, "@pmNumber|pmnumber"); pm_volume = first_xpath_string(NULL, pm_code, "@pmVolume|pmvolume"); snprintf(pmc, 256, "%s-%s-%s-%s", model_ident_code, pm_issuer, pm_number, pm_volume); snprintf(pm_fname, PATH_MAX, "PMC-%s", pmc); if (!no_issue) { strcpy(pm_fname_temp, pm_fname); if (issue_info) { issue_number = first_xpath_string(NULL, issue_info, "@issueNumber|@issno"); in_work = first_xpath_string(NULL, issue_info, "@inWork|@inwork"); if (snprintf(pm_fname, PATH_MAX, "%s_%s-%s", pm_fname_temp, issue_number, in_work ? in_work : "00") < 0) { ENCODING_ERROR } } else if (language) { if (snprintf(pm_fname, PATH_MAX, "%s_\?\?\?-\?\?", pm_fname_temp) < 0) { ENCODING_ERROR } } } if (language) { int i; language_iso_code = first_xpath_string(NULL, language, "@languageIsoCode|@language"); country_iso_code = first_xpath_string(NULL, language, "@countryIsoCode|@country"); for (i = 0; language_iso_code[i]; ++i) language_iso_code[i] = toupper(language_iso_code[i]); strcpy(pm_fname_temp, pm_fname); if (snprintf(pm_fname, PATH_MAX, "%s_%s-%s", pm_fname_temp, language_iso_code, country_iso_code) < 0) { ENCODING_ERROR } } xmlFree(model_ident_code); xmlFree(pm_issuer); xmlFree(pm_number); xmlFree(pm_volume); xmlFree(issue_number); xmlFree(in_work); xmlFree(language_iso_code); xmlFree(country_iso_code); for (cur = search_paths->children; cur && !found; cur = cur->next) { char *path; path = (char *) xmlNodeGetContent(cur); if (verbosity >= DEBUG) { fprintf(stderr, I_SEARCH, pm_fname, path); } if (find_csdb_object(fs_pm_fname, path, pm_fname, is_pm, recursive_search)) { if (verbosity >= DEBUG) { fprintf(stderr, I_FOUND, fs_pm_fname); } found = true; if (recursive) { xmlDocPtr subpm; xmlNodePtr content; if (verbosity >= VERBOSE) { fprintf(stderr, I_INCLUDE, fs_pm_fname); } subpm = read_xml_doc(fs_pm_fname); content = first_xpath_node(subpm, NULL, "//content"); if (content) { xmlNodePtr c; flatten_pm_entry(content, xiNs); for (c = content->last; c; c = c->prev) { if (xmlStrcmp(c->name, BAD_CAST "pmEntry") != 0) { continue; } xmlAddNextSibling(pm_ref, xmlCopyNode(c, 1)); } } xmlFreeDoc(subpm); } else if (flatten_ref) { if (verbosity >= VERBOSE) { fprintf(stderr, I_INCLUDE, fs_pm_fname); } if (xinclude) { xi = xmlNewNode(xiNs, BAD_CAST "include"); xmlSetProp(xi, BAD_CAST "href", BAD_CAST fs_pm_fname); if (use_pub_fmt) { xi = xmlAddChild(pub, xi); } else { xi = xmlAddPrevSibling(pm_ref, xi); } } else { doc = read_xml_doc(fs_pm_fname); pm = xmlDocGetRootElement(doc); xmlAddPrevSibling(pm_ref, xmlCopyNode(pm, 1)); xmlFreeDoc(doc); } } } else if (remove_unresolved) { if (verbosity >= VERBOSE) { fprintf(stderr, I_REMOVE, pm_fname); } } else { if (verbosity >= NORMAL) { fprintf(stderr, W_MISSING_REF, pm_fname); } } xmlFree(path); } if ((found && (flatten_ref || recursive)) || (!found && remove_unresolved)) { xmlUnlinkNode(pm_ref); xmlFreeNode(pm_ref); } } static void flatten_dm_ref(xmlNodePtr dm_ref, xmlNsPtr xiNs) { xmlNodePtr dm_code; xmlNodePtr issue_info; xmlNodePtr language; char *model_ident_code; char *system_diff_code; char *system_code; char *sub_system_code; char *sub_sub_system_code; char *assy_code; char *disassy_code; char *disassy_code_variant; char *info_code; char *info_code_variant; char *item_location_code; char *issue_number = NULL; char *in_work = NULL; char *language_iso_code = NULL; char *country_iso_code = NULL; char dmc[256]; char dm_fname[PATH_MAX]; char dm_fname_temp[PATH_MAX]; char fs_dm_fname[PATH_MAX] = ""; xmlNodePtr xi; xmlDocPtr doc; xmlNodePtr dmodule; bool found = false; xmlNodePtr cur; /* Skip DM refs if they do not need to be processed. */ if (only_pm_refs || !(flatten_ref || remove_unresolved || flatten_container)) { return; } dm_code = first_xpath_node(NULL, dm_ref, ".//dmCode|.//avee"); issue_info = ignore_iss ? NULL : first_xpath_node(NULL, dm_ref, ".//issueInfo|.//issno"); language = first_xpath_node(NULL, dm_ref, ".//language"); model_ident_code = first_xpath_string(NULL, dm_code, "@modelIdentCode|modelic"); system_diff_code = first_xpath_string(NULL, dm_code, "@systemDiffCode|sdc"); system_code = first_xpath_string(NULL, dm_code, "@systemCode|chapnum"); sub_system_code = first_xpath_string(NULL, dm_code, "@subSystemCode|section"); sub_sub_system_code = first_xpath_string(NULL, dm_code, "@subSubSystemCode|subsect"); assy_code = first_xpath_string(NULL, dm_code, "@assyCode|subject"); disassy_code = first_xpath_string(NULL, dm_code, "@disassyCode|discode"); disassy_code_variant = first_xpath_string(NULL, dm_code, "@disassyCodeVariant|discodev"); info_code = first_xpath_string(NULL, dm_code, "@infoCode|incode"); info_code_variant = first_xpath_string(NULL, dm_code, "@infoCodeVariant|incodev"); item_location_code = first_xpath_string(NULL, dm_code, "@itemLocationCode|itemloc"); snprintf(dmc, 256, "%s-%s-%s-%s%s-%s-%s%s-%s%s-%s", model_ident_code, system_diff_code, system_code, sub_system_code, sub_sub_system_code, assy_code, disassy_code, disassy_code_variant, info_code, info_code_variant, item_location_code); snprintf(dm_fname, PATH_MAX, "DMC-%s", dmc); if (!no_issue) { strcpy(dm_fname_temp, dm_fname); if (issue_info) { issue_number = first_xpath_string(NULL, issue_info, "@issueNumber|@issno"); in_work = first_xpath_string(NULL, issue_info, "@inWork|@inwork"); if (snprintf(dm_fname, PATH_MAX, "%s_%s-%s", dm_fname_temp, issue_number, in_work ? in_work : "00") < 0) { ENCODING_ERROR } } else if (language) { if (snprintf(dm_fname, PATH_MAX, "%s_\?\?\?-\?\?", dm_fname_temp) < 0) { ENCODING_ERROR } } } if (language) { int i; language_iso_code = first_xpath_string(NULL, language, "@languageIsoCode|@language"); country_iso_code = first_xpath_string(NULL, language, "@countryIsoCode|@country"); for (i = 0; language_iso_code[i]; ++i) language_iso_code[i] = toupper(language_iso_code[i]); strcpy(dm_fname_temp, dm_fname); if (snprintf(dm_fname, PATH_MAX, "%s_%s-%s", dm_fname_temp, language_iso_code, country_iso_code) < 0) { ENCODING_ERROR } } xmlFree(model_ident_code); xmlFree(system_diff_code); xmlFree(system_code); xmlFree(sub_system_code); xmlFree(sub_sub_system_code); xmlFree(assy_code); xmlFree(disassy_code); xmlFree(disassy_code_variant); xmlFree(info_code); xmlFree(info_code_variant); xmlFree(item_location_code); xmlFree(issue_number); xmlFree(in_work); xmlFree(language_iso_code); xmlFree(country_iso_code); for (cur = search_paths->children; cur && !found; cur = cur->next) { char *path; path = (char *) xmlNodeGetContent(cur); if (verbosity >= DEBUG) { fprintf(stderr, I_SEARCH, dm_fname, path); } if (find_csdb_object(fs_dm_fname, path, dm_fname, is_dm, recursive_search)) { if (verbosity >= DEBUG) { fprintf(stderr, I_FOUND, fs_dm_fname); } found = true; /* Flatten a container data module by copying the * dmRefs inside the container directly in to the * publication module. */ if (flatten_container) { xmlDocPtr doc; xmlNodePtr refs; doc = read_xml_doc(fs_dm_fname); refs = first_xpath_node(doc, NULL, "//container/refs"); if (refs) { xmlNodePtr c; /* First, flatten the dmRefs in the * container itself. */ flatten_pm_entry(refs, xiNs); /* Copy each dmRef from the container * into the PM. */ for (c = refs->last; c; c = c->prev) { if (c->type != XML_ELEMENT_NODE) { continue; } xmlAddNextSibling(dm_ref, xmlCopyNode(c, 1)); } } xmlFreeDoc(doc); } if (flatten_ref) { if (verbosity >= VERBOSE) { fprintf(stderr, I_INCLUDE, fs_dm_fname); } if (xinclude) { xi = xmlNewNode(xiNs, BAD_CAST "include"); xmlSetProp(xi, BAD_CAST "href", BAD_CAST fs_dm_fname); if (use_pub_fmt) { xi = xmlAddChild(pub, xi); } else { xi = xmlAddPrevSibling(dm_ref, xi); } } else { xmlChar *app; doc = read_xml_doc(fs_dm_fname); dmodule = xmlDocGetRootElement(doc); if ((app = xmlGetProp(dm_ref, BAD_CAST "applicRefId"))) { xmlSetProp(dmodule, BAD_CAST "applicRefId", app); } xmlFree(app); xmlAddPrevSibling(dm_ref, xmlCopyNode(dmodule, 1)); xmlFreeDoc(doc); } } } else if (remove_unresolved) { if (verbosity >= VERBOSE) { fprintf(stderr, I_REMOVE, dm_fname); } } else { if (verbosity >= NORMAL) { fprintf(stderr, W_MISSING_REF, dm_fname); } } xmlFree(path); } if ((found && flatten_ref) || (!found && remove_unresolved)) { xmlUnlinkNode(dm_ref); xmlFreeNode(dm_ref); } } static void flatten_pm_entry(xmlNodePtr pm_entry, xmlNsPtr xiNs) { xmlNodePtr cur, next; cur = pm_entry->children; while (cur) { next = cur->next; if (xmlStrcmp(cur->name, BAD_CAST "dmRef") == 0 || xmlStrcmp(cur->name, BAD_CAST "refdm") == 0) { flatten_dm_ref(cur, xiNs); } else if (xmlStrcmp(cur->name, BAD_CAST "pmRef") == 0 || xmlStrcmp(cur->name, BAD_CAST "refpm") == 0) { flatten_pm_ref(cur, xiNs); } else if (xmlStrcmp(cur->name, BAD_CAST "pmEntry") == 0 || xmlStrcmp(cur->name, BAD_CAST "pmentry") == 0) { flatten_pm_entry(cur, xiNs); } cur = next; } if (xmlChildElementCount(pm_entry) == 0 || xmlStrcmp((cur = xmlLastElementChild(pm_entry))->name, BAD_CAST "pmEntryTitle") == 0 || xmlStrcmp(cur->name, BAD_CAST "title") == 0) { xmlUnlinkNode(pm_entry); xmlFreeNode(pm_entry); } } static void transform_doc(xmlDocPtr doc, unsigned char *xml, unsigned int len, const char **params) { xmlDocPtr styledoc, res, src; xsltStylesheetPtr style; xmlNodePtr old; styledoc = read_xml_mem((const char *) xml, len); style = xsltParseStylesheetDoc(styledoc); src = xmlCopyDoc(doc, 1); res = xsltApplyStylesheet(style, src, params); xmlFreeDoc(src); old = xmlDocSetRootElement(doc, xmlCopyNode(xmlDocGetRootElement(res), 1)); xmlFreeNode(old); xmlFreeDoc(res); xsltFreeStylesheet(style); } static void remove_dup_refs(xmlDocPtr pm) { const char *params[5]; params[0] = "INF_PREFIX"; params[1] = "\"" INF_PREFIX "\""; params[2] = "verbosity"; switch (verbosity) { case QUIET: params[3] = "0"; break; case NORMAL: params[3] = "1"; break; case VERBOSE: params[3] = "2"; break; case DEBUG: params[3] = "3"; break; } params[4] = NULL; if (verbosity >= VERBOSE) { fprintf(stderr, I_REMDUPS); } transform_doc(pm, xsl_remdups1_xsl, xsl_remdups1_xsl_len, NULL); transform_doc(pm, xsl_remdups2_xsl, xsl_remdups2_xsl_len, params); transform_doc(pm, ___common_remove_empty_pmentries_xsl, ___common_remove_empty_pmentries_xsl_len, NULL); } static void flatten_file(xmlNodePtr pub, xmlNsPtr xiNs, const char *fname) { if (xinclude) { xmlNodePtr xi; xi = xmlNewChild(pub, xiNs, BAD_CAST "include", NULL); xmlSetProp(xi, BAD_CAST "href", BAD_CAST fname); } else { xmlDocPtr doc; doc = read_xml_doc(fname); xmlAddChild(pub, xmlCopyNode(xmlDocGetRootElement(doc), 1)); xmlFreeDoc(doc); } } static void flatten_list(const char *path, xmlNodePtr pub, xmlNsPtr xiNs) { FILE *f = NULL; char line[PATH_MAX]; if (path) { if (!(fopen(path, "r"))) { if (verbosity >= NORMAL) { fprintf(stderr, E_BAD_LIST, path); } return; } } else { f = stdin; } while (fgets(line, PATH_MAX, f)) { strtok(line, "\t\r\n"); flatten_file(pub, xiNs, line); } if (path) { fclose(f); } } int main(int argc, char **argv) { int c; char *pm_fname = NULL; xmlNodePtr pm; xmlNodePtr content; xmlNodePtr cur; const char *sopts = "cDd:fxmNPpqRruvI:ilh?"; struct option lopts[] = { {"version" , no_argument , 0, 0}, {"help" , no_argument , 0, 'h'}, {"containers" , no_argument , 0, 'c'}, {"remove" , no_argument , 0, 'D'}, {"dir" , required_argument, 0, 'd'}, {"overwrite" , no_argument , 0, 'f'}, {"use-xinclude", no_argument , 0, 'x'}, {"modify" , no_argument , 0, 'm'}, {"omit-issue" , no_argument , 0, 'N'}, {"only-pm-refs", no_argument , 0, 'P'}, {"simple" , no_argument , 0, 'p'}, {"quiet" , no_argument , 0, 'q'}, {"recursively" , no_argument , 0, 'R'}, {"recursive" , no_argument , 0, 'r'}, {"unique" , no_argument , 0, 'u'}, {"verbose" , no_argument , 0, 'v'}, {"include" , required_argument, 0, 'I'}, {"ignore-issue", no_argument , 0, 'i'}, LIBXML2_PARSE_LONGOPT_DEFS {0, 0, 0, 0} }; int loptind = 0; int overwrite = 0; int remove_dups = 0; bool is_list = false; xmlNsPtr xiNs = NULL; search_paths = xmlNewNode(NULL, BAD_CAST "searchPaths"); search_dir = strdup("."); while ((c = getopt_long(argc, argv, sopts, lopts, &loptind)) != -1) { switch (c) { case 0: if (strcmp(lopts[loptind].name, "version") == 0) { show_version(); return 0; } LIBXML2_PARSE_LONGOPT_HANDLE(lopts, loptind, optarg) break; case 'c': flatten_container = 1; break; case 'D': remove_unresolved = 1; break; case 'd': free(search_dir); search_dir = strdup(optarg); break; case 'f': overwrite = 1; break; case 'x': xinclude = 1; break; case 'm': flatten_ref = 0; break; case 'N': no_issue = 1; break; case 'P': only_pm_refs = 1; break; case 'p': use_pub_fmt = 1; break; case 'q': --verbosity; break; case 'R': recursive = 1; break; case 'r': recursive_search = 1; break; case 'u': remove_dups = 1; break; case 'v': ++verbosity; break; case 'I': xmlNewChild(search_paths, NULL, BAD_CAST "path", BAD_CAST optarg); break; case 'i': ignore_iss = 1; break; case 'l': is_list = true; use_pub_fmt = 1; break; case 'h': case '?': show_help(); exit(0); } } xmlNewChild(search_paths, NULL, BAD_CAST "path", BAD_CAST search_dir); free(search_dir); if (use_pub_fmt) { pub_doc = xmlNewDoc(BAD_CAST "1.0"); pub = xmlNewNode(NULL, BAD_CAST "publication"); xmlDocSetRootElement(pub_doc, pub); if (optind < argc) { int i; for (i = optind; i < argc; ++i) { if (is_list) { flatten_list(argv[i], pub, xiNs); } else { flatten_file(pub, xiNs, argv[i]); } } } else if (is_list) { flatten_list(NULL, pub, xiNs); } else { flatten_file(pub, xiNs, "-"); } } else { if (optind < argc) { pm_fname = argv[optind]; } else { pm_fname = "-"; } if (!(pub_doc = read_xml_doc(pm_fname))) { fprintf(stderr, E_BAD_PM, pm_fname); exit(EXIT_BAD_PM); } pm = xmlDocGetRootElement(pub_doc); content = find_child(pm, "content"); if (content) { if (xinclude) { xiNs = xmlNewNs(pm, BAD_CAST "http://www.w3.org/2001/XInclude", BAD_CAST "xi"); } } else { fprintf(stderr, E_BAD_PM, pm_fname); exit(EXIT_BAD_PM); } cur = content->children; while (cur) { xmlNodePtr next; next = cur->next; if (xmlStrcmp(cur->name, BAD_CAST "pmEntry") == 0 || xmlStrcmp(cur->name, BAD_CAST "pmentry") == 0) { flatten_pm_entry(cur, xiNs); } cur = next; } } /* Remove duplicate entries from the flattened PM. */ if (remove_dups) { remove_dup_refs(pub_doc); } save_xml_doc(pub_doc, (overwrite && !use_pub_fmt) ? pm_fname : "-"); xmlFreeNode(search_paths); xmlFreeDoc(pub_doc); xsltCleanupGlobals(); xmlCleanupParser(); return 0; }