/ .. / / -> download
#include "s1kd_tools.h"

#define PROGRESS_BAR_WIDTH 60

/* Default global XML parsing options.
 *
 * XML_PARSE_NOERROR / XML_PARSE_NOWARNING
 *   Suppress the error logging built-in to the parser. The tools will handle
 *   reporting errors.
 *
 * XML_PARSE_NONET
 *   Disable network access as a safety precaution.
 */
int DEFAULT_PARSE_OPTS = XML_PARSE_NOERROR | XML_PARSE_NOWARNING | XML_PARSE_NONET;

/* Return the full path name from a relative path. */
char *real_path(const char *path, char *real)
{
	#ifdef _WIN32
	if (!GetFullPathName(path, PATH_MAX, real, NULL)) {
	#else
	if (!realpath(path, real)) {
	#endif
		strcpy(real, path);
	}
	return real;
}

/* Search up the directory tree to find a configuration file. */
bool find_config(char *dst, const char *name)
{
	char cwd[PATH_MAX], prev[PATH_MAX];
	bool found = true;

	real_path(".", cwd);
	strcpy(prev, cwd);

	while (access(name, F_OK) == -1) {
		char cur[PATH_MAX];

		if (chdir("..") || strcmp(real_path(".", cur), prev) == 0) {
			found = false;
			break;
		}

		strcpy(prev, cur);
	}

	if (found) {
		real_path(name, dst);
	} else {
		strcpy(dst, name);
	}

	return chdir(cwd) == 0 && found;
}

/* Generate an XPath expression for a node. */
xmlChar *xpath_of(xmlNodePtr node)
{
	xmlNodePtr path, cur;
	xmlChar *dst = NULL;

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

	/* Build XPath expression node by traversing up the tree. */
	while (node && node->type != XML_DOCUMENT_NODE) {
		xmlNodePtr e;
		const xmlChar *name;

		e = xmlNewChild(path, NULL, BAD_CAST "node", NULL);

		switch (node->type) {
			case XML_COMMENT_NODE:
				name = BAD_CAST "comment()";
				break;
			case XML_PI_NODE:
				name = BAD_CAST "processing-instruction()";
				break;
			case XML_TEXT_NODE:
				name = BAD_CAST "text()";
				break;
			default:
				name = node->name;
				break;
		}

		if (node->ns != NULL) {
			xmlSetProp(e, BAD_CAST "ns", node->ns->prefix);
		}
		xmlSetProp(e, BAD_CAST "name", name);

		/* Locate the node's position within its parent. */
		if (node->type != XML_ATTRIBUTE_NODE) {
			int n = 1;
			xmlChar pos[16];

			for (cur = node->parent->children; cur; cur = cur->next) {
				if (cur == node) {
					break;
				} else if (cur->type == node->type && (node->type != XML_ELEMENT_NODE || xmlStrcmp(cur->name, node->name) == 0)) {
					++n;
				}
			}

			xmlStrPrintf(pos, 16, "%d", n);
			xmlSetProp(e, BAD_CAST "pos", pos);
		}

		node = node->parent;
	}

	/* Convert XPath expression node to string. */
	for (cur = path->last; cur; cur = cur->prev) {
		xmlChar *ns, *name, *pos;

		ns   = xmlGetProp(cur, BAD_CAST "ns");
		name = xmlGetProp(cur, BAD_CAST "name");
		pos  = xmlGetProp(cur, BAD_CAST "pos");

		dst = xmlStrcat(dst, BAD_CAST "/");
		if (!pos) {
			dst = xmlStrcat(dst, BAD_CAST "@");
		}
		if (ns) {
			dst = xmlStrcat(dst, ns);
			dst = xmlStrcat(dst, BAD_CAST ":");
		}
		dst = xmlStrcat(dst, name);
		if (pos) {
			dst = xmlStrcat(dst, BAD_CAST "[");
			dst = xmlStrcat(dst, pos);
			dst = xmlStrcat(dst, BAD_CAST "]");
		}

		xmlFree(ns);
		xmlFree(name);
		xmlFree(pos);
	}

	xmlFreeNode(path);

	return dst;
}

/* Make a copy of a file. */
int copy(const char *from, const char *to)
{

	FILE *f1, *f2;
	char buf[4096];
	size_t n;
	struct stat sf, st;

	if (stat(from, &sf) == -1) {
		return 1;
	}
	if (stat(to, &st) == 0 && sf.st_dev == st.st_dev && sf.st_ino == st.st_ino) {
		return 1;
	}

	f1 = fopen(from, "rb");
	f2 = fopen(to, "wb");

	while ((n = fread(buf, 1, 4096, f1)) > 0) {
		fwrite(buf, 1, n, f2);
	}

	fclose(f1);
	fclose(f2);

	return 0;
}

/* Determine if path is a directory. */
bool isdir(const char *path, bool recursive)
{
	struct stat st;
	char s[PATH_MAX], *b;

	strcpy(s, path);
	b = basename(s);

	if (recursive && (strcmp(b, ".") == 0 || strcmp(b, "..") == 0)) {
		return false;
	}

	if (stat(path, &st) != 0) {
		return false;
	}

	return S_ISDIR(st.st_mode);
}

/* Not exposed by the libxml API < 2.12.0 */
#if LIBXML_VERSION < 21200
void xmlFreeEntity(xmlEntityPtr entity)
{
    xmlDictPtr dict = NULL;

    if (entity == NULL)
        return;

    if (entity->doc != NULL)
        dict = entity->doc->dict;


    if ((entity->children) && (entity->owner == 1) &&
        (entity == (xmlEntityPtr) entity->children->parent))
        xmlFreeNodeList(entity->children);
    if (dict != NULL) {
        if ((entity->name != NULL) && (!xmlDictOwns(dict, entity->name)))
            xmlFree((char *) entity->name);
        if ((entity->ExternalID != NULL) &&
	    (!xmlDictOwns(dict, entity->ExternalID)))
            xmlFree((char *) entity->ExternalID);
        if ((entity->SystemID != NULL) &&
	    (!xmlDictOwns(dict, entity->SystemID)))
            xmlFree((char *) entity->SystemID);
        if ((entity->URI != NULL) && (!xmlDictOwns(dict, entity->URI)))
            xmlFree((char *) entity->URI);
        if ((entity->content != NULL)
            && (!xmlDictOwns(dict, entity->content)))
            xmlFree((char *) entity->content);
        if ((entity->orig != NULL) && (!xmlDictOwns(dict, entity->orig)))
            xmlFree((char *) entity->orig);
    } else {
        if (entity->name != NULL)
            xmlFree((char *) entity->name);
        if (entity->ExternalID != NULL)
            xmlFree((char *) entity->ExternalID);
        if (entity->SystemID != NULL)
            xmlFree((char *) entity->SystemID);
        if (entity->URI != NULL)
            xmlFree((char *) entity->URI);
        if (entity->content != NULL)
            xmlFree((char *) entity->content);
        if (entity->orig != NULL)
            xmlFree((char *) entity->orig);
    }
    xmlFree(entity);
}
#endif

/* Compare the codes of two CSDB objects. */
static int codecmp(const char *p1, const char *p2)
{
	char s1[PATH_MAX], s2[PATH_MAX], *b1, *b2;

	strcpy(s1, p1);
	strcpy(s2, p2);

	b1 = basename(s1);
	b2 = basename(s2);

	return strcasecmp(b1, b2);
}

/* Match a string with a pattern case-insensitively, using ? as a wildcard. */
bool strmatch(const char *p, const char *s)
{
	const unsigned char *cp = (const unsigned char *) p;
	const unsigned char *cs = (const unsigned char *) s;

	while (*cp) {
		if (tolower(*cp) != tolower(*cs) && *cp != '?') {
			return false;
		}
		++cp;
		++cs;
	}

	return true;
}

/* Match a string with a pattern case-insensitvely, using ? as a wildcard, up to a certain length. */
bool strnmatch(const char *p, const char *s, int n)
{
	const unsigned char *cp = (const unsigned char *) p;
	const unsigned char *cs = (const unsigned char *) s;
	int i = 0;

	while (*cp && i < n) {
		if (tolower(*cp) != tolower(*cs) && *cp != '?') {
			return false;
		}
		++cp;
		++cs;
		++i;
	}

	return true;
}

/* Find a CSDB object in a directory hierarchy based on its code. */
bool find_csdb_object(char *dst, const char *path, const char *code, bool (*is)(const char *), bool recursive)
{
	DIR *dir;
	struct dirent *cur;
	bool found = false;
	int len = strlen(path);
	char fpath[PATH_MAX], cpath[PATH_MAX];

	if (!isdir(path, false)) {
		return false;
	}

	if (!(dir = opendir(path))) {
		return false;
	}

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

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

		if (recursive && isdir(cpath, true)) {
			char tmp[PATH_MAX];

			if (find_csdb_object(tmp, cpath, code, is, true) && (!found || codecmp(tmp, dst) > 0)) {
				strcpy(dst, tmp);
				found = true;
			}
		} else if ((!is || is(cur->d_name)) && strmatch(code, cur->d_name)) {
			if (!found || codecmp(cpath, dst) > 0) {
				strcpy(dst, cpath);
				found = true;
			}
		}
	}

	closedir(dir);

	return found;
}

