/ .. / / -> download
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <getopt.h>
#include <stdbool.h>
#include <time.h>
#include <libxml/tree.h>
#include <libxml/xpath.h>
#include "s1kd_tools.h"

#define PROG_NAME "s1kd-upissue"
#define VERSION "5.0.1"

/* Message prefixes. */
#define ERR_PREFIX PROG_NAME ": ERROR: "
#define INF_PREFIX PROG_NAME ": INFO: "

/* Error messages. */
#define E_BAD_LIST ERR_PREFIX "Could not read list: %s\n"
#define E_ICN_INWORK ERR_PREFIX "ICNs cannot have inwork issues.\n"
#define E_BAD_FILENAME ERR_PREFIX "Filename does not contain issue info and -N not specified.\n"
#define E_ISSUE_TOO_LARGE ERR_PREFIX "%s is at the max issue number.\n"
#define E_INWORK_TOO_LARGE ERR_PREFIX "%s is at the max inwork number.\n"
#define E_NON_XML_STDIN ERR_PREFIX "Cannot use -m, -N or read from stdin when file does not contain issue info metadata.\n"
#define E_FILE_EXISTS ERR_PREFIX "%s already exists. Use -f to overwrite.\n"

/* Info messages. */
#define I_UPISSUE INF_PREFIX "Upissued %s to %s\n"

/* Exit codes. */
#define EXIT_NO_FILE 1
#define EXIT_NO_OVERWRITE 2
#define EXIT_BAD_FILENAME 3
#define EXIT_BAD_DATE 4
#define EXIT_ICN_INWORK 5
#define EXIT_ISSUE_TOO_LARGE 6

/* Verbosity of the output. */
static enum verbosity { QUIET, NORMAL, VERBOSE } verbosity = NORMAL;

/* Show help/usage information. */
static void show_help(void)
{
	puts("Usage: " PROG_NAME " [-045defHilmNQqRrsuvw^] [-1 <type>] [-2 <type>] [-c <reason>] [-I <date>] [-t <urt>] [-z <type>] [<file>...]");
	putchar('\n');
	puts("Options:");
	puts("  -0, --unverified             Set the quality assurance to unverified.");
	puts("  -1, --first-ver <type>       Set first verification type.");
	puts("  -2, --second-ver <type>      Set second verification type.");
	puts("  -4, --remove-marks           Remove change marks (but not RFUs).");
	puts("  -5, --print                  Print filenames of upissued objects.");
	puts("  -c, --reason <reason>        Add an RFU to the upissued object.");
	puts("  -d, --dry-run                Do not create or modify any files.");
	puts("  -e, --erase                  Remove old issue.");
	puts("  -f, --overwrite              Overwrite existing upissued object.");
	puts("  -H, --highlight              Highlight the last RFU.");
	puts("  -h, -?, --help               Show usage message.");
	puts("  -I, --date <date>            The issue date to use for the upissued objects.");
	puts("  -i, --official               Increase issue number instead of inwork.");
	puts("  -l, --list                   Treat input as list of objects.");
	puts("  -m, --modify                 Modify metadata without upissuing.");
	puts("  -N, --omit-issue             Omit issue/inwork numbers from filename.");
	puts("  -Q, --keep-qa                Keep quality assurance from old issue.");
	puts("  -q, --quiet                  Quiet mode.");
	puts("  -R, --keep-unassoc-marks     Only delete change marks associated with an RFU.");
	puts("  -r, --(keep|remove)-changes  Keep RFUs and change marks from old issue. In -m mode, remove them.");
	puts("  -s, --(keep|change)-date     Do not change issue date. In -m mode, change issue date.");
	puts("  -t, --type <urt>             Set the updateReasonType of the last RFU.");
	puts("  -u, --clean-rfus             Remove unassociated RFUs.");
	puts("  -w, --lock                   Make old and official issues read-only.");
	puts("  -z, --issue-type <type>      Set the issue type of the new issue.");
	puts("  -^, --remove-deleted         Remove \"delete\"d elements.");
	puts("  --version                    Show version information");
	LIBXML2_PARSE_LONGOPT_HELP
}

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

/* Remove change markup attributes from elements referencing old RFUs */
static void del_assoc_rfu_attrs(xmlNodePtr rfu, xmlXPathContextPtr ctx)
{
	xmlXPathObjectPtr obj;
	char *rfuid;

	rfuid = (char *) xmlGetProp(rfu, BAD_CAST "id");

	obj = xmlXPathEvalExpression(BAD_CAST "//*[@reasonForUpdateRefIds]", ctx);

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

		for (i = 0; i < obj->nodesetval->nodeNr; ++i) {
			char *ids, *id = NULL;
			bool used = false;

			ids = (char *) xmlGetProp(obj->nodesetval->nodeTab[i], BAD_CAST "reasonForUpdateRefIds");

			while ((id = strtok(id ? NULL : ids, " "))) {
				if (strcmp(id, rfuid) == 0) {
					used = true;
					break;
				}
			}

			xmlFree(ids);

			if (used) {
				xmlUnsetProp(obj->nodesetval->nodeTab[i], BAD_CAST "changeType");
				xmlUnsetProp(obj->nodesetval->nodeTab[i], BAD_CAST "changeMark");
				xmlUnsetProp(obj->nodesetval->nodeTab[i], BAD_CAST "reasonForUpdateRefIds");
			}
		}
	}

	xmlXPathFreeObject(obj);
	xmlFree(rfuid);
}

/* Determine if an RFU is ever referenced */
static bool rfu_used(xmlNodePtr rfu, xmlXPathContextPtr ctx)
{
	xmlXPathObjectPtr obj;
	bool ret = false;
	char *rfuid;

	rfuid = (char *) xmlGetProp(rfu, BAD_CAST "id");

	obj = xmlXPathEvalExpression(BAD_CAST "//@reasonForUpdateRefIds", ctx);

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

		for (i = 0; i < obj->nodesetval->nodeNr; ++i) {
			char *ids, *id = NULL;

			ids = (char *) xmlNodeGetContent(obj->nodesetval->nodeTab[i]);

			while ((id = strtok(id ? NULL : ids, " "))) {
				if (strcmp(id, rfuid) == 0) {
					ret = true;
					break;
				}
			}

			xmlFree(ids);

			if (ret) {
				break;
			}
		}
	}

	xmlXPathFreeObject(obj);
	xmlFree(rfuid);

	return ret;
}

/* Remove RFUs which are never referenced */
static void rem_unassoc_rfus(xmlDocPtr doc)
{
	xmlXPathContextPtr ctx;
	xmlXPathObjectPtr obj;

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

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

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

	xmlXPathFreeObject(obj);
	xmlXPathFreeContext(ctx);
}

/* Remove all change markup attributes */
static void del_rfu_attrs(xmlXPathContextPtr ctx, bool iss30)
{
	xmlXPathObjectPtr obj;
	const xmlChar *change, *mark, *rfc;

	if (iss30) {
		change = BAD_CAST "change";
		mark = BAD_CAST "mark";
		rfc = BAD_CAST "rfc";
		obj = xmlXPathEvalExpression(BAD_CAST "//*[@change or @mark or @rfc or @level]", ctx);
	} else {
		change = BAD_CAST "changeType";
		mark = BAD_CAST "changeMark";
		rfc = BAD_CAST "reasonForUpdateRefIds";
		obj = xmlXPathEvalExpression(BAD_CAST "//*[@changeType or @changeMark or @reasonForUpdateRefIds]", ctx);
	}

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

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

			type = xmlGetProp(obj->nodesetval->nodeTab[i], change);

			if (xmlStrcmp(type, BAD_CAST "delete") == 0) {
				xmlUnlinkNode(obj->nodesetval->nodeTab[i]);
				xmlFreeNode(obj->nodesetval->nodeTab[i]);
				obj->nodesetval->nodeTab[i] = NULL;
			} else {
				xmlUnsetProp(obj->nodesetval->nodeTab[i], change);
				xmlUnsetProp(obj->nodesetval->nodeTab[i], mark);
				xmlUnsetProp(obj->nodesetval->nodeTab[i], rfc);

				if (iss30) {
					xmlUnsetProp(obj->nodesetval->nodeTab[i], BAD_CAST "level");
				}
			}

			xmlFree(type);
		}
	}

	xmlXPathFreeObject(obj);
}