/* Find a CSDB object in a list of paths. */
bool find_csdb_object_in_list(char *dst, char (*objects)[PATH_MAX], int n, const char *code)
{
	int i;
	bool found = false;

	for (i = 0; i < n; ++i) {
		char *name, *base;

		name = strdup(objects[i]);
		base = basename(name);
		found = strmatch(code, base);
		free(name);

		if (found) {
			strcpy(dst, objects[i]);
			break;
		}
	}

	return found;
}

/* Convert string to double. Returns true if the string contained only a
 * correct double value or false if it contained extra content. */
static bool strtodbl(double *d, const char *s)
{
	char *e;
	*d = strtod(s, &e);
	return e != s && *e == 0;
}

/* Tests whether a value is in an S1000D range (a~c is equivalent to a|b|c) */
bool is_in_range(const char *value, const char *range)
{
	char *ran, *first, *last;
	bool ret;
	double f, l, v;

	if (!strchr(range, '~')) {
		return strcmp(value, range) == 0;
	}

	ran = malloc(strlen(range) + 1);

	strcpy(ran, range);

	first = strtok(ran, "~");
	last = strtok(NULL, "~");

	/* Attempt to compare the values numerically. If any of the values are
	 * non-numeric, fall back to lexicographic comparison.
	 *
	 * For example, if the range is 20~100, 30 falls in this range
	 * numerically but not lexicographically.
	 */
	if (strtodbl(&f, first) && strtodbl(&l, last) && strtodbl(&v, value)) {
		ret = v - f >= 0 && v - l <= 0;
	} else {
		ret = strcmp(value, first) >= 0 && strcmp(value, last) <= 0;
	}

	free(ran);

	return ret;
}