/* Remove changeInline elements. */
static void del_change_inline(xmlNodePtr node, bool iss30)
{
	bool remove;
	xmlNodePtr cur;

	/* Remove <change> (3.0-) or <changeInline> (4.0+) elements only if the
	 * change attributes have been removed (so that in -R mode,
	 * unassociated inline change elements will be kept). */
	if (iss30) {
		remove = xmlStrcmp(node->name, BAD_CAST "change") == 0 && !(xmlHasProp(node, BAD_CAST "mark") || xmlHasProp(node, BAD_CAST "change") || xmlHasProp(node, BAD_CAST "rfc"));
	} else{
		remove = xmlStrcmp(node->name, BAD_CAST "changeInline") == 0 && !(xmlHasProp(node, BAD_CAST "changeMark") || xmlHasProp(node, BAD_CAST "changeType") || xmlHasProp(node, BAD_CAST "reasonForUpdateRefIds"));
	}

	/* Apply to descendants first to remove nested changeInlines. */
	cur = node->children;
	while (cur) {
		xmlNodePtr next = cur->next;
		del_change_inline(cur, iss30);
		cur = next;
	}

	/* Merge children into parent. */
	if (remove) {
		cur = node->last;
		while (cur) {
			xmlNodePtr prev = cur->prev;
			xmlAddNextSibling(node, cur);
			cur = prev;
		}

		xmlUnlinkNode(node);
		xmlFreeNode(node);
	}
}

/* Delete old RFUs */
static void del_rfus(xmlDocPtr doc, bool only_assoc, bool iss30)
{
	xmlXPathContextPtr ctx;
	xmlXPathObjectPtr obj;

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

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

		if (only_assoc) {
			for (i = 0; i < obj->nodesetval->nodeNr; ++i) {
				del_assoc_rfu_attrs(obj->nodesetval->nodeTab[i], ctx);
				xmlUnlinkNode(obj->nodesetval->nodeTab[i]);
				xmlFreeNode(obj->nodesetval->nodeTab[i]);
				obj->nodesetval->nodeTab[i] = NULL;
			}
		} else {
			for (i = 0; i < obj->nodesetval->nodeNr; ++i) {
				xmlUnlinkNode(obj->nodesetval->nodeTab[i]);
				xmlFreeNode(obj->nodesetval->nodeTab[i]);
				obj->nodesetval->nodeTab[i] = NULL;
			}

			del_rfu_attrs(ctx, iss30);
		}
	}

	xmlXPathFreeObject(obj);
	xmlXPathFreeContext(ctx);

	del_change_inline(xmlDocGetRootElement(doc), iss30);
}

static void del_marks(xmlDocPtr doc, bool iss30)
{
	xmlXPathContextPtr ctx;

	ctx = xmlXPathNewContext(doc);
	del_rfu_attrs(ctx, iss30);
	xmlXPathFreeContext(ctx);

	del_change_inline(xmlDocGetRootElement(doc), iss30);
}

static void set_unverified(xmlDocPtr doc, bool iss30)
{
	xmlNodePtr qa, cur;

	qa = xpath_first_node(doc, NULL, BAD_CAST (iss30 ? "//qa" : "//qualityAssurance"));

	if (!qa)
		return;

	cur = qa->children;
	while (cur) {
		xmlNodePtr next = cur->next;
		xmlUnlinkNode(cur);
		xmlFreeNode(cur);
		cur = next;
	}

	xmlNewChild(qa, NULL, BAD_CAST (iss30 ? "unverif" : "unverified"), NULL);
}