/* Tests whether a value is in an S1000D set (a|b|c) */
bool is_in_set(const char *value, const char *set)
{
	char *s, *val = NULL;
	bool ret = false;

	if (!strchr(set, '|')) {
		return is_in_range(value, set);
	}

	s = malloc(strlen(set) + 1);

	strcpy(s, set);

	while ((val = strtok(val ? NULL : s, "|"))) {
		if (is_in_range(value, val)) {
			ret = true;
			break;
		}
	}

	free(s);

	return ret;
}

/* Add a NOTATION to the DTD. */
void add_notation(xmlDocPtr doc, const xmlChar *name, const xmlChar *pubId, const xmlChar *sysId)
{
	xmlValidCtxtPtr valid;

	if (!doc->intSubset) {
		xmlCreateIntSubset(doc, xmlDocGetRootElement(doc)->name, NULL, NULL);
	}

	if (!xmlHashLookup(doc->intSubset->notations, BAD_CAST name)) {
		valid = xmlNewValidCtxt();
		xmlAddNotationDecl(valid, doc->intSubset, name, pubId, sysId);
		xmlFreeValidCtxt(valid);
	}
}

/* Add an ICN entity from a file path. */
xmlEntityPtr add_icn(xmlDocPtr doc, const char *path, bool fullpath)
{
	char *full, *base, *name;
	char *infoEntityIdent;
	char *notation;
	xmlEntityPtr e;

	full = strdup(path);
	base = basename(full);
	name = strdup(base);

	infoEntityIdent = strtok(name, ".");
	notation = strtok(NULL, "");

	add_notation(doc, BAD_CAST notation, NULL, BAD_CAST notation);
	e = xmlAddDocEntity(doc, BAD_CAST infoEntityIdent,
		XML_EXTERNAL_GENERAL_UNPARSED_ENTITY, NULL,
		BAD_CAST (fullpath ? path : base), BAD_CAST notation);

	free(name);
	free(full);

	return e;
}

/* Make a file read-only. */
void mkreadonly(const char *path)
{
	#ifdef _WIN32
	SetFileAttributesA(path, FILE_ATTRIBUTE_READONLY);
	#else
	struct stat st;
	stat(path, &st);
	chmod(path, (st.st_mode & 07777) & ~(S_IWUSR | S_IWGRP | S_IWOTH));
	#endif
}