static void set_qa(xmlDocPtr doc, char *firstver, char *secondver, bool iss30)
{
	xmlNodePtr qa, unverif, ver1, ver2;

	if (!(firstver || secondver))
		return;

	qa = xpath_first_node(doc, NULL, BAD_CAST (iss30 ? "//qa" : "//qualityAssurance"));

	if (!qa)
		return;

	unverif = xpath_first_node(doc, NULL, BAD_CAST (iss30 ? "//unverif" : "//unverified"));

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

	ver1 = xpath_first_node(doc, NULL, BAD_CAST (iss30 ? "//firstver" : "//firstVerification"));
	ver2 = xpath_first_node(doc, NULL, BAD_CAST (iss30 ? "//secver" : "//secondVerification"));

	if (firstver) {
		if (!secondver) {
			xmlNodePtr ver2;
			ver2 = xpath_first_node(doc, NULL, BAD_CAST (iss30 ? "//secver" : "//secondVerification"));
			if (ver2) {
				xmlUnlinkNode(ver2);
				xmlFreeNode(ver2);
			}
		}

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

		ver1 = xmlNewChild(qa, NULL, BAD_CAST (iss30 ? "firstver" : "firstVerification"), NULL);
		xmlSetProp(ver1, BAD_CAST (iss30 ? "type" : "verificationType"), BAD_CAST firstver);
	}

	if (secondver) {
		if (!ver1) {
			ver1 = xmlNewChild(qa, NULL, BAD_CAST (iss30 ? "firstver" : "firstVerification"), NULL);
			xmlSetProp(ver1, BAD_CAST (iss30 ? "type" : "verificationType"), BAD_CAST secondver);
		}

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

		ver2 = xmlNewChild(qa, NULL, BAD_CAST (iss30 ? "secver" : "secondVerification"), NULL);
		xmlSetProp(ver2, BAD_CAST (iss30 ? "type" : "verificationType"), BAD_CAST secondver);
	}
}

/* Add RFUs to the upissued object. */
#define ISS_30_RFU_PATH "(//qa|//sbc|//fic|//idstatus//ein|//skill|//rfu)[last()]"
#define ISS_4X_RFU_PATH "(//qualityAssurance|//systemBreakdownCode|//functionalItemCode|//identAndStatusSection//functionalItemRef|//skillLevel|//reasonForUpdate)[last()]"

static void add_rfus(xmlDocPtr doc, xmlNodePtr rfus, bool iss30)
{
	xmlNodePtr node, cur, next;

	node = xpath_first_node(doc, NULL, BAD_CAST (iss30 ? ISS_30_RFU_PATH : ISS_4X_RFU_PATH));

	if (!node) {
		return;
	}

	next = xmlNextElementSibling(node);

	while (next && (
		xmlStrcmp(next->name, BAD_CAST "rfu") == 0 ||
		xmlStrcmp(next->name, BAD_CAST "reasonForUpdate") == 0)) {
		node = next;
		next = xmlNextElementSibling(node);
	}

	for (cur = rfus->last; cur; cur = cur->prev) {
		xmlNodePtr rfu;

		rfu = xmlCopyNode(cur, 1);

		if (iss30) {
			xmlNodeSetName(rfu, BAD_CAST "rfu");
			xmlUnsetProp(rfu, BAD_CAST "updateReasonType");
			xmlUnsetProp(rfu, BAD_CAST "updateHighlight");
		} else {
			xmlChar *content;

			content = xmlNodeGetContent(rfu);
			xmlNodeSetContent(rfu, NULL);
			xmlNewChild(rfu, NULL, BAD_CAST "simplePara", content);
			xmlFree(content);
		}

		xmlAddNextSibling(node, rfu);
	}
}

static void set_iss_date(xmlDocPtr dmdoc, const char *issdate)
{
	char year_s[5], month_s[3], day_s[3];
	xmlNodePtr issueDate;

	issueDate = xpath_first_node(dmdoc, NULL, BAD_CAST "//issueDate|//issdate");

	if (issdate) {
		sscanf(issdate, "%4s-%2s-%2s", year_s, month_s, day_s);
	} else {
		time_t now;
		struct tm *local;
		unsigned short year, month, day;

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

		year = local->tm_year + 1900;
		month = local->tm_mon + 1;
		day = local->tm_mday;

		if (snprintf(year_s, 5, "%u", year) < 0 ||
		    snprintf(month_s, 3, "%.2u", month) < 0 ||
		    snprintf(day_s, 3, "%.2u", day) < 0)
			exit(EXIT_BAD_DATE);
	}

	xmlSetProp(issueDate, (xmlChar *) "year",  (xmlChar *) year_s);
	xmlSetProp(issueDate, (xmlChar *) "month", (xmlChar *) month_s);
	xmlSetProp(issueDate, (xmlChar *) "day",   (xmlChar *) day_s);
}

static void set_status(xmlDocPtr dmdoc, const char *status, bool iss30, xmlNodePtr issueInfo)
{
	xmlNodePtr dmStatus;

	if (iss30) {
		xmlSetProp(issueInfo, BAD_CAST "type", BAD_CAST status);
	} else {
		if ((dmStatus = xpath_first_node(dmdoc, NULL, BAD_CAST "//dmStatus|//pmStatus"))) {
			xmlSetProp(dmStatus, BAD_CAST "issueType", BAD_CAST status);
		}
	}
}

/* Upissue options */
static bool print_fnames = false;
static bool newissue = false;
static bool overwrite = false;
static char *status = NULL;
static bool no_issue = false;
static bool keep_rfus = false;
static bool set_date = true;
static bool only_assoc_rfus = false;
static bool reset_qa = true;
static bool dry_run = false;
static char *firstver = NULL;
static char *secondver = NULL;
static xmlNodePtr rfus = NULL;
static bool lock = false;
static bool remdel= false;
static bool remold = false;
static bool only_mod = false;
static bool clean_rfus = false;
static char *issdate = NULL;
static bool remove_marks = false;
static bool set_unverif = false;