/* Insert a child node instead of appending one. */
xmlNodePtr add_first_child(xmlNodePtr parent, xmlNodePtr child)
{
	if (parent->children) {
		return xmlAddPrevSibling(parent->children, child);
	} else {
		return xmlAddChild(parent, child);
	}
}

/* Convert string to lowercase. */
void lowercase(char *s)
{
	int i;
	for (i = 0; s[i]; ++i) {
		s[i] = tolower(s[i]);
	}
}

/* Convert string to uppercase. */
void uppercase(char *s)
{
	int i;
	for (i = 0; s[i]; ++i) {
		s[i] = toupper(s[i]);
	}
}

/* Return whether a bitset contains an option. */
bool optset(int opts, int opt)
{
	return ((opts & opt) == opt);
}

/* Read an XML document from a file. */
xmlDocPtr read_xml_doc(const char *path)
{
	xmlDocPtr doc;

	doc = xmlReadFile(path, NULL, DEFAULT_PARSE_OPTS);

	if (optset(DEFAULT_PARSE_OPTS, XML_PARSE_XINCLUDE)) {
		xmlXIncludeProcessFlags(doc, DEFAULT_PARSE_OPTS);
	}

	return doc;
}

/* Read an XML document from memory. */
xmlDocPtr read_xml_mem(const char *buffer, int size)
{
	xmlDocPtr doc;

	doc = xmlReadMemory(buffer, size, NULL, NULL, DEFAULT_PARSE_OPTS);

	if (optset(DEFAULT_PARSE_OPTS, XML_PARSE_XINCLUDE)) {
		xmlXIncludeProcessFlags(doc, DEFAULT_PARSE_OPTS);
	}

	return doc;
}

/* Save an XML document to a file. */
int save_xml_doc(xmlDocPtr doc, const char *path)
{
	return xmlSaveFile(path, doc);
}

/* Return the first node matching an XPath expression. */
xmlNodePtr xpath_first_node(xmlDocPtr doc, xmlNodePtr node, const xmlChar *path)
{
	xmlXPathContextPtr ctx;
	xmlXPathObjectPtr obj;
	xmlNodePtr first;

	if (doc) {
		ctx = xmlXPathNewContext(doc);
	} else {
		ctx = xmlXPathNewContext(node->doc);
	}

	ctx->node = node;

	obj = xmlXPathEvalExpression(BAD_CAST path, ctx);

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

	xmlXPathFreeObject(obj);
	xmlXPathFreeContext(ctx);

	return first;
}

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

/* Add a dependency test to an assertion if it contains any of the dependent
 * values.
 *
 * If the assertion uses a set (|), the values will be split up in order to
 * only add the dependency to the appropriate values.
 */