static void upissue(const char *path)
{
	char *issueNumber = NULL;
	char *inWork = NULL;
	int issueNumber_int = 0;
	int inWork_int = 0;
	char dmfile[PATH_MAX], cpfile[PATH_MAX];
	xmlDocPtr dmdoc;
	xmlNodePtr issueInfo;
	xmlChar *issno_name, *inwork_name;
	bool iss30 = false;
	int up_issueNumber_int = 0;
	int up_inWork_int = 0;
	char upissued_issueNumber[32];
	char upissued_inWork[32];
	char *p, *i = NULL;

	strcpy(dmfile, path);

	if (access(dmfile, F_OK) == -1 && strcmp(dmfile, "-") != 0) {
		if (verbosity >= NORMAL) {
			fprintf(stderr, ERR_PREFIX "Could not read file %s.\n", dmfile);
		}
		exit(EXIT_NO_FILE);
	}

	dmdoc = read_xml_doc(dmfile);

	if (dmdoc) {
		issueInfo = xpath_first_node(dmdoc, NULL, BAD_CAST "//issueInfo|//issno");
	} else {
		issueInfo = NULL;
	}

	if (!issueInfo && no_issue) {
		if (verbosity >= NORMAL) {
			fprintf(stderr, E_NON_XML_STDIN);
		}
		exit(EXIT_NO_OVERWRITE);
	}

	/* Apply modifications without actually upissuing. */
	if (only_mod) {
		iss30 = xmlStrcmp(issueInfo->name, BAD_CAST "issueInfo") != 0;

		if (clean_rfus && !iss30) {
			rem_unassoc_rfus(dmdoc);
		}

		if (remdel) {
			rem_delete_elems(dmdoc);
		}

		if (set_unverif) {
			set_unverified(dmdoc, iss30);
		}

		add_rfus(dmdoc, rfus, iss30);
		set_qa(dmdoc, firstver, secondver, iss30);

		if (status) {
			set_status(dmdoc, status, iss30, issueInfo);
		}

		/* The following options have the opposite effect in -m mode:
		 * -s sets the date
		 * -r removes RFUs
		 */
		if (!set_date) {
			set_iss_date(dmdoc, issdate);
		}
		if (keep_rfus) {
			del_rfus(dmdoc, only_assoc_rfus, iss30);
		} else if (remove_marks) {
			del_marks(dmdoc, iss30);
		}

		if (overwrite) {
			save_xml_doc(dmdoc, dmfile);
		} else {
			save_xml_doc(dmdoc, "-");
		}

		xmlFreeDoc(dmdoc);

		return;
	}

	p = strchr(dmfile, '_');

	/* Issue info from XML */
	if (issueInfo) {
		i = p + 1;

		iss30 = strcmp((char *) issueInfo->name, "issueInfo") != 0;

		if (iss30) {
			issno_name = BAD_CAST "issno";
			inwork_name = BAD_CAST "inwork";
		} else {
			issno_name = BAD_CAST "issueNumber";
			inwork_name = BAD_CAST "inWork";
		}

		issueNumber = (char *) xmlGetProp(issueInfo, issno_name);
		inWork = (char *) xmlGetProp(issueInfo, inwork_name);

		if (!inWork) {
			inWork = strdup("00");
		}
	/* Get issue/inwork from filename only */
	} else if (p) {
		i = p + 1;

		if (i > dmfile + strlen(dmfile) - 6) {
			if (verbosity >= NORMAL) {
				fprintf(stderr, E_BAD_FILENAME);
			}
			exit(EXIT_BAD_FILENAME);
		}

		issueNumber = calloc(4, 1);
		inWork = calloc(3, 1);

		strncpy(issueNumber, i, 3);
		strncpy(inWork, i + 4, 2);
	/* Get issue from ICN */
	} else if ((p = strchr(dmfile, '-'))) {
		int n, c = 0;
		int l;

		if (!newissue) {
			if (verbosity >= NORMAL) {
				fprintf(stderr, E_ICN_INWORK);
			}
			exit(EXIT_ICN_INWORK);
		}

		l = strlen(dmfile);

		/* Get second-to-last '-' */
		for (n = l; n >= 0; --n) {
			if (dmfile[n] == '-') {
				if (c == 1) {
					break;
				} else {
					++c;
				}
			}
		}

		i = dmfile + n + 1;

		if (n == -1 || i > dmfile + l - 6) {
			if (verbosity >= NORMAL) {
				fprintf(stderr, E_BAD_FILENAME);
			}
			exit(EXIT_BAD_FILENAME);
		}

		issueNumber = calloc(4, 1);
		strncpy(issueNumber, i, 3);
	} else {
		if (verbosity >= NORMAL) {
			fprintf(stderr, E_BAD_FILENAME);
		}
		exit(EXIT_BAD_FILENAME);
	}

	if ((issueNumber_int = atoi(issueNumber)) >= 999) {
		if (verbosity >= NORMAL) {
			fprintf(stderr, E_ISSUE_TOO_LARGE, dmfile);
		}
		exit(EXIT_ISSUE_TOO_LARGE);
	}
	if (inWork) {
		if ((inWork_int = atoi(inWork)) >= 99) {
			if (verbosity >= NORMAL) {
				fprintf(stderr, E_INWORK_TOO_LARGE, dmfile);
			}
			exit(EXIT_ISSUE_TOO_LARGE);
		}
	}

	if (newissue) {
		up_issueNumber_int = issueNumber_int + 1;
		if (inWork) {
			up_inWork_int = 0;
		}
	} else {
		up_issueNumber_int = issueNumber_int;
		if (inWork) {
			up_inWork_int = inWork_int + 1;
		}
	}
	snprintf(upissued_issueNumber, 32, "%.3d", up_issueNumber_int);
	snprintf(upissued_inWork, 32, "%.2d", up_inWork_int);

	if (issueInfo) {
		xmlSetProp(issueInfo, issno_name, BAD_CAST upissued_issueNumber);
		xmlSetProp(issueInfo, inwork_name, BAD_CAST upissued_inWork);

		/* Optionally cleanup unused RFUs */
		if (clean_rfus && !iss30) {
			rem_unassoc_rfus(dmdoc);
		}

		/* When upissuing an official module to first inwork issue... */
		if (strcmp(inWork, "00") == 0) {
				/* Delete RFUs */
				if (!keep_rfus) {
					del_rfus(dmdoc, only_assoc_rfus, iss30);
				}

				/* Set unverified */
				if (reset_qa) {
					set_unverified(dmdoc, iss30);
				}
		/* Or, perform certain actions any time. */
		} else {
			if (remdel) {
				rem_delete_elems(dmdoc);
			}

			if (set_unverif) {
				set_unverified(dmdoc, iss30);
			}
		}

		if (set_date) {
			set_iss_date(dmdoc, issdate);
		}

		set_qa(dmdoc, firstver, secondver, iss30);
		add_rfus(dmdoc, rfus, iss30);

		/* If an issue type is specified, use that. */
		if (status) {
			set_status(dmdoc, status, iss30, issueInfo);
		/* Otherwise:
		 * - if the object is official, default to "status"
		 * - if the object is not official, keep the previous issue type. */
		} else if (inWork_int == 0) {
			set_status(dmdoc, "status", iss30, issueInfo);
		}

		if (remove_marks) {
			del_marks(dmdoc, iss30);
		}
	}

	xmlFree(issueNumber);
	xmlFree(inWork);

	if (!dmdoc) { /* Preserve non-XML filename for copying */
		strcpy(cpfile, dmfile);
	}

	if (!no_issue) {
		if (!dry_run) {
			if (remold && dmdoc) { /* Delete previous issue (XML file) */
				remove(dmfile);
			} else if (lock) { /* Remove write permission from previous issue. */
				mkreadonly(dmfile);
			}
		}

		if (p) {
			memcpy(i, upissued_issueNumber, 3);
			if (inWork) {
				memcpy(i + 4, upissued_inWork, 2);
			}
		}
	}

	if (!dry_run) {
		if (!overwrite && access(dmfile, F_OK) != -1) {
			if (verbosity >= NORMAL) {
				fprintf(stderr, E_FILE_EXISTS, dmfile);
			}
			exit(EXIT_NO_OVERWRITE);
		}

		if (dmdoc) {
			save_xml_doc(dmdoc, dmfile);
		} else { /* Copy non-XML file */
			copy(cpfile, dmfile);

			if (remold) { /* Delete previous issue (non-XML file) */
				remove(cpfile);
			}
		}

		/* Lock official issues. */
		if (lock && newissue) {
			mkreadonly(dmfile);
		}
	}

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

	if (print_fnames) {
		puts(dmfile);
	}

	if (dmdoc) {
		xmlFreeDoc(dmdoc);
	}
}

static void upissue_list(const char *path)
{
	FILE *f;
	char line[PATH_MAX];

	if (path) {
		if (!(f = 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");
		upissue(line);
	}

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

int main(int argc, char **argv)
{
	int i;
	bool islist = false;

	const char *sopts = "ivsNfrRI:Qq01:2:45delc:t:Hwmuz:^h?";
	struct option lopts[] = {
		{"version"           , no_argument      , 0, 0},
		{"help"              , no_argument      , 0, 'h'},
		{"unverified"        , no_argument      , 0, '0'},
		{"first-ver"         , required_argument, 0, '1'},
		{"second-ver"        , required_argument, 0, '2'},
		{"remove-marks"      , no_argument      , 0, '4'},
		{"print"             , no_argument      , 0, '5'},
		{"reason"            , required_argument, 0, 'c'},
		{"dry-run"           , no_argument      , 0, 'd'},
		{"erase"             , no_argument      , 0, 'e'},
		{"overwrite"         , no_argument      , 0, 'f'},
		{"keep-date"         , no_argument      , 0, 's'},
		{"change-date"       , no_argument      , 0, 's'},
		{"official"          , no_argument      , 0, 'i'},
		{"list"              , no_argument      , 0, 'l'},
		{"modify"            , no_argument      , 0, 'm'},
		{"omit-issue"        , no_argument      , 0, 'N'},
		{"keep-qa"           , no_argument      , 0, 'Q'},
		{"quiet"             , no_argument      , 0, 'q'},
		{"keep-unassoc-marks", no_argument      , 0, 'R'},
		{"keep-changes"      , no_argument      , 0, 'r'},
		{"remove-changes"    , no_argument      , 0, 'r'},
		{"date"              , required_argument, 0, 'I'},
		{"type"              , required_argument, 0, 't'},
		{"clean-rfus"        , no_argument      , 0, 'u'},
		{"highlight"         , no_argument      , 0, 'H'},
		{"verbose"           , no_argument      , 0, 'v'},
		{"lock"              , no_argument      , 0, 'w'},
		{"issue-type"        , required_argument, 0, 'z'},
		{"remove-deleted"    , no_argument      , 0, '^'},
		LIBXML2_PARSE_LONGOPT_DEFS
		{0, 0, 0, 0}
	};
	int loptind = 0;

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

	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 '0':
				set_unverif = true;
				break;
			case '1':
				firstver = strdup(optarg);
				break;
			case '2':
				secondver = strdup(optarg);
				break;
			case '4':
				remove_marks = true;
				break;
			case '5':
				print_fnames = true;
				break;
			case 'c':
				xmlNewChild(rfus, NULL, BAD_CAST "reasonForUpdate", BAD_CAST optarg);
				break;
			case 'd':
				dry_run = true;
				break;
			case 'e':
				remold = true;
				break;
			case 'f':
				overwrite = true;
				break;
			case 'I':
				issdate = strdup(optarg);
				break;
			case 'i':
				newissue = true;
				break;
			case 'l':
				islist = true;
				break;
			case 'm':
				only_mod = true;
				no_issue = true;
				break;
			case 'N':
				no_issue = true;
				overwrite = true;
				break;
			case 'Q':
				reset_qa = false;
				break;
			case 'q':
				--verbosity;
				break;
			case 'R':
				only_assoc_rfus = true;
				break;
			case 'r':
				keep_rfus = true;
				break;
			case 's':
				set_date = false;
				break;
			case 't':
				xmlSetProp(rfus->last, BAD_CAST "updateReasonType", BAD_CAST optarg);
				break;
			case 'u':
				clean_rfus = true;
				break;
			case 'H':
				xmlSetProp(rfus->last, BAD_CAST "updateHighlight", BAD_CAST "1");
				break;
			case 'v':
				++verbosity;
				break;
			case 'w':
				lock = true;
				break;
			case 'z':
				status = strdup(optarg);
				if (!(strcmp(status, "changed") == 0 || strcmp(status, "rinstate-changed") == 0)) {
					remove_marks = true;
				}
				break;
			case '^':
				remdel = true;
				break;
			case 'h':
			case '?':
				show_help();
				return 0;
		}
	}

	if (optind < argc) {
		for (i = optind; i < argc; i++) {
			if (islist) {
				upissue_list(argv[i]);
			} else {
				upissue(argv[i]);
			}
		}
	} else if (islist) {
		upissue_list(NULL);
	} else {
		no_issue = true;
		overwrite = true;
		upissue("-");
	}

	free(firstver);
	free(secondver);
	free(status);
	free(issdate);
	xmlFreeNode(rfus);

	xmlCleanupParser();

	return 0;
}


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