static void add_cct_depend_to_assert(xmlNodePtr *assert, const xmlChar *id, const xmlChar *forval, xmlNodePtr applic)
{
	xmlChar *vals, *v = NULL;
	bool match = false;
	xmlNodePtr eval;
	char *end = NULL;

	vals = xmlGetProp(*assert, BAD_CAST "applicPropertyValues");

	eval = xmlNewNode(NULL, BAD_CAST "evaluate");
	xmlSetProp(eval, BAD_CAST "andOr", BAD_CAST "or");

	/* Split any sets (|) */
	while ((v = BAD_CAST strtok_r(v ? NULL : (char *) vals, "|", &end))) {
		xmlNodePtr a;

		a = xmlNewNode(NULL, BAD_CAST "assert");
		xmlSetProp(a, BAD_CAST "applicPropertyIdent", id);
		xmlSetProp(a, BAD_CAST "applicPropertyType", BAD_CAST "condition");
		xmlSetProp(a, BAD_CAST "applicPropertyValues", v);

		/* If the current value has a dependency, add the depedency
		 * test to it with an AND evaluate.
		 */
		if (xmlStrcmp(v, forval) == 0) {
			xmlNodePtr cur, e;

			match = true;

			e = xmlNewChild(eval, NULL, BAD_CAST "evaluate", NULL);
			xmlSetProp(e, BAD_CAST "andOr", BAD_CAST "and");

			for (cur = applic->children; cur; cur = cur->next) {
				if (xmlStrcmp(cur->name, BAD_CAST "assert") == 0 || xmlStrcmp(cur->name, BAD_CAST "evaluate") == 0) {
					xmlAddChild(e, xmlCopyNode(cur, 1));
				}
			}

			xmlAddChild(e, a);
		} else {
			xmlAddChild(eval, a);
		}
	}

	xmlFree(vals);

	/* If any of the values in the set had a dependency, replace the assert
	 * with the new evaluation.
	 */
	if (match) {
		xmlChar *op;

		op = xmlGetProp((*assert)->parent, BAD_CAST "andOr");

		/* If the dependency test is being added to an OR evaluate,
		 * simplify the output by combining the new OR and the existing
		 * OR.
		 */
		if (!eval->children->next || (op && xmlStrcmp(op, BAD_CAST "or") == 0)) {
			xmlNodePtr cur, last;

			for (cur = eval->children, last = *assert; cur; cur = cur->next) {
				xmlChar *o;

				o = xmlGetProp(cur, BAD_CAST "andOr");

				/* Combine evaluates with the same operation. */
				if (o && xmlStrcmp(o, op) == 0) {
					xmlNodePtr c;

					for (c = cur->children; c; c = c->next) {
						last = xmlAddNextSibling(last, xmlCopyNode(c, 1));
					}
				} else {
					last = xmlAddNextSibling(last, xmlCopyNode(cur, 1));
				}

				xmlFree(o);
			}

			xmlFreeNode(eval);
		/* Otherwise, just add the new OR. */
		} else {
			xmlAddNextSibling(*assert, eval);
		}

		xmlFree(op);

		xmlUnlinkNode(*assert);
		xmlFreeNode(*assert);
		*assert = NULL;
	} else {
		xmlFreeNode(eval);
	}
}

/* Add a dependency from the CCT. */
static void add_cct_depend(xmlDocPtr doc, xmlNodePtr dep)
{
	xmlChar *id, *test, *vals, *xpath;
	int n;
	xmlNodePtr applic;

	id = xmlGetProp(dep->parent, BAD_CAST "id");
	test = xmlGetProp(dep, BAD_CAST "dependencyTest");
	vals = xmlGetProp(dep, BAD_CAST "forCondValues");

	/* Find the annotation for the dependency test. */
	n = xmlStrlen(test) + 17;
	xpath = malloc(n * sizeof(xmlChar));
	xmlStrPrintf(xpath, n, "//applic[@id='%s']", test);
	applic = xpath_first_node(dep->doc, NULL, xpath);
	xmlFree(xpath);

	if (applic) {
		xmlXPathContextPtr ctx;
		xmlXPathObjectPtr obj;
		xmlChar *v = NULL;
		char *end = NULL;

		ctx = xmlXPathNewContext(doc);

		/* Add dependency tests to assertions that use the dependant values. */
		while ((v = BAD_CAST strtok_r(v ? NULL : (char *) vals, "|", &end))) {
			n = xmlStrlen(id) + 70;
			xpath = malloc(n * sizeof(xmlChar));
			xmlStrPrintf(xpath, n, "//assert[@applicPropertyIdent='%s' and @applicPropertyType='condition']", id);
			obj = xmlXPathEvalExpression(xpath, ctx);
			xmlFree(xpath);

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

				for (i = 0; i < obj->nodesetval->nodeNr; ++i) {
					add_cct_depend_to_assert(&obj->nodesetval->nodeTab[i], id, v, applic);
				}
			}

			xmlXPathFreeObject(obj);
		}

		xmlXPathFreeContext(ctx);

		/* Handle subdependencies, that is, dependant values which themselves
		 * have dependencies.
		 */
		ctx = xmlXPathNewContext(applic->doc);
		ctx->node = applic;
		obj = xmlXPathEvalExpression(BAD_CAST ".//assert[@applicPropertyIdent and @applicPropertyType='condition']", ctx);

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

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

				ident = xmlGetProp(obj->nodesetval->nodeTab[i], BAD_CAST "applicPropertyIdent");
				add_cct_depends(doc, applic->doc, ident);
				xmlFree(ident);
			}
		}

		xmlXPathFreeObject(obj);
		xmlXPathFreeContext(ctx);
	}

	xmlFree(id);
	xmlFree(test);
	xmlFree(vals);
}

/* Add CCT dependencies to the source object.
 *
 * If id is NULL, all conditions will be added.
 *
 * If id is not NULL, then only the dependencies for condition with that id
 * will be added. This is useful when handling sub-depedencies, to only handle
 * the conditions which pertain to a particular dependency test.
 */
void add_cct_depends(xmlDocPtr doc, xmlDocPtr cct, xmlChar *id)
{
	xmlXPathContextPtr ctx;
	xmlXPathObjectPtr obj;

	ctx = xmlXPathNewContext(cct);

	if (id) {
		int n;
		xmlChar *xpath;

		n = xmlStrlen(id) + 26;
		xpath = malloc(n * sizeof(xmlChar));
		xmlStrPrintf(xpath, n, "//cond[@id='%s']/dependency", id);

		obj = xmlXPathEvalExpression(xpath, ctx);

		xmlFree(xpath);
	} else {
		obj = xmlXPathEvalExpression(BAD_CAST "//cond/dependency", ctx);
	}

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

		for (i = 0; i < obj->nodesetval->nodeNr; ++i) {
			add_cct_depend(doc, obj->nodesetval->nodeTab[i]);
		}
	}

	xmlXPathFreeObject(obj);
	xmlXPathFreeContext(ctx);
}

/* Test whether an object value matches a regex pattern. */
bool match_pattern(const xmlChar *value, const xmlChar *pattern)
{
	xmlRegexpPtr regex;
	bool match;
	regex = xmlRegexpCompile(BAD_CAST pattern);
	match = xmlRegexpExec(regex, BAD_CAST value);
	xmlRegFreeRegexp(regex);
	return match;
}

/* Display a progress bar. */
void print_progress_bar(float cur, float total)
{
	float p;
	int i, b;

	p = cur / total;
	b = PROGRESS_BAR_WIDTH * p;

	fprintf(stderr, "\r[");
	for (i = 0; i < PROGRESS_BAR_WIDTH; ++i) {
		if (i < b) {
			fputc('=', stderr);
		} else {
			fputc(' ', stderr);
		}
	}
	fprintf(stderr, "] %d%% (%d/%d) ", (int)(p * 100.0), (int) cur, (int) total);
	if (cur == total) {
		fputc('\n', stderr);
	}
	fflush(stderr);
}

/* Print progress information in the zenity --progress format. */
void print_zenity_progress(const char *message, float cur, float total)
{
	int p;

	p = (int)((cur / total) * 100.0);

	printf("# %s %d%% (%d/%d)\n%d\n", message, p, (int) cur, (int) total, p);
}

/* Determine if the file is an XML file. */
static bool is_xml(const char *name)
{
	return strncasecmp(name + strlen(name) - 4, ".XML", 4) == 0;
}

/* Determine if the file is a data module. */
bool is_dm(const char *name)
{
	return strncmp(name, "DMC-", 4) == 0 && is_xml(name);
}

/* Determine if the file is a publication module. */
bool is_pm(const char *name)
{
	return strncmp(name, "PMC-", 4) == 0 && is_xml(name);
}

/* Determine if the file is a comment. */
bool is_com(const char *name)
{
	return strncmp(name, "COM-", 4) == 0 && is_xml(name);
}

/* Determine if the file is an ICN metadata file. */
bool is_imf(const char *name)
{
	return strncmp(name, "IMF-", 4) == 0 && is_xml(name);
}

/* Determine if the file is a data dispatch note. */
bool is_ddn(const char *name)
{
	return strncmp(name, "DDN-", 4) == 0 && is_xml(name);
}

/* Determine if the file is a data management list. */
bool is_dml(const char *name)
{
	return strncmp(name, "DML-", 4) == 0 && is_xml(name);
}

/* Determine if the file is an ICN. */
bool is_icn(const char *name)
{
	return strncmp(name, "ICN-", 4) == 0;
}

/* Determine if the file is a SCORM content package. */
bool is_smc(const char *name)
{
	return (strncmp(name, "SMC-", 4) == 0 || strncmp(name, "SME-", 4) == 0) && is_xml(name);
}

/* Determine if the file is a data update file. */
bool is_upf(const char *name)
{
	return (strncmp(name, "UPF-", 4) == 0 || strncmp(name, "UPE-", 4) == 0) && is_xml(name);
}

/* Interpolate a command string with a file name and execute it. */
int execfile(const char *execstr, const char *path)
{
	int i, j, n, e, c = 0;
	char *fmtstr, *cmd;

	n = strlen(execstr);

	fmtstr = malloc(n * 2);

	for (i = 0, j = 0; i < n; ++i) {
		switch (execstr[i]) {
			case '{':
				if (execstr[i+1] && execstr[i+1] == '}') {
					fmtstr[j++] = '%';
					fmtstr[j++] = '1';
					fmtstr[j++] = '$';
					fmtstr[j++] = 's';
					++c;
					++i;
				}
				break;
			case '%':
			case '\\':
				fmtstr[j++] = execstr[i];
				fmtstr[j++] = execstr[i];
				break;
			default:
				fmtstr[j++] = execstr[i];
		}
	}

	fmtstr[j] = 0;

	n = strlen(fmtstr) + strlen(path) * c;
	cmd = malloc(n);
	snprintf(cmd, n, fmtstr, path);
	free(fmtstr);

	e = system(cmd);

	free(cmd);

	if (WIFSIGNALED(e)) {
		raise(WTERMSIG(e));
	}

	return WEXITSTATUS(e);
}

/* Copy only the latest issues of CSDB objects. */
int extract_latest_csdb_objects(char (*latest)[PATH_MAX], char (*files)[PATH_MAX], int nfiles)
{
	int i, nlatest = 0;
	for (i = 0; i < nfiles; ++i) {
		char *name1, *name2, *base1, *base2;

		name1 = strdup(files[i]);
		base1 = basename(name1);
		if (i > 0) {
			name2 = strdup(files[i - 1]);
			base2 = basename(name2);
		} else {
			name2 = NULL;
		}

		if (i == 0 || strncmp(base1, base2, strchr(base1, '_') - base1) != 0) {
			strcpy(latest[nlatest++], files[i]);
		} else {
			strcpy(latest[nlatest - 1], files[i]);
		}

		free(name1);
		free(name2);
	}
	return nlatest;
}

/* Compare the base names of two files. */
int compare_basename(const void *a, const void *b)
{
	char *sa, *sb, *ba, *bb;
	int d;

	sa = strdup((const char *) a);
	sb = strdup((const char *) b);
	ba = basename(sa);
	bb = basename(sb);

	d = strcasecmp(ba, bb);

	free(sa);
	free(sb);

	return d;
}

/* Determine if a CSDB object is a CIR. */
bool is_cir(const char *path, const bool ignore_del)
{
	xmlDocPtr doc;
	xmlXPathContextPtr ctx;
	xmlXPathObjectPtr obj;
	bool is;

	if (!(doc = read_xml_doc(path))) {
		return false;
	}

	ctx = xmlXPathNewContext(doc);

	/* Check that this is a CIR/TIR DM. */
	obj = xmlXPathEvalExpression(BAD_CAST "//commonRepository|//techRepository|//techrep", ctx);
	is = !xmlXPathNodeSetIsEmpty(obj->nodesetval);
	xmlXPathFreeObject(obj);

	/* Check that the DM is not "deleted" if ignore_del = true. */
	if (is && ignore_del) {
		obj = xmlXPathEvalExpression(BAD_CAST "//dmodule[identAndStatusSection/dmStatus/@issueType='deleted' or status/issno/@type='deleted']", ctx);
		is = xmlXPathNodeSetIsEmpty(obj->nodesetval);
		xmlXPathFreeObject(obj);
	}

	xmlXPathFreeContext(ctx);

	xmlFreeDoc(doc);

	return is;
}

/* Recursively remove nodes marked as "delete". */
static void rem_delete_nodes(xmlNodePtr node)
{
	xmlChar *change;
	xmlNodePtr cur;

	if (!node) {
		return;
	}

	change = xmlGetProp(node, BAD_CAST "change");

	if (!change) {
		change = xmlGetProp(node, BAD_CAST "changeType");
	}

	if (xmlStrcmp(change, BAD_CAST "delete") == 0) {
		xmlUnlinkNode(node);
		xmlFreeNode(node);
		return;
	}

	cur = node->children;
	while (cur) {
		xmlNodePtr next = cur->next;
		rem_delete_nodes(cur);
		cur = next;
	}
}

/* Remove elements marked as "delete". */
void rem_delete_elems(xmlDocPtr doc)
{
	rem_delete_nodes(xmlDocGetRootElement(doc));
}

/* Evaluate an applic statement, returning whether it is valid or invalid given
 * the user-supplied applicability settings.
 *
 * If assume is true, undefined attributes and conditions are ignored. This is
 * primarily useful for determining which elements are not applicable in content
 * and may be removed.
 *
 * If assume is false, undefined attributes or conditions will cause an applic
 * statement to evaluate as invalid. This is primarily useful for determining
 * which applic statements and references are unambiguously true (they do not
 * rely on any undefined attributes or conditions) and therefore may be removed.
 *
 * An undefined attribute/condition is a product attribute (ACT) or
 * condition (CCT) for which a value is asserted in the applic statement but
 * for which no value was supplied by the user.
 */
bool eval_applic(xmlNodePtr defs, xmlNodePtr node, bool assume);

/* Evaluate multiple values for a property */
static bool eval_multi(xmlNodePtr multi, const char *ident, const char *type, const char *value)
{
	xmlNodePtr cur;
	bool result = false;

	for (cur = multi->children; cur; cur = cur->next) {
		xmlChar *cur_value;
		bool in_set;

		cur_value = xmlNodeGetContent(cur);
		in_set = is_in_set((char *) cur_value, value);
		xmlFree(cur_value);

		if (in_set) {
			result = true;
			break;
		}
	}

	return result;
}

/* Tests whether ident:type=value was defined by the user */
bool is_applic(xmlNodePtr defs, const char *ident, const char *type, const char *value, bool assume)
{
	xmlNodePtr cur;

	bool result = assume;

	if (!(ident && type && value)) {
		return assume;
	}

	for (cur = defs->children; cur; cur = cur->next) {
		char *cur_ident = (char *) xmlGetProp(cur, BAD_CAST "applicPropertyIdent");
		char *cur_type  = (char *) xmlGetProp(cur, BAD_CAST "applicPropertyType");
		char *cur_value = (char *) xmlGetProp(cur, BAD_CAST "applicPropertyValues");

		bool match = strcmp(cur_ident, ident) == 0 && strcmp(cur_type, type) == 0;

		if (match) {
			if (cur_value) {
				result = is_in_set(cur_value, value);
			} else if (assume) {
				result = eval_multi(cur, ident, type, value);
			}
		}

		xmlFree(cur_ident);
		xmlFree(cur_type);
		xmlFree(cur_value);

		if (match) {
			break;
		}

	}

	return result;
}

/* Tests whether an <assert> element is applicable */
bool eval_assert(xmlNodePtr defs, xmlNodePtr assert, bool assume)
{
	xmlNodePtr ident_attr, type_attr, values_attr;
	char *ident, *type, *values;

	bool ret;

	ident_attr  = xpath_first_node(NULL, assert, BAD_CAST "@applicPropertyIdent|@actidref");
	type_attr   = xpath_first_node(NULL, assert, BAD_CAST "@applicPropertyType|@actreftype");
	values_attr = xpath_first_node(NULL, assert, BAD_CAST "@applicPropertyValues|@actvalues");

	ident  = (char *) xmlNodeGetContent(ident_attr);
	type   = (char *) xmlNodeGetContent(type_attr);
	values = (char *) xmlNodeGetContent(values_attr);

	ret = is_applic(defs, ident, type, values, assume);

	xmlFree(ident);
	xmlFree(type);
	xmlFree(values);

	return ret;
}

enum operator { AND, OR };

/* Test whether an <evaluate> element is applicable. */
bool eval_evaluate(xmlNodePtr defs, xmlNodePtr evaluate, bool assume)
{
	xmlChar *andOr;
	enum operator op;
	bool ret = assume;
	xmlNodePtr cur;

	andOr = xpath_first_value(NULL, evaluate, BAD_CAST "@andOr|@operator");

	if (!andOr) {
		return false;
	}

	if (xmlStrcmp(andOr, BAD_CAST "and") == 0) {
		op = AND;
	} else {
		op = OR;
	}

	xmlFree(andOr);

	for (cur = evaluate->children; cur; cur = cur->next) {
		if (xmlStrcmp(cur->name, BAD_CAST "assert") == 0 || xmlStrcmp(cur->name, BAD_CAST "evaluate") == 0) {
			ret = eval_applic(defs, cur, assume);

			if ((op == AND && !ret) || (op == OR && ret)) {
				break;
			}
		}
	}

	return ret;
}

/* Generic test for either <assert> or <evaluate> */
bool eval_applic(xmlNodePtr defs, xmlNodePtr node, bool assume)
{
	if (xmlStrcmp(node->name, BAD_CAST "assert") == 0) {
		return eval_assert(defs, node, assume);
	} else if (xmlStrcmp(node->name, BAD_CAST "evaluate") == 0) {
		return eval_evaluate(defs, node, assume);
	}

	return false;
}


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