/ .. / / -> download
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <getopt.h>
#include <string.h>
#include <stdbool.h>
#include <libxml/tree.h>
#include <libxml/xpath.h>
#include <libxml/xpathInternals.h>
#include <libxml/debugXML.h>
#include <libxslt/transform.h>
#include <libexslt/exslt.h>
#include "brex.h"
#include "s1kd_tools.h"

#define NONE 1
#define SAXON 2
#define XQILLA 3

#if XPATH2_ENGINE == 0
#undef XPATH2_ENGINE
#endif

#ifndef XPATH2_ENGINE
#define XPATH2_ENGINE NONE
#endif

#if XPATH2_ENGINE == SAXON
#include "saxon/saxon.h"
#elif XPATH2_ENGINE == XQILLA
#include "xqilla/xqilla.h"
#endif

/* Progress formats. */
#define PROGRESS_OFF 0
#define PROGRESS_CLI 1
#define PROGRESS_ZENITY 2

#define PROG_NAME "s1kd-brexcheck"
#define VERSION "4.10.0"

#define STRUCT_OBJ_RULE_PATH BAD_CAST \
	"//contextRules[not(@rulesContext) or @rulesContext=$schema]//structureObjectRule|" \
	"//contextrules[not(@context) or @context=$schema]//objrule"

/* Prefixes on console messages. */
#define E_PREFIX PROG_NAME ": ERROR: "
#define W_PREFIX PROG_NAME ": WARNING: "
#define F_PREFIX PROG_NAME ": FAILED: "
#define S_PREFIX PROG_NAME ": SUCCESS: "

/* Error messages. */
#define E_NODMOD E_PREFIX "Could not read file \"%s\".\n"
#define E_NODMOD_STDIN E_PREFIX "stdin does not contain valid XML.\n"
#define E_BAD_LIST E_PREFIX "Could not read list: %s\n"
#define E_MAXOBJS E_PREFIX "Out of memory\n"
#define E_NOBREX_LAYER E_PREFIX "No BREX data module found for BREX %s.\n"
#define E_BREX_NOT_FOUND E_PREFIX "Could not find BREX data module: %s\n"
#define E_NOBREX E_PREFIX "No BREX data module found for %s.\n"
#define E_NOBREX_STDIN E_PREFIX "No BREX data module found for object on stdin.\n"
#define E_BAD_XPATH_VERSION E_PREFIX "Unsupported XPath version: %s\n"

/* Warning messages. */
#define W_NOBREX W_PREFIX "%s does not reference a BREX data module.\n"
#define W_NOBREX_STDIN W_PREFIX "Object on stdin does not reference a BREX data module.\n"
#define W_INVOBJPATH W_PREFIX "Ignoring invalid object path in BREX %s (%ld): %s\n"

/* Failure messages. */
#define F_INVALIDDOC F_PREFIX "%s failed to validate against BREX %s.\n"

/* Success messages. */
#define S_VALIDDOC S_PREFIX "%s validated successfully against BREX %s.\n"

/* Exit status codes. */
#define EXIT_BREX_ERROR 1
#define EXIT_BAD_DMODULE 2
#define EXIT_BREX_NOT_FOUND 3
#define EXIT_BAD_XPATH_VERSION 4
#define EXIT_MAX_OBJS 5

/* URI for the XMLSchema-instance namespace. */
#define XSI_URI BAD_CAST "http://www.w3.org/2001/XMLSchema-instance"

/* Initial maximum numbers of CSDB objects/search paths. */
static unsigned BREX_MAX = 1;
static unsigned DMOD_MAX = 1;
static unsigned BREX_PATH_MAX = 1;

/* Verbosity of the tool's output. */
enum verbosity {SILENT, NORMAL, VERBOSE};

/* Whether to use short, single-line error messages. */
static bool shortmsg = false;

/* Business rules severity levels configuration file. */
static char *brsl_fname = NULL;
static xmlDocPtr brsl;

/* Print the filenames of invalid objects. */
static enum show_fnames { SHOW_NONE, SHOW_INVALID, SHOW_VALID } show_fnames = SHOW_NONE;

/* Search for BREX data modules recursively. */
static bool recursive_search = false;

/* Directory to start search for BREX data modules in. */
static char *search_dir = NULL;

/* Output XML tree if it passes the BREX check. */
static bool output_tree = false;

/* Ignore empty/non-XML files. */
static bool ignore_empty = false;

/* Remove elements marked as "delete" before check. */
static bool rem_delete = false;

/* Version of XPath to use. */
enum xpath_version { DYNAMIC, XPATH_1, XPATH_2 };

struct opts {
	enum verbosity verbosity;

	/* Whether to check layered BREX DMs. */
	bool layered;

	/* Whether to check object values. */
	bool check_values;

	/* Whether to check the SNS of specified data modules against the SNS
	 * rules defined in the BREX data modules.
	 *
	 * In normal SNS check mode, optional levels that are omitted from the
	 * SNS rules only allow the value of '0' (or '00'/'0000' for the
	 * assyCode). Any other code is treated as invalid.
	 */
	bool check_sns;

	/* In strict SNS check mode, all levels of the SNS must be explicitly
	 * defined in the SNS rules, otherwise an error will be reported.
	 */
	bool strict_sns;

	/* In unstrict SNS check mode, if an optional level is omitted from the
	 * SNS rules, that is interpreted as allowing ANY code.
	 */
	bool unstrict_sns;

	/* Whether to check notation rules, that is, what NOTATIONs are allowed in
	 * the DTD.
	 */
	bool check_notations;

	/* Assume object filenames do not include issue info. */
	bool ignore_issue;

	/* Force a version of XPath to be used. */
	enum xpath_version xpath_version;
};

/* Return the first node in a set matching an XPath expression. */
static xmlNodePtr firstXPathNode(xmlDocPtr doc, xmlNodePtr context, const char *xpath)
{
	xmlXPathContextPtr ctx;
	xmlXPathObjectPtr obj;
	xmlNodePtr node;

	ctx = xmlXPathNewContext(doc ? doc : context->doc);
	ctx->node = context;

	obj = xmlXPathEvalExpression(BAD_CAST xpath, ctx);

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

	xmlXPathFreeObject(obj);
	xmlXPathFreeContext(ctx);

	return node;
}

/* Return the string value of the first node matching an XPath expression. */
static xmlChar *firstXPathValue(xmlNodePtr node, const char *expr)
{
	return xmlNodeGetContent(firstXPathNode(NULL, node, expr));
}

/* Check the values of objects against the patterns in the BREX rule. */
static bool check_node_values(xmlNodePtr node, xmlNodeSetPtr values)
{
	int i;
	bool ret = false;

	if (xmlXPathNodeSetIsEmpty(values))
		return true;

	for (i = 0; i < values->nodeNr; ++i) {
		xmlChar *allowed, *value, *form;

		allowed = firstXPathValue(values->nodeTab[i], "@valueAllowed|@val1");
		form    = firstXPathValue(values->nodeTab[i], "@valueForm|@valtype");
		value   = xmlNodeGetContent(node);

		if (form && xmlStrcmp(form, BAD_CAST "range") == 0) {
			ret = ret || is_in_set((char *) value, (char *) allowed);
		} else if (form && xmlStrcmp(form, BAD_CAST "pattern") == 0) {
			ret = ret || match_pattern(value, allowed);
		} else {
			ret = ret || xmlStrcmp(value, allowed) == 0;
		}

		xmlFree(allowed);
		xmlFree(form);
		xmlFree(value);
	}

	return ret;
}

/* Check an individual node's value against a rule. */
static bool check_single_object_values(xmlNodePtr rule, xmlNodePtr node)
{
	xmlXPathContextPtr ctx;
	xmlXPathObjectPtr obj;
	bool ret;

	ctx = xmlXPathNewContext(rule->doc);
	ctx->node = rule;

	obj = xmlXPathEvalExpression(BAD_CAST "objectValue|objval", ctx);

	if (!xmlXPathNodeSetIsEmpty(obj->nodesetval)) {
		ret = check_node_values(node, obj->nodesetval);
	} else {
		ret = false;
	}

	xmlXPathFreeObject(obj);
	xmlXPathFreeContext(ctx);

	return ret;
}

/* Check the values of a set of nodes against a rule. */
static bool check_objects_values(xmlNodePtr rule, xmlNodeSetPtr nodes)
{
	xmlXPathContextPtr ctx;
	xmlXPathObjectPtr obj;
	bool ret = true;

	if (xmlXPathNodeSetIsEmpty(nodes))
		return true;

	ctx = xmlXPathNewContext(rule->doc);
	ctx->node = rule;

	obj = xmlXPathEvalExpression(BAD_CAST "objectValue|objval", ctx);

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

		for (i = 0; i < nodes->nodeNr; ++i) {
			if (!check_node_values(nodes->nodeTab[i], obj->nodesetval)) {
				ret = false;
				break;
			}
		}
	}

	xmlXPathFreeObject(obj);
	xmlXPathFreeContext(ctx);

	return ret;
}

/* Determine whether a BREX context rule is violated. */
static bool is_invalid(xmlNodePtr rule, char *allowedObjectFlag, xmlXPathObjectPtr obj, struct opts *opts)
{
	bool invalid = false;

	if (allowedObjectFlag) {
		if (strcmp(allowedObjectFlag, "0") == 0) {
			if (xmlXPathNodeSetIsEmpty(obj->nodesetval)) {
				invalid = obj->boolval;
			} else {
				invalid = true;
			}
		} else if (strcmp(allowedObjectFlag, "1") == 0) {
			if (xmlXPathNodeSetIsEmpty(obj->nodesetval)) {
				invalid = !obj->boolval;
			} else {
				invalid = false;
			}
		}
	}

	if (!invalid && opts->check_values)
		invalid = !check_objects_values(rule, obj->nodesetval);

	return invalid;
}

/* Dump the XML branches that violate a given BREX context rule. */
static void dump_nodes_xml(xmlNodeSetPtr nodes, const char *fname, xmlNodePtr brexError, xmlNodePtr rule, struct opts *opts)
{
	int i;

	for (i = 0; i < nodes->nodeNr; ++i) {
		xmlNodePtr node = nodes->nodeTab[i];
		xmlNodePtr object;
		char line_s[16];
		xmlChar *xpath;

		if (opts->check_values && check_single_object_values(rule, node)) {
			continue;
		}

		snprintf(line_s, 16, "%ld", xmlGetLineNo(node));
		object = xmlNewChild(brexError, NULL, BAD_CAST "object", NULL);
		xmlSetProp(object, BAD_CAST "line", BAD_CAST line_s);

		xpath = xpath_of(node);
		xmlSetProp(object, BAD_CAST "xpath", xpath);
		xmlFree(xpath);

		if (node->type == XML_ATTRIBUTE_NODE) node = node->parent;

		xmlAddChild(object, xmlCopyNode(node, 2));
	}
}

/* Determine whether a file in the filesystem is an XML file by extension. */
static bool is_xml_file(const char *fname)
{
	return strcasecmp(fname + (strlen(fname) - 4), ".XML") == 0;
}

/* Search for the BREX in the built-in default BREX data modules. */
static bool search_brex_fname_from_default_brex(char *fname, char *dmcode, int len)
{
	if (strncasecmp(dmcode, "DMC-S1000D-G-04-10-0301-00A-022A-D_001-00_EN-US", len) == 0 || strncasecmp(dmcode, "DMC-S1000D-G-04-10-0301-00A-022A-D_\?\?\?-\?\?_EN-US", len) == 0) return strcpy(fname, "DMC-S1000D-G-04-10-0301-00A-022A-D");
	if (strncasecmp(dmcode, "DMC-S1000D-F-04-10-0301-00A-022A-D_001-00_EN-US", len) == 0 || strncasecmp(dmcode, "DMC-S1000D-F-04-10-0301-00A-022A-D_\?\?\?-\?\?_EN-US", len) == 0) return strcpy(fname, "DMC-S1000D-F-04-10-0301-00A-022A-D");
	if (strncasecmp(dmcode, "DMC-S1000D-E-04-10-0301-00A-022A-D_012-00_EN-US", len) == 0 || strncasecmp(dmcode, "DMC-S1000D-E-04-10-0301-00A-022A-D_\?\?\?-\?\?_EN-US", len) == 0) return strcpy(fname, "DMC-S1000D-E-04-10-0301-00A-022A-D");
	if (strncasecmp(dmcode, "DMC-S1000D-D-04-10-0301-00A-022A-D_006-00_EN-US", len) == 0 || strncasecmp(dmcode, "DMC-S1000D-D-04-10-0301-00A-022A-D_\?\?\?-\?\?_EN-US", len) == 0) return strcpy(fname, "DMC-S1000D-D-04-10-0301-00A-022A-D");
	if (strncasecmp(dmcode, "DMC-S1000D-A-04-10-0301-00A-022A-D_005-00_EN-US", len) == 0 || strncasecmp(dmcode, "DMC-S1000D-A-04-10-0301-00A-022A-D_\?\?\?-\?\?_EN-US", len) == 0) return strcpy(fname, "DMC-S1000D-A-04-10-0301-00A-022A-D");
	if (strncasecmp(dmcode, "DMC-AE-A-04-10-0301-00A-022A-D_003-00_EN-US", len) == 0 || strncasecmp(dmcode, "DMC-AE-A-04-10-0301-00A-022A-D_\?\?\?-\?\?_EN-US", len) == 0) return strcpy(fname, "DMC-AE-A-04-10-0301-00A-022A-D");
	return false;
}

/* Find the filename of a BREX data module referenced by a CSDB object.
 * -1  Object does not reference a BREX DM.
 *  0  Object references a BREX DM, and it was found.
 *  1  Object references a BREX DM, but it couldn't be found.
 */
static int find_brex_fname_from_doc(char *fname, xmlDocPtr doc, char (*spaths)[PATH_MAX],
	int nspaths, char (*dmod_fnames)[PATH_MAX], int num_dmod_fnames,
	struct opts *opts)
{
	xmlXPathContextPtr context;
	xmlXPathObjectPtr object;

	xmlNodePtr brexDmRef, dmCode, issueInfo, language;

	char *modelIdentCode;
	char *systemDiffCode;
	char *systemCode;
	char *subSystemCode;
	char *subSubSystemCode;
	char *assyCode;
	char *disassyCode;
	char *disassyCodeVariant;
	char *infoCode;
	char *infoCodeVariant;
	char *itemLocationCode;

	char dmcode[256];
	int len;

	bool found;

	context = xmlXPathNewContext(doc);

	object = xmlXPathEval(BAD_CAST "//brexDmRef|//brexref", context);
	if (xmlXPathNodeSetIsEmpty(object->nodesetval)) {
		xmlXPathFreeObject(object);
		xmlXPathFreeContext(context);
		return -1;
	} else {
		brexDmRef = object->nodesetval->nodeTab[0];
	}
	xmlXPathFreeObject(object);

	xmlXPathSetContextNode(brexDmRef, context);

	object = xmlXPathEval(BAD_CAST ".//dmCode|.//avee", context);
	if (xmlXPathNodeSetIsEmpty(object->nodesetval)) {
		dmCode = NULL;
	} else {
		dmCode = object->nodesetval->nodeTab[0];
	}
	xmlXPathFreeObject(object);

	object = xmlXPathEval(BAD_CAST ".//issueInfo|.//issno", context);
	if (xmlXPathNodeSetIsEmpty(object->nodesetval)) {
		issueInfo = NULL;
	} else {
		issueInfo = object->nodesetval->nodeTab[0];
	}
	xmlXPathFreeObject(object);

	object = xmlXPathEval(BAD_CAST ".//language", context);
	if (xmlXPathNodeSetIsEmpty(object->nodesetval)) {
		language = NULL;
	} else {
		language = object->nodesetval->nodeTab[0];
	}
	xmlXPathFreeObject(object);

	xmlXPathFreeContext(context);

	if (xmlStrcmp(dmCode->name, BAD_CAST "dmCode") == 0) {
		modelIdentCode     = (char *) xmlGetProp(dmCode, BAD_CAST "modelIdentCode");
		systemDiffCode     = (char *) xmlGetProp(dmCode, BAD_CAST "systemDiffCode");
		systemCode         = (char *) xmlGetProp(dmCode, BAD_CAST "systemCode");
		subSystemCode      = (char *) xmlGetProp(dmCode, BAD_CAST "subSystemCode");
		subSubSystemCode   = (char *) xmlGetProp(dmCode, BAD_CAST "subSubSystemCode");
		assyCode           = (char *) xmlGetProp(dmCode, BAD_CAST "assyCode");
		disassyCode        = (char *) xmlGetProp(dmCode, BAD_CAST "disassyCode");
		disassyCodeVariant = (char *) xmlGetProp(dmCode, BAD_CAST "disassyCodeVariant");
		infoCode           = (char *) xmlGetProp(dmCode, BAD_CAST "infoCode");
		infoCodeVariant    = (char *) xmlGetProp(dmCode, BAD_CAST "infoCodeVariant");
		itemLocationCode   = (char *) xmlGetProp(dmCode, BAD_CAST "itemLocationCode");
	} else {
		modelIdentCode     = (char *) firstXPathValue(dmCode, "modelic");
		systemDiffCode     = (char *) firstXPathValue(dmCode, "sdc");
		systemCode         = (char *) firstXPathValue(dmCode, "chapnum");
		subSystemCode      = (char *) firstXPathValue(dmCode, "section");
		subSubSystemCode   = (char *) firstXPathValue(dmCode, "subsect");
		assyCode           = (char *) firstXPathValue(dmCode, "subject");
		disassyCode        = (char *) firstXPathValue(dmCode, "discode");
		disassyCodeVariant = (char *) firstXPathValue(dmCode, "discodev");
		infoCode           = (char *) firstXPathValue(dmCode, "incode");
		infoCodeVariant    = (char *) firstXPathValue(dmCode, "incodev");
		itemLocationCode   = (char *) firstXPathValue(dmCode, "itemloc");
	}

	snprintf(dmcode, 256, "DMC-%s-%s-%s-%s%s-%s-%s%s-%s%s-%s",
		modelIdentCode,
		systemDiffCode,
		systemCode,
		subSystemCode,
		subSubSystemCode,
		assyCode,
		disassyCode,
		disassyCodeVariant,
		infoCode,
		infoCodeVariant,
		itemLocationCode);

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

	if (!opts->ignore_issue) {
		if (issueInfo) {
			char *issue_number, *in_work;
			char iss[8];

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

			snprintf(iss, 8, "_%s-%s", issue_number, in_work ? in_work : "00");
			strcat(dmcode, iss);

			xmlFree(issue_number);
			xmlFree(in_work);
		} else if (language) {
			strcat(dmcode, "_\?\?\?-\?\?");
		}
	}

	if (language) {
		char *language_iso_code, *country_iso_code;
		char lang[8];

		language_iso_code = (char *) firstXPathValue(language, "@languageIsoCode|@language");
		country_iso_code  = (char *) firstXPathValue(language, "@countryIsoCode|@country");

		snprintf(lang, 8, "_%s-%s", language_iso_code, country_iso_code);
		strcat(dmcode, lang);

		xmlFree(language_iso_code);
		xmlFree(country_iso_code);
	}

	len = strlen(dmcode);

	/* Look for the BREX in the current directory. */
	found = find_csdb_object(fname, search_dir, dmcode, is_xml_file, recursive_search);

	/* Look for the BREX in any of the specified search paths. */
	if (!found) {
		int i;

		for (i = 0; i < nspaths; ++i) {
			found = find_csdb_object(fname, spaths[i], dmcode, is_xml_file, recursive_search);
		}
	}

	/* Look for the BREX in the list of objects to check. */
	if (!found) {
		found = find_csdb_object_in_list(fname, dmod_fnames, num_dmod_fnames, dmcode);
	}

	/* Look for the BREX in the built-in default BREX. */
	if (!found) {
		found = search_brex_fname_from_default_brex(fname, dmcode, len);
	}

	if (opts->verbosity > SILENT && !found) {
		fprintf(stderr, E_BREX_NOT_FOUND, dmcode);
	}

	return !found;
}

/* Determine whether a violated rule counts as a failure, based on its
 * business rule severity level.
 */
static bool is_failure(xmlChar *severity)
{
	xmlXPathContextPtr ctx;
	xmlXPathObjectPtr obj;

	bool ret = true;

	ctx = xmlXPathNewContext(brsl);
	obj = xmlXPathEvalExpression(BAD_CAST "//brSeverityLevel", ctx);

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

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

			value = xmlGetProp(obj->nodesetval->nodeTab[i], BAD_CAST "value");
			match = xmlStrcmp(value, severity) == 0;
			xmlFree(value);

			if (match) {
				xmlChar *fail;

				fail = xmlGetProp(obj->nodesetval->nodeTab[i], BAD_CAST "fail");
				ret = xmlStrcmp(fail, BAD_CAST "no") != 0;
				xmlFree(fail);
				break;
			}
		}
	}

	xmlXPathFreeObject(obj);
	xmlXPathFreeContext(ctx);

	return ret;
}

/* Extract the type name of a business rule severity level. */
static xmlChar *brsl_type(xmlChar *severity)
{
	xmlXPathContextPtr ctx;
	xmlXPathObjectPtr obj;
	xmlChar *type;

	ctx = xmlXPathNewContext(brsl);
	xmlXPathRegisterVariable(ctx, BAD_CAST "severity", xmlXPathNewString(severity));
	obj = xmlXPathEvalExpression(BAD_CAST "//brSeverityLevel[@value=$severity]", ctx);

	if (xmlXPathNodeSetIsEmpty(obj->nodesetval)) {
		type = NULL;
	} else {
		type = xmlNodeGetContent(obj->nodesetval->nodeTab[0]);
	}
	
	xmlXPathFreeObject(obj);
	xmlXPathFreeContext(ctx);

	return type;
}

/* Copy the allowed object values to the XML report. */
static void add_object_values(xmlNodePtr brexError, xmlNodePtr rule)
{
	xmlXPathContextPtr ctx;
	xmlXPathObjectPtr obj;

	ctx = xmlXPathNewContext(rule->doc);
	ctx->node = rule;

	obj = xmlXPathEvalExpression(BAD_CAST "objectValue|objval", ctx);

	if (!xmlXPathNodeSetIsEmpty(obj->nodesetval)) {
		int i;
		for (i = 0; i < obj->nodesetval->nodeNr; ++i) {
			xmlAddChild(brexError, xmlCopyNode(obj->nodesetval->nodeTab[i], 1));
		}
	}

	xmlXPathFreeObject(obj);
	xmlXPathFreeContext(ctx);
}

/* Print the XML report as plain text messages */
static void print_node(xmlNodePtr node)
{
	xmlNodePtr cur;

	if (strcmp((char *) node->name, "error") == 0) {
		char *dpath = (char *) xmlGetProp(node->parent->parent, BAD_CAST "path");
		char *bpath = (char *) xmlGetProp(node->parent, BAD_CAST "path");
		if (xmlStrcmp(node->parent->name, BAD_CAST "sns") == 0) {
			if (shortmsg) {
				fprintf(stderr, "SNS ERROR: %s: ", dpath);
			} else {
				fprintf(stderr, "SNS ERROR: %s\n", dpath);
			}
		} else if (xmlStrcmp(node->parent->name, BAD_CAST "notations") == 0) {
			if (shortmsg) {
				fprintf(stderr, "NOTATION ERROR: %s: ", dpath);
			} else {
				fprintf(stderr, "NOTATION ERROR: %s\n", dpath);
			}
		} else {
			if (shortmsg) {
				fprintf(stderr, "BREX ERROR: %s: ", dpath);
			} else {
				fprintf(stderr, "BREX ERROR: %s\n", dpath);
				fprintf(stderr, "  BREX: %s\n", bpath);
			}
		}
		xmlFree(dpath);
		xmlFree(bpath);
	} else if (strcmp((char *) node->name, "type") == 0 && !shortmsg) {
		char *type = (char *) xmlNodeGetContent(node);
		fprintf(stderr, "  TYPE: %s\n", type);
		xmlFree(type);
	} else if (xmlStrcmp(node->name, BAD_CAST "brDecisionRef") == 0) {
		xmlChar *brdp = xmlGetProp(node, BAD_CAST "brDecisionIdentNumber");
		if (shortmsg) {
			fprintf(stderr, "%s: ", (char *) brdp);
		} else {
			fprintf(stderr, "  %s\n", (char *) brdp);
		}
		xmlFree(brdp);
	} else if (strcmp((char *) node->name, "objectUse") == 0) {
		char *use = (char *) xmlNodeGetContent(node);
		if (shortmsg) {
			fprintf(stderr, "%s", use);
		} else {
			fprintf(stderr, "  %s\n", use);
		}
		xmlFree(use);
	} else if (strcmp((char *) node->name, "objectValue") == 0 && !shortmsg) {
		char *allowed = (char *) xmlGetProp(node, BAD_CAST "valueAllowed");
		char *content = (char *) xmlNodeGetContent(node);
		fprintf(stderr, "  VALUE ALLOWED:");
		if (allowed)
			fprintf(stderr, " %s", allowed);
		if (content && strcmp(content, "") != 0)
			fprintf(stderr, " (%s)", content);
		fputc('\n', stderr);
		xmlFree(content);
		xmlFree(allowed);
	} else if (strcmp((char *) node->name, "objval") == 0 && !shortmsg) {
		char *allowed = (char *) xmlGetProp(node, BAD_CAST "val1");
		char *content = (char *) xmlNodeGetContent(node);
		fprintf(stderr, "  VALUE ALLOWED:");
		if (allowed)
			fprintf(stderr, " %s", allowed);
		if (content && strcmp(content, "") != 0)
			fprintf(stderr, " (%s)", content);
		fputc('\n', stderr);
		xmlFree(content);
		xmlFree(allowed);
	} else if (strcmp((char *) node->name, "object") == 0 && !shortmsg) {
		char *line = (char *) xmlGetProp(node, BAD_CAST "line");
		char *path = (char *) xmlGetProp(node, BAD_CAST "xpath");
		fprintf(stderr, "  line %s (%s):\n", line, path);
		xmlDebugDumpOneNode(stderr, node->children, 2);
		xmlFree(line);
		xmlFree(path);
	} else if (strcmp((char *) node->name, "code") == 0) {
		char *code = (char *) xmlNodeGetContent(node);
		if (!shortmsg) fprintf(stderr, "  ");
		fprintf(stderr, "Value of %s does not conform to SNS: ", code);
		xmlFree(code);
	} else if (strcmp((char *) node->name, "invalidValue") == 0) {
		char *value = (char *) xmlNodeGetContent(node);
		if (shortmsg) {
			fprintf(stderr, "%s", value);
		} else {
			fprintf(stderr, "%s\n", value);
		}
		xmlFree(value);
	} else if (strcmp((char *) node->name, "invalidNotation") == 0) {
		char *value = (char *) xmlNodeGetContent(node);
		if (!shortmsg) fprintf(stderr, "  ");
		fprintf(stderr, "Notation %s is not allowed", value);
		if (shortmsg)
			fprintf(stderr, ": ");
		else
			fprintf(stderr, ".\n");
		xmlFree(value);
	}

	for (cur = node->children; cur; cur = cur->next) {
		print_node(cur);
	}

	if (shortmsg && xmlStrcmp(node->name, BAD_CAST "error") == 0) {
		fputc('\n', stderr);
	}
}

/* Register extra XPath functions in a new XPath context. */
static void register_functions(xmlXPathContextPtr ctx)
{
	exsltDateXpathCtxtRegister(ctx, BAD_CAST "date");
	exsltMathXpathCtxtRegister(ctx, BAD_CAST "math");
	exsltSetsXpathCtxtRegister(ctx, BAD_CAST "set");
	exsltStrXpathCtxtRegister(ctx, BAD_CAST "str");
}

/* Register all namespaces applicable to a node in a new XPath context. */
#if XPATH2_ENGINE == SAXON
static void register_namespaces(xmlXPathContextPtr ctx, void *saxon_xpath, xmlNodePtr node)
#elif XPATH2_ENGINE == XQILLA
static void register_namespaces(xmlXPathContextPtr ctx, void *xqilla_ns_resolver, xmlNodePtr node)
#else
static void register_namespaces(xmlXPathContextPtr ctx, xmlNodePtr node)
#endif
{
	xmlNodePtr cur;

	for (cur = node; cur; cur = cur->parent) {
		if (cur->nsDef) {
			xmlNsPtr cur_ns;
			for (cur_ns = cur->nsDef; cur_ns; cur_ns = cur_ns->next) {
				xmlXPathRegisterNs(ctx, cur_ns->prefix, cur_ns->href);

#if XPATH2_ENGINE == SAXON
				if (saxon_xpath) {
					saxon_register_namespace(saxon_xpath, cur_ns->prefix, cur_ns->href);
				}
#elif XPATH2_ENGINE == XQILLA
				if (xqilla_ns_resolver) {
					xqilla_register_namespace(xqilla_ns_resolver, cur_ns->prefix, cur_ns->href);
				}
#endif

			}
		}
	}
}

/* Check the context rules of a BREX DM against a CSDB object. */
#if XPATH2_ENGINE == SAXON
static int check_brex_rules(xmlDocPtr brex_doc, xmlNodeSetPtr rules, xmlDocPtr doc, const char *fname, const char *brexfname, xmlNodePtr documentNode, struct opts *opts, void *saxon_processor, void *saxon_node)
#elif XPATH2_ENGINE == XQILLA
static int check_brex_rules(xmlDocPtr brex_doc, xmlNodeSetPtr rules, xmlDocPtr doc, const char *fname, const char *brexfname, xmlNodePtr documentNode, struct opts *opts, void *xqilla_doc)
#else
static int check_brex_rules(xmlDocPtr brex_doc, xmlNodeSetPtr rules, xmlDocPtr doc, const char *fname, const char *brexfname, xmlNodePtr documentNode, struct opts *opts)
#endif
{
	xmlChar *defaultBrSeverityLevel;
	int nerr = 0;
	xmlNodePtr brexNode;

	defaultBrSeverityLevel = xmlGetProp(firstXPathNode(brex_doc, NULL, "//brex"), BAD_CAST "defaultBrSeverityLevel");

	brexNode = xmlNewChild(documentNode, NULL, BAD_CAST "brex", NULL);
	xmlSetProp(brexNode, BAD_CAST "path", BAD_CAST brexfname);

	if (!xmlXPathNodeSetIsEmpty(rules)) {
		int i;

		for (i = 0; i < rules->nodeNr; ++i) {
			xmlXPathContextPtr context;
			xmlXPathObjectPtr object;
			xmlNodePtr brDecisionRef, objectPath, objectUse;
			xmlChar *allowedObjectFlag, *path, *use, *brdp;

#if XPATH2_ENGINE == SAXON
			void *xpath_processor;

			if (saxon_processor) {
				xpath_processor = saxon_new_xpath_processor(saxon_processor);
			} else {
				xpath_processor = NULL;
			}
#elif XPATH2_ENGINE == XQILLA
			void *xqilla_ns_resolver;

			if (xqilla_doc) {
				xqilla_ns_resolver = xqilla_create_ns_resolver(xqilla_doc);
			} else {
				xqilla_ns_resolver = NULL;
			}
#endif

			brDecisionRef = firstXPathNode(brex_doc, rules->nodeTab[i], "brDecisionRef");
			objectPath = firstXPathNode(brex_doc, rules->nodeTab[i], "objectPath|objpath");
			objectUse  = firstXPathNode(brex_doc, rules->nodeTab[i], "objectUse|objuse");

			brdp = xmlGetProp(brDecisionRef, BAD_CAST "brDecisionIdentNumber");
			allowedObjectFlag = firstXPathValue(objectPath, "@allowedObjectFlag|@objappl");
			path = xmlNodeGetContent(objectPath);
			use  = xmlNodeGetContent(objectUse);

			context = xmlXPathNewContext(doc);
			register_functions(context);

#if XPATH2_ENGINE == SAXON
			register_namespaces(context, xpath_processor, objectPath);

			if (xpath_processor) {
				object = saxon_eval_xpath(saxon_processor, xpath_processor, saxon_node, path, context);
			} else {
				object = xmlXPathEval(path, context);
			}
#elif XPATH2_ENGINE == XQILLA
			register_namespaces(context, xqilla_ns_resolver, objectPath);

			if (xqilla_ns_resolver) {
				object = xqilla_eval_xpath(xqilla_doc, xqilla_ns_resolver, path, context);
			} else {
				object = xmlXPathEval(path, context);
			}
#else
			register_namespaces(context, objectPath);

			object = xmlXPathEvalExpression(path, context);
#endif

			if (object != NULL) {
				if (is_invalid(rules->nodeTab[i], (char *) allowedObjectFlag, object, opts)) {
					xmlChar *severity;
					xmlNodePtr brexError, err_path;

					if (!(severity = xmlGetProp(rules->nodeTab[i], BAD_CAST "brSeverityLevel"))) {
						severity = xmlStrdup(defaultBrSeverityLevel);
					}

					brexError = xmlNewChild(brexNode, NULL, BAD_CAST "error", NULL);

					if (severity) {
						xmlSetProp(brexError, BAD_CAST "brSeverityLevel", severity);

						if (brsl_fname) {
							xmlChar *type = brsl_type(severity);
							xmlNewChild(brexError, NULL, BAD_CAST "type", type);
							xmlFree(type);
						}
					} else {
						xmlSetProp(brexError, BAD_CAST "fail", BAD_CAST "yes");
					}

					if (brDecisionRef) {
						xmlAddChild(brexError, xmlCopyNode(brDecisionRef, 1));
					}

					err_path = xmlNewChild(brexError, NULL, BAD_CAST "objectPath", path);
					xmlSetProp(err_path, BAD_CAST "allowedObjectFlag", allowedObjectFlag);
					xmlNewChild(brexError, NULL, BAD_CAST "objectUse", use);

					add_object_values(brexError, rules->nodeTab[i]);

					if (!xmlXPathNodeSetIsEmpty(object->nodesetval)) {
						dump_nodes_xml(object->nodesetval, fname,
							brexError, rules->nodeTab[i],
							opts);
					}

					if (severity) {
						if (is_failure(severity)) {
							++nerr;
						} else {
							xmlSetProp(brexError, BAD_CAST "fail", BAD_CAST "no");
						}
					} else {
						++nerr;
					}

					xmlFree(severity);

					if (opts->verbosity > SILENT) {
						print_node(brexError);
					}
				}
			} else {
				long int line;
				xmlChar line_s[16];
				xmlChar *xpath;
				xmlNodePtr xpath_err;

				line = xmlGetLineNo(objectPath);
				xmlStrPrintf(line_s, 16, "%ld", line);

				xpath = xpath_of(objectPath);

				xpath_err = xmlNewChild(brexNode, brexNode->ns, BAD_CAST "xpathError", path);
				xmlSetProp(xpath_err, BAD_CAST "line", line_s);
				xmlSetProp(xpath_err, BAD_CAST "xpath", xpath);

				xmlFree(xpath);

				if (opts->verbosity > SILENT) {
					fprintf(stderr, W_INVOBJPATH, brexfname, line, path);
				}
			}

#if XPATH2_ENGINE == SAXON
			saxon_free_xpath_processor(xpath_processor);
#endif
			/* FIXME: If the XPath expression was invalid, xmlXPathFreeObject doesn't
			 *        seem to free everything, so there will be a memory leak. */
			xmlXPathFreeObject(object);
			xmlXPathFreeContext(context);
			xmlFree(brdp);
			xmlFree(allowedObjectFlag);
			xmlFree(path);
			xmlFree(use);
		}
	}

	if (!brexNode->children) {
		xmlNewChild(brexNode, NULL, BAD_CAST "noErrors", NULL);
	}

	xmlFree(defaultBrSeverityLevel);

	return nerr;
}

/* Load a BREX DM from the filesystem or from in-memory. */
static xmlDocPtr load_brex(const char *name, xmlDocPtr dmod_doc)
{
	/* If the BREX name is -, this means the DM on stdin is a BREX DM.
	 * BREX DMs are checked against themselves, so return a copy of the
	 * same document.
	 */
	if (strcmp(name, "-") == 0) {
		return xmlCopyDoc(dmod_doc, 1);
	/* If the BREX name is an existing filename, read from that. */
	} else if (access(name, F_OK) != -1) {
		return read_xml_doc(name);
	/* If the BREX name is one of the standard Default BREX codes, read
	 * it from memory.
	 */
	} else {
		unsigned char *xml = NULL;
		unsigned int len = 0;

		if (strcmp(name, "DMC-S1000D-G-04-10-0301-00A-022A-D") == 0) {
			xml = brex_DMC_S1000D_G_04_10_0301_00A_022A_D_001_00_EN_US_XML;
			len = brex_DMC_S1000D_G_04_10_0301_00A_022A_D_001_00_EN_US_XML_len;
		} else if (strcmp(name, "DMC-S1000D-F-04-10-0301-00A-022A-D") == 0) {
			xml = brex_DMC_S1000D_F_04_10_0301_00A_022A_D_001_00_EN_US_XML;
			len = brex_DMC_S1000D_F_04_10_0301_00A_022A_D_001_00_EN_US_XML_len;
		} else if (strcmp(name, "DMC-S1000D-E-04-10-0301-00A-022A-D") == 0) {
			xml = brex_DMC_S1000D_E_04_10_0301_00A_022A_D_012_00_EN_US_XML;
			len = brex_DMC_S1000D_E_04_10_0301_00A_022A_D_012_00_EN_US_XML_len;
		} else if (strcmp(name, "DMC-S1000D-D-04-10-0301-00A-022A-D") == 0) {
			xml = brex_DMC_S1000D_D_04_10_0301_00A_022A_D_006_00_EN_US_XML;
			len = brex_DMC_S1000D_D_04_10_0301_00A_022A_D_006_00_EN_US_XML_len;
		} else if (strcmp(name, "DMC-S1000D-A-04-10-0301-00A-022A-D") == 0) {
			xml = brex_DMC_S1000D_A_04_10_0301_00A_022A_D_005_00_EN_US_XML;
			len = brex_DMC_S1000D_A_04_10_0301_00A_022A_D_005_00_EN_US_XML_len;
		} else if (strcmp(name, "DMC-AE-A-04-10-0301-00A-022A-D") == 0) {
			xml = brex_DMC_AE_A_04_10_0301_00A_022A_D_003_00_XML;
			len = brex_DMC_AE_A_04_10_0301_00A_022A_D_003_00_XML_len;
		}

		return read_xml_mem((const char *) xml, len);
	}
}

/* Determine which parts of the SNS rules to check. */
static bool should_check(xmlChar *code, char *path, xmlDocPtr snsRulesDoc, xmlNodePtr ctx, struct opts *opts)
{
	bool ret;

	if (opts->strict_sns) return true;

	if (opts->unstrict_sns)
		return firstXPathNode(snsRulesDoc, ctx, path);

	if (strcmp(path, ".//snsSubSystem") == 0 || strcmp(path, ".//snsSubSubSystem") == 0) {
		ret = xmlStrcmp(code, BAD_CAST "0") != 0;
	} else {
		ret = !(xmlStrcmp(code, BAD_CAST "00") == 0 || xmlStrcmp(code, BAD_CAST "0000") == 0);
	}

	return ret || firstXPathNode(snsRulesDoc, ctx, path);
}

/* Check SNS rules against a CSDB object. */
static bool check_brex_sns_rules(xmlDocPtr snsRulesDoc, xmlNodePtr snsRulesGroup, xmlDocPtr dmod_doc, xmlNodePtr documentNode, struct opts *opts)
{
	xmlNodePtr dmcode, snsCheck, snsError;
	xmlChar *systemCode, *subSystemCode, *subSubSystemCode, *assyCode;
	char value[256];
	char xpath[256];
	xmlNodePtr ctx = NULL;
	bool correct = true;

	/* Only check SNS in data modules. */
	if (xmlStrcmp(xmlDocGetRootElement(dmod_doc)->name, BAD_CAST "dmodule") != 0)
		return correct;

	dmcode = firstXPathNode(dmod_doc, NULL, "//dmIdent/dmCode");

	systemCode       = xmlGetProp(dmcode, BAD_CAST "systemCode");
	subSystemCode    = xmlGetProp(dmcode, BAD_CAST "subSystemCode");
	subSubSystemCode = xmlGetProp(dmcode, BAD_CAST "subSubSystemCode");
	assyCode         = xmlGetProp(dmcode, BAD_CAST "assyCode");

	snsCheck = xmlNewChild(documentNode, NULL, BAD_CAST "sns", NULL);
	snsError = xmlNewNode(NULL, BAD_CAST "error");

	/* Check the SNS of the data module against the SNS rules in descending order. */

	/* System code. */
	if (should_check(systemCode, "//snsSystem", snsRulesDoc, ctx, opts)) {
		sprintf(xpath, "//snsSystem[snsCode = '%s']", (char *) systemCode);
		if (!(ctx = firstXPathNode(snsRulesDoc, ctx, xpath))) {
			xmlNewChild(snsError, NULL, BAD_CAST "code", BAD_CAST "systemCode");
			xmlNewChild(snsError, NULL, BAD_CAST "invalidValue", systemCode);
			xmlAddChild(snsCheck, snsError);
			correct = false;
		}
	}

	/* Subsystem code. */
	if (correct && should_check(subSystemCode, ".//snsSubSystem", snsRulesDoc, ctx, opts)) {
		sprintf(xpath, ".//snsSubSystem[snsCode = '%s']", (char *) subSystemCode);
		if (!(ctx = firstXPathNode(snsRulesDoc, ctx, xpath))) {
			xmlNewChild(snsError, NULL, BAD_CAST "code", BAD_CAST "subSystemCode");
			sprintf(value, "%s-%s", systemCode, subSystemCode);
			xmlNewChild(snsError, NULL, BAD_CAST "invalidValue", BAD_CAST value);
			xmlAddChild(snsCheck, snsError);
			correct = false;
		}
	}

	/* Subsubsystem code. */
	if (correct && should_check(subSubSystemCode, ".//snsSubSubSystem", snsRulesDoc, ctx, opts)) {
		sprintf(xpath, ".//snsSubSubSystem[snsCode = '%s']", (char *) subSubSystemCode);
		if (!(ctx = firstXPathNode(snsRulesDoc, ctx, xpath))) {
			xmlNewChild(snsError, NULL, BAD_CAST "code", BAD_CAST "subSubSystemCode");
			sprintf(value, "%s-%s%s", systemCode, subSystemCode, subSubSystemCode);
			xmlNewChild(snsError, NULL, BAD_CAST "invalidValue", BAD_CAST value);
			xmlAddChild(snsCheck, snsError);
			correct = false;
		}
	}

	/* Assembly code. */
	if (correct && should_check(assyCode, ".//snsAssy", snsRulesDoc, ctx, opts)) {
		sprintf(xpath, ".//snsAssy[snsCode = '%s']", (char *) assyCode);
		if (!firstXPathNode(snsRulesDoc, ctx, xpath)) {
			xmlNewChild(snsError, NULL, BAD_CAST "code", BAD_CAST "assyCode");
			sprintf(value, "%s-%s%s-%s", systemCode, subSystemCode, subSubSystemCode, assyCode);
			xmlNewChild(snsError, NULL, BAD_CAST "invalidValue", BAD_CAST value);
			xmlAddChild(snsCheck, snsError);
			correct = false;
		}
	}

	if (correct) {
		xmlFreeNode(snsError);
		xmlNewChild(snsCheck, NULL, BAD_CAST "noErrors", NULL);
	} else if (opts->verbosity > SILENT) {
		print_node(snsError);
	}

	xmlFree(systemCode);
	xmlFree(subSystemCode);
	xmlFree(subSubSystemCode);
	xmlFree(assyCode);

	return correct;
}

/* Check the SNS rules of BREX DMs against a CSDB object. */
static bool check_brex_sns(char (*brex_fnames)[PATH_MAX], int nbrex_fnames,
	xmlDocPtr dmod_doc,xmlNodePtr documentNode, struct opts *opts)
{
	int i;
	xmlDocPtr snsRulesDoc;
	xmlNodePtr snsRulesGroup;
	bool correct;

	/* The valid SNS is taken as a combination of the snsRules from all specified BREX data modules. */
	snsRulesDoc = xmlNewDoc(BAD_CAST "1.0");
	xmlDocSetRootElement(snsRulesDoc, xmlNewNode(NULL, BAD_CAST "snsRulesGroup"));
	snsRulesGroup = xmlDocGetRootElement(snsRulesDoc);

	for (i = 0; i < nbrex_fnames; ++i) {
		xmlDocPtr brex;

		brex = load_brex(brex_fnames[i], dmod_doc);

		xmlAddChild(snsRulesGroup, xmlCopyNode(firstXPathNode(brex, NULL, "//snsRules"), 1));

		xmlFreeDoc(brex);
	}

	correct = check_brex_sns_rules(snsRulesDoc, snsRulesGroup, dmod_doc, documentNode, opts);

	xmlFreeDoc(snsRulesDoc);

	return correct;
}

/* Check the notation used by an entity against the notation rules. */
static int check_entity(xmlEntityPtr entity, xmlDocPtr notationRuleDoc,
	xmlNodePtr notationCheck, struct opts *opts)
{	
	char xpath[256];
	xmlNodePtr rule;
	xmlNodePtr notationError;

	sprintf(xpath, "//notationRule[notationName='%s' and notationName/@allowedNotationFlag!='0']",
		(char *) entity->content);

	if ((rule = firstXPathNode(notationRuleDoc, NULL, xpath)))
		return 0;

	sprintf(xpath, "(//notationRule[notationName='%s']|//notationRule)[1]", (char *) entity->content);
	rule = firstXPathNode(notationRuleDoc, NULL, xpath);

	notationError = xmlNewChild(notationCheck, NULL, BAD_CAST "error", NULL);
	xmlNewChild(notationError, NULL, BAD_CAST "invalidNotation", entity->content);
	xmlAddChild(notationError, xmlCopyNode(firstXPathNode(notationRuleDoc, rule, "objectUse"), 1));

	if (opts->verbosity > SILENT) {
		print_node(notationError);
	}

	return 1;
}

/* Check notation rules against a CSDB object. */
static int check_brex_notation_rules(xmlDocPtr notationRuleDoc, xmlNodePtr notationRuleGroup, xmlDocPtr dmod_doc, xmlNodePtr documentNode, struct opts *opts)
{
	xmlDtdPtr dtd;
	xmlNodePtr notationCheck, cur;
	int invalid = 0;

	if (!(dtd = dmod_doc->intSubset))
		return 0;

	notationCheck = xmlNewChild(documentNode, NULL, BAD_CAST "notations", NULL);

	for (cur = dtd->children; cur; cur = cur->next) {
		if (cur->type == XML_ENTITY_DECL && ((xmlEntityPtr) cur)->etype == 3) {
			invalid += check_entity((xmlEntityPtr) cur, notationRuleDoc,
				notationCheck, opts);
		}
	}

	if (!notationCheck->children) {
		xmlNewChild(notationCheck, NULL, BAD_CAST "noErrors", NULL);
	}

	return invalid;
}

/* Check the notation rules of BREX DMs against a CSDB object. */
static int check_brex_notations(char (*brex_fnames)[PATH_MAX], int nbrex_fnames,
	xmlDocPtr dmod_doc, xmlNodePtr documentNode, struct opts *opts)
{
	xmlDocPtr notationRuleDoc;
	xmlNodePtr notationRuleGroup;
	int i, invalid;

	notationRuleDoc = xmlNewDoc(BAD_CAST "1.0");
	xmlDocSetRootElement(notationRuleDoc, xmlNewNode(NULL, BAD_CAST "notationRuleGroup"));
	notationRuleGroup = xmlDocGetRootElement(notationRuleDoc);

	for (i = 0; i < nbrex_fnames; ++i) {
		xmlDocPtr brex;

		brex = load_brex(brex_fnames[i], dmod_doc);

		xmlAddChild(notationRuleGroup, xmlCopyNode(firstXPathNode(brex, NULL, "//notationRuleList"), 1));

		xmlFreeDoc(brex);
	}

	invalid = check_brex_notation_rules(notationRuleDoc, notationRuleGroup, dmod_doc, documentNode, opts);

	xmlFreeDoc(notationRuleDoc);

	return invalid;
}

/* Print the filenames of CSDB objects with BREX errors. */
static void print_fnames(xmlNodePtr node)
{
	if (xmlStrcmp(node->name, BAD_CAST "document") == 0 && firstXPathNode(NULL, node, "brex/error")) {
		xmlChar *fname;
		fname = xmlGetProp(node, BAD_CAST "path");
		puts((char *) fname);
		xmlFree(fname);
	} else {
		xmlNodePtr cur;

		for (cur = node->children; cur; cur = cur->next) {
			print_fnames(cur);
		}
	}
}

/* Print the filenames of CSDB objects with no BREX errors. */
static void print_valid_fnames(xmlNodePtr node)
{
	if (xmlStrcmp(node->name, BAD_CAST "document") == 0 && !firstXPathNode(NULL, node, "brex/error")) {
		xmlChar *fname;
		fname = xmlGetProp(node, BAD_CAST "path");
		puts((char *) fname);
		xmlFree(fname);
	} else {
		xmlNodePtr cur;

		for (cur = node->children; cur; cur = cur->next) {
			print_fnames(cur);
		}
	}
}

#if XPATH2_ENGINE != NONE
/* Determine whether the S1000D issue of a BREX DM means it may require
 * XPath 2.0 to evaluate its rules.
 */
static bool brex_requires_xpath2(xmlDocPtr brex, struct opts *opts)
{
	xmlChar *issue;
	bool requires_xpath2;

	switch (opts->xpath_version) {
		case XPATH_1: return false;
		case XPATH_2: return true;
		case DYNAMIC: break;
	}

	issue = xmlGetNsProp(xmlDocGetRootElement(brex),
		BAD_CAST "noNamespaceSchemaLocation",
		BAD_CAST "http://www.w3.org/2001/XMLSchema-instance");

	/* These issues of S1000D only required XPath 1.0. */
	     if (xmlStrncmp(issue, BAD_CAST "http://www.s1000d.org/S1000D_2-0", 32) == 0) requires_xpath2 = false;
	else if (xmlStrncmp(issue, BAD_CAST "http://www.s1000d.org/S1000D_2-1", 32) == 0) requires_xpath2 = false;
	else if (xmlStrncmp(issue, BAD_CAST "http://www.s1000d.org/S1000D_2-2", 32) == 0) requires_xpath2 = false;
	else if (xmlStrncmp(issue, BAD_CAST "http://www.s1000d.org/S1000D_2-3", 32) == 0) requires_xpath2 = false;
	else if (xmlStrncmp(issue, BAD_CAST "http://www.s1000d.org/S1000D_3-0", 32) == 0) requires_xpath2 = false;
	else requires_xpath2 = true;

	xmlFree(issue);

	return requires_xpath2;
}
#endif

/* Check context, SNS, and notation rules of BREX DMs against a CSDB object. */
#if XPATH2_ENGINE == SAXON
static int check_brex(xmlDocPtr dmod_doc, const char *docname, char (*brex_fnames)[PATH_MAX], int num_brex_fnames, xmlNodePtr brexCheck, struct opts *opts, void *saxon_processor)
#elif XPATH2_ENGINE == XQILLA
static int check_brex(xmlDocPtr dmod_doc, const char *docname, char (*brex_fnames)[PATH_MAX], int num_brex_fnames, xmlNodePtr brexCheck, struct opts *opts, void *xqilla_impl)
#else
static int check_brex(xmlDocPtr dmod_doc, const char *docname, char (*brex_fnames)[PATH_MAX], int num_brex_fnames, xmlNodePtr brexCheck, struct opts *opts)
#endif
{
	xmlDocPtr brex_doc;
	xmlNodePtr documentNode;

	int i;
	int total = 0;
	bool valid_sns = true;
	int invalid_notations = 0;

	xmlChar *schema;

	xmlDocPtr validtree = NULL;

#if XPATH2_ENGINE == SAXON
	void *saxon_node;
#elif XPATH2_ENGINE == XQILLA
	void *xqilla_parser;
	void *xqilla_doc;
#endif

	/* Make a copy of the original XML tree before performing extra
	 * processing on it. */
	if (output_tree) {
		validtree = xmlCopyDoc(dmod_doc, 1);
	}

	/* Remove "delete" elements. */
	if (rem_delete) {
		rem_delete_elems(dmod_doc);
	}

#if XPATH2_ENGINE == SAXON
	saxon_node = saxon_new_node(saxon_processor, dmod_doc);
#elif XPATH2_ENGINE == XQILLA
	xqilla_parser = xqilla_create_parser(xqilla_impl);
	xqilla_doc = xqilla_create_doc(xqilla_impl, xqilla_parser, dmod_doc);
#endif

	schema = xmlGetNsProp(xmlDocGetRootElement(dmod_doc), BAD_CAST "noNamespaceSchemaLocation", XSI_URI);

	documentNode = xmlNewChild(brexCheck, NULL, BAD_CAST "document", NULL);
	xmlSetProp(documentNode, BAD_CAST "path", BAD_CAST docname);

	if (opts->check_sns &&
	    !(valid_sns = check_brex_sns(brex_fnames, num_brex_fnames, dmod_doc,
			                 documentNode, opts)))
	{
		++total;
	}

	if (opts->check_notations) {
		invalid_notations = check_brex_notations(brex_fnames, num_brex_fnames, dmod_doc, documentNode, opts);
		total += invalid_notations;
	}

	for (i = 0; i < num_brex_fnames; ++i) {
		xmlXPathContextPtr context;
		xmlXPathObjectPtr result;
		int status;
#if XPATH2_ENGINE != NONE
		bool use_xpath2;
#endif

		brex_doc = load_brex(brex_fnames[i], dmod_doc);

		if (!brex_doc) {
			if (opts->verbosity > SILENT) {
				fprintf(stderr, E_NODMOD, brex_fnames[i]);
			}
			exit(EXIT_BAD_DMODULE);
		}

		context = xmlXPathNewContext(brex_doc);
		xmlXPathRegisterVariable(context, BAD_CAST "schema", xmlXPathNewString(schema));

		result = xmlXPathEvalExpression(STRUCT_OBJ_RULE_PATH, context);

#if XPATH2_ENGINE != NONE
		/* Determine if the BREX rules should be evaluated with XPath 2.0. */
		use_xpath2 = brex_requires_xpath2(brex_doc, opts);
#endif

#if XPATH2_ENGINE == SAXON
		if (use_xpath2) {
			status = check_brex_rules(brex_doc, result->nodesetval, dmod_doc, docname, brex_fnames[i], documentNode, opts, saxon_processor, saxon_node);
		} else {
			status = check_brex_rules(brex_doc, result->nodesetval, dmod_doc, docname, brex_fnames[i], documentNode, opts, NULL, NULL);
		}
#elif XPATH2_ENGINE == XQILLA
		if (use_xpath2) {
			status = check_brex_rules(brex_doc, result->nodesetval, dmod_doc, docname, brex_fnames[i], documentNode, opts, xqilla_doc);
		} else {
			status = check_brex_rules(brex_doc, result->nodesetval, dmod_doc, docname, brex_fnames[i], documentNode, opts, NULL);
		}
#else
		status = check_brex_rules(brex_doc, result->nodesetval, dmod_doc, docname, brex_fnames[i], documentNode, opts);
#endif

		if (opts->verbosity >= VERBOSE) {
			fprintf(stderr,
				status || !valid_sns || invalid_notations ?
				F_INVALIDDOC :
				S_VALIDDOC, docname, brex_fnames[i]);
		}

		total += status;

		xmlXPathFreeObject(result);
		xmlXPathFreeContext(context);
		xmlFreeDoc(brex_doc);
	}

	xmlFree(schema);

	switch (show_fnames) {
		case SHOW_NONE: break;
		case SHOW_INVALID: print_fnames(documentNode); break;
		case SHOW_VALID: print_valid_fnames(documentNode); break;
	}

	if (output_tree) {
		if (total == 0) {
			save_xml_doc(validtree, "-");
		}
		xmlFreeDoc(validtree);
	}

#if XPATH2_ENGINE == SAXON
	saxon_free_node(saxon_node);
#elif XPATH2_ENGINE == XQILLA
	xqilla_free_parser(xqilla_parser);
#endif

	return total;
}

/* Determine if a BREX exists in the given search paths. */
static bool brex_exists(char fname[PATH_MAX], char (*fnames)[PATH_MAX], int nfnames, char (*spaths)[PATH_MAX], int nspaths)
{
	int i;

	for (i = 0; i < nfnames; ++i) {
		if (strcmp(fname, fnames[i]) == 0) {
			return true;
		}
	}

	return false;
}

/* Add a path to a list of paths, extending its size if necessary. */
static void add_path(char (**list)[PATH_MAX], int *n, unsigned *max, const char *s, struct opts *opts)
{
	if ((*n) == (*max)) {
		if (!(*list = realloc(*list, (*max *= 2) * PATH_MAX))) {
			if (opts->verbosity > SILENT) {
				fprintf(stderr, E_MAXOBJS);
			}
			exit(EXIT_MAX_OBJS);
		}
	}

	strcpy((*list)[(*n)++], s);
}

/* Add the BREX referenced by another BREX DM in layered mode (-l).*/
static int add_layered_brex(char (**fnames)[PATH_MAX], int nfnames, char (*spaths)[PATH_MAX], int nspaths, char (*dmod_fnames)[PATH_MAX], int num_dmod_fnames, xmlDocPtr dmod_doc, struct opts *opts)
{
	int i;
	int total = nfnames;

	for (i = 0; i < nfnames; ++i) {
		xmlDocPtr doc;
		char fname[PATH_MAX];
		int err;

		doc = load_brex((*fnames)[i], dmod_doc);

		err = find_brex_fname_from_doc(fname, doc, spaths, nspaths, dmod_fnames, num_dmod_fnames, opts);

		if (err) {
			fprintf(stderr, E_NOBREX_LAYER, (*fnames)[i]);
			exit(EXIT_BREX_NOT_FOUND);
		} else if (!brex_exists(fname, (*fnames), nfnames, spaths, nspaths)) {
			add_path(fnames, &total, &BREX_MAX, fname, opts);
			total = add_layered_brex(fnames, total, spaths, nspaths, dmod_fnames, num_dmod_fnames, dmod_doc, opts);
		}

		xmlFreeDoc(doc);
	}

	return total;
}

/* Add CSDB objects to check from a list of filenames. */
static void add_dmod_list(const char *fname, char (**dmod_fnames)[PATH_MAX], int *num_dmod_fnames, struct opts *opts)
{
	FILE *f;
	char path[PATH_MAX];

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

	while (fgets(path, PATH_MAX, f)) {
		strtok(path, "\t\r\n");
		add_path(dmod_fnames, num_dmod_fnames, &DMOD_MAX, path, opts);
	}

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

/* Return the default BREX DMC for a given issue of the spec. */
static const char *default_brex_dmc(xmlDocPtr doc)
{
	xmlChar *schema;
	const char *code;

	schema = xmlGetNsProp(xmlDocGetRootElement(doc), BAD_CAST "noNamespaceSchemaLocation", XSI_URI);

	if (schema == NULL || xmlStrstr(schema, BAD_CAST "S1000D_5-0")) {
		code = "DMC-S1000D-G-04-10-0301-00A-022A-D";
	} else if (xmlStrstr(schema, BAD_CAST "S1000D_4-2")) {
		code = "DMC-S1000D-F-04-10-0301-00A-022A-D";
	} else if (xmlStrstr(schema, BAD_CAST "S1000D_4-1")) {
		code = "DMC-S1000D-E-04-10-0301-00A-022A-D";
	} else if (xmlStrstr(schema, BAD_CAST "S1000D_4-0")) {
		code = "DMC-S1000D-D-04-10-0301-00A-022A-D";
	} else {
		code = "DMC-AE-A-04-10-0301-00A-022A-D";
	}

	xmlFree(schema);

	return code;
}

static void print_stats(xmlDocPtr doc)
{
	xmlDocPtr styledoc;
	xsltStylesheetPtr style;
	xmlDocPtr res;

	styledoc = read_xml_mem((const char *) stats_xsl, stats_xsl_len);
	style = xsltParseStylesheetDoc(styledoc);

	res = xsltApplyStylesheet(style, doc, NULL);

	fprintf(stderr, "%s", (char *) res->children->content);

	xmlFreeDoc(res);
	xsltFreeStylesheet(style);
}

/* Add configuration information to report. */
static void add_config_to_report(xmlNodePtr brexCheck, struct opts *opts)
{
	if (opts->layered) {
		xmlSetProp(brexCheck, BAD_CAST "layered", BAD_CAST "yes");
	} else {
		xmlSetProp(brexCheck, BAD_CAST "layered", BAD_CAST "no");
	}

	if (opts->check_values) {
		xmlSetProp(brexCheck, BAD_CAST "checkObjectValues", BAD_CAST "yes");
	} else {
		xmlSetProp(brexCheck, BAD_CAST "checkObjectValues", BAD_CAST "no");
	}

	if (opts->check_sns) {
		if (opts->strict_sns) {
			xmlSetProp(brexCheck, BAD_CAST "snsCheck", BAD_CAST "strict");
		} else if (opts->unstrict_sns) {
			xmlSetProp(brexCheck, BAD_CAST "snsCheck", BAD_CAST "unstrict");
		} else {
			xmlSetProp(brexCheck, BAD_CAST "snsCheck", BAD_CAST "normal");
		}
	} else {
		xmlSetProp(brexCheck, BAD_CAST "snsCheck", BAD_CAST "no");
	}

	if (opts->check_notations) {
		xmlSetProp(brexCheck, BAD_CAST "notationCheck", BAD_CAST "yes");
	} else {
		xmlSetProp(brexCheck, BAD_CAST "notationCheck", BAD_CAST "no");
	}
}

/* Determine XPath version of user-supplied string. */
static void set_xpath_version(struct opts *opts, const char *str)
{
	if (strcmp(str, "1.0") == 0) {
		opts->xpath_version = XPATH_1;
#if XPATH2_ENGINE != NONE
	} else if (strcmp(str, "2.0") == 0) {
		opts->xpath_version = XPATH_2;
#endif
	} else {
		if (opts->verbosity > SILENT) {
			fprintf(stderr, E_BAD_XPATH_VERSION, str);
		}

		exit(EXIT_BAD_XPATH_VERSION);
	}
}

#ifdef LIBS1KD
typedef enum {
	S1KD_BREXCHECK_VALUES = 1,
	S1KD_BREXCHECK_SNS = 2,
	S1KD_BREXCHECK_STRICT_SNS = 4,
	S1KD_BREXCHECK_UNSTRICT_SNS = 8,
	S1KD_BREXCHECK_NOTATIONS = 16,
	S1KD_BREXCHECK_NORMAL_LOG = 32,
	S1KD_BREXCHECK_VERBOSE_LOG = 64
} s1kdBREXCheckOption;

static void init_opts(struct opts *opts, int options)
{
	if (optset(options, S1KD_BREXCHECK_NORMAL_LOG)) {
		opts->verbosity = NORMAL;
	} else if (optset(options, S1KD_BREXCHECK_VERBOSE_LOG)) {
		opts->verbosity = VERBOSE;
	} else {
		opts->verbosity = SILENT;
	}

	opts->layered         = false;
	opts->check_values    = optset(options, S1KD_BREXCHECK_VALUES);
	opts->check_sns       = optset(options, S1KD_BREXCHECK_SNS);
	opts->strict_sns      = optset(options, S1KD_BREXCHECK_STRICT_SNS);
	opts->unstrict_sns    = optset(options, S1KD_BREXCHECK_UNSTRICT_SNS);
	opts->check_notations = optset(options, S1KD_BREXCHECK_NOTATIONS);
	opts->ignore_issue    = false;
	opts->xpath_version   = DYNAMIC;
}

static void free_opts(struct opts *opts)
{
}

int s1kdDocCheckDefaultBREX(xmlDocPtr doc, int options, xmlDocPtr *report)
{
	int err;
	xmlDocPtr brex;
	xmlDocPtr rep;
	xmlXPathContextPtr ctx;
	xmlXPathObjectPtr obj;
	xmlNodePtr node;
	const char *brex_dmc;
	struct opts opts;
#if XPATH2_ENGINE == SAXON
	void *saxon_processor, *saxon_node;
#elif XPATH2_ENGINE == XQILLA
	void *xqilla_impl, *xqilla_parser, *xqilla_doc;
#endif
#if XPATH2_ENGINE != NONE
	bool use_xpath2;
#endif

	init_opts(&opts, options);

#if XPATH2_ENGINE != NONE
	use_xpath2 = brex_requires_xpath2(brex, &opts);
#endif

	rep = xmlNewDoc(BAD_CAST "1.0");
	node = xmlNewNode(NULL, BAD_CAST "brexCheck");
	xmlDocSetRootElement(rep, node);
	add_config_to_report(node, &opts);

	node = xmlNewChild(node, NULL, BAD_CAST "document", NULL);
	xmlSetProp(node, BAD_CAST "path", doc->URL);

	brex_dmc = default_brex_dmc(doc);
	brex = load_brex(brex_dmc, doc);

	ctx = xmlXPathNewContext(brex);
	obj = xmlXPathEvalExpression(BAD_CAST "//structureObjectRule", ctx);

#if XPATH2_ENGINE == SAXON
	if (use_xpath2) {
		saxon_processor = saxon_new_processor();
		saxon_node = saxon_new_node(saxon_processor, doc);

		err = check_brex_rules(brex, obj->nodesetval, doc, doc->URL, brex_dmc, node, &opts, saxon_processor, saxon_node);

		saxon_free_node(saxon_node);
		saxon_free_processor(saxon_processor);
	} else {
		err = check_brex_rules(brex, obj->nodesetval, doc, doc->URL, brex_dmc, node, &opts, NULL, NULL);
	}
#elif XPATH2_ENGINE == XQILLA
	if (use_xpath2) {
		xqilla_impl = xqilla_initialize();
		xqilla_parser = xqilla_create_parser(xqilla_impl);
		xqilla_doc = xqilla_create_doc(xqilla_impl, xqilla_parser, doc);

		err = check_brex_rules(brex, obj->nodesetval, doc, doc->URL, brex_dmc, node, &opts, xqilla_doc);

		xqilla_free_parser(xqilla_parser);
		xqilla_terminate();
	} else {
		err = check_brex_rules(brex, obj->nodesetval, doc, doc->URL, brex_dmc, node, &opts, NULL);
	}
#else
	err = check_brex_rules(brex, obj->nodesetval, doc, doc->URL, brex_dmc, node, &opts);
#endif

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

	if (report) {
		*report = rep;
	} else {
		xmlFreeDoc(rep);
	}

	free_opts(&opts);

	return err;
}

int s1kdCheckDefaultBREX(const char *object_xml, int object_size, int options, char **report_xml, int *report_size)
{
	xmlDocPtr doc, rep;
	int err;

	doc = read_xml_mem(object_xml, object_size);
	err = s1kdDocCheckDefaultBREX(doc, options, &rep);
	xmlFreeDoc(doc);

	if (report_xml && report_size) {
		xmlDocDumpMemory(rep, (xmlChar **) report_xml, report_size);
		xmlFreeDoc(rep);
	}

	return err;
}

int s1kdDocCheckBREX(xmlDocPtr doc, xmlDocPtr brex, int options, xmlDocPtr *report)
{
	int err = 0;
	xmlDocPtr rep;
	xmlXPathContextPtr ctx;
	xmlXPathObjectPtr obj;
	xmlNodePtr node;
	struct opts opts;
#if XPATH2_ENGINE == SAXON
	void *saxon_processor, *saxon_node;
#elif XPATH2_ENGINE == XQILLA
	void *xqilla_impl, *xqilla_parser, *xqilla_doc;
#endif
#if XPATH2_ENGINE != NONE
	bool use_xpath2;
#endif

	init_opts(&opts, options);

#if XPATH2_ENGINE != NONE
	use_xpath2 = brex_requires_xpath2(brex, &opts);
#endif

	rep = xmlNewDoc(BAD_CAST "1.0");
	node = xmlNewNode(NULL, BAD_CAST "brexCheck");
	xmlDocSetRootElement(rep, node);
	add_config_to_report(node, &opts);

	node = xmlNewChild(node, NULL, BAD_CAST "document", NULL);
	xmlSetProp(node, BAD_CAST "path", doc->URL);

	ctx = xmlXPathNewContext(brex);
	obj = xmlXPathEvalExpression(BAD_CAST "//structureObjectRule", ctx);

	if (opts.check_sns) {
		xmlDocPtr snsRulesDoc = xmlNewDoc(BAD_CAST "1.0");
		xmlNodePtr snsRulesGroup = xmlNewNode(NULL, BAD_CAST "snsRules");

		xmlDocSetRootElement(snsRulesDoc, snsRulesGroup);
		xmlAddChild(snsRulesGroup, xmlCopyNode(firstXPathNode(brex, NULL, "//snsRules"), 1));

		err += check_brex_sns_rules(snsRulesDoc, snsRulesGroup, doc, node, &opts);

		xmlFreeDoc(snsRulesDoc);
	}

	if (opts.check_notations) {
		xmlDocPtr notationRulesDoc = xmlNewDoc(BAD_CAST "1.0");
		xmlNodePtr notationRulesGroup = xmlNewNode(NULL, BAD_CAST "notationRules");

		xmlDocSetRootElement(notationRulesDoc, notationRulesGroup);
		xmlAddChild(notationRulesGroup, xmlCopyNode(firstXPathNode(brex, NULL, "//notationRuleList"), 1));

		err += check_brex_notation_rules(notationRulesDoc, notationRulesGroup, doc, node, &opts);

		xmlFreeDoc(notationRulesDoc);
	}

#if XPATH2_ENGINE == SAXON
	if (use_xpath2) {
		saxon_processor = saxon_new_processor();
		saxon_node = saxon_new_node(saxon_processor, doc);

		err += check_brex_rules(brex, obj->nodesetval, doc, doc->URL, brex->URL, node, &opts, saxon_processor, saxon_node);

		saxon_free_node(saxon_node);
		saxon_free_processor(saxon_processor);
	} else {
		err += check_brex_rules(brex, obj->nodesetval, doc, doc->URL, brex->URL, node, &opts, NULL, NULL);
	}
#elif XPATH2_ENGINE == XQILLA
	if (use_xpath2) {
		xqilla_impl = xqilla_initialize();
		xqilla_parser = xqilla_create_parser(xqilla_impl);
		xqilla_doc = xqilla_create_doc(xqilla_impl, xqilla_parser, doc);

		err += check_brex_rules(brex, obj->nodesetval, doc, doc->URL, brex->URL, node, &opts, xqilla_doc);

		xqilla_free_parser(xqilla_parser);
		xqilla_terminate();
	} else {
		err += check_brex_rules(brex, obj->nodesetval, doc, doc->URL, brex->URL, node, &opts, NULL);
	}
#else
	err += check_brex_rules(brex, obj->nodesetval, doc, doc->URL, brex->URL, node, &opts);
#endif

	xmlXPathFreeObject(obj);
	xmlXPathFreeContext(ctx);

	if (report) {
		*report = rep;
	} else {
		xmlFreeDoc(rep);
	}

	free_opts(&opts);

	return err;
}

int s1kdCheckBREX(const char *object_xml, int object_size, const char *brex_xml, int brex_size, int options, char **report_xml, int *report_size)
{
	xmlDocPtr doc, brex, rep;
	int err;

	doc = read_xml_mem(object_xml, object_size);
	brex = read_xml_mem(brex_xml, brex_size);
	err = s1kdDocCheckBREX(doc, brex, options, &rep);
	xmlFreeDoc(doc);
	xmlFreeDoc(brex);

	if (report_xml && report_size) {
		xmlDocDumpMemory(rep, (xmlChar **) report_xml, report_size);
		xmlFreeDoc(rep);
	}

	return err;
}
#else
/* Show usage message. */
static void show_help(void)
{
	puts("Usage: " PROG_NAME " [-b <brex>] [-d <dir>] [-I <path>] [-w <file>] [-X <version>] [-F|-f] [-BceLlNnopqrS[tu]sTvx^h?] [<object>...]");
	puts("");
	puts("Options:");
	puts("  -B, --default-brex                   Use the default BREX.");
	puts("  -b, --brex <brex>                    Use <brex> as the BREX data module.");
	puts("  -c, --values                         Check object values.");
	puts("  -d, --dir <dir>                      Directory to start search for BREX in.");
	puts("  -e, --ignore-empty                   Ignore empty/non-XML files.");
	puts("  -F, --valid-filenames                Print the filenames of valid objects.");
	puts("  -f, --filenames                      Print the filenames of invalid objects.");
	puts("  -h, -?, --help                       Show this help message.");
	puts("  -I, --include <path>                 Add <path> to search path for BREX data module.");
	puts("  -L, --list                           Input is a list of data module filenames.");
	puts("  -l, --layered                        Check BREX referenced by other BREX.");
	puts("  -N, --omit-issue                     Assume issue/inwork numbers are omitted.");
	puts("  -n, --notations                      Check notation rules.");
	puts("  -o, --output-valid                   Output valid CSDB objects to stdout.");
	puts("  -p, --progress                       Display progress bar.");
	puts("  -q, --quiet                          Quiet mode. Do not print errors.");
	puts("  -r, --recursive                      Search for BREX recursively.");
	puts("  -S[tu], --sns [--strict|--unstrict]  Check SNS rules.");
	puts("  -s, --short                          Short messages.");
	puts("  -T, --summary                        Print a summary of the check.");
	puts("  -v, --verbose                        Verbose mode.");
	puts("  -w, --severity-levels <file>         List of severity levels.");
	puts("  -X, --xpath-version <version>        Force the version of XPath that will be used.");
	puts("  -x, --xml                            XML output.");
	puts("  -^, --remove-deleted                 Check with elements marked as \"delete\" removed.");
	puts("  --version                            Show version information.");
	puts("  --zenity-progress                    Prints progress information in the zenity --progress format.");
	LIBXML2_PARSE_LONGOPT_HELP
}

/* Show version information. */
#if XPATH2_ENGINE == SAXON
static void show_version(void *saxon_processor)
#else
static void show_version(void)
#endif
{
	printf("%s (s1kd-tools) %s\n", PROG_NAME, VERSION);

#if XPATH2_ENGINE == SAXON
	printf("Using libxml %s, libxslt %s, libexslt %s and %s\n", xmlParserVersion, xsltEngineVersion, exsltLibraryVersion, saxon_version(saxon_processor));
	puts("XPath support: 1.0 (libxml), 2.0 (Saxon)");
#elif XPATH2_ENGINE == XQILLA
	printf("Using libxml %s, libxslt %s, libexslt %s and Xerces-C %s + XQilla\n", xmlParserVersion, xsltEngineVersion, exsltLibraryVersion, xqilla_version());
	puts("XPath support: 1.0 (libxml), 2.0 (XQilla)");
#else
	printf("Using libxml %s, libxslt %s and libexslt %s\n", xmlParserVersion, xsltEngineVersion, exsltLibraryVersion);
	puts("XPath support: 1.0 (libxml)");
#endif
}

int main(int argc, char *argv[])
{
	int c;
	int i;

	char (*brex_fnames)[PATH_MAX] = malloc(BREX_MAX * PATH_MAX);
	int num_brex_fnames = 0;

	char (*brex_search_paths)[PATH_MAX] = malloc(BREX_PATH_MAX * PATH_MAX);
	int num_brex_search_paths = 0;

	char (*dmod_fnames)[PATH_MAX] = malloc(DMOD_MAX * PATH_MAX);
	int num_dmod_fnames = 0;

	int status = 0;

	bool use_stdin = false;
	bool xmlout = false;
	int show_progress = PROGRESS_OFF;
	bool is_list = false;
	bool use_default_brex = false;
	bool show_stats = false;

	xmlDocPtr outdoc;
	xmlNodePtr brexCheck;

	struct opts opts = {
		/* verbosity */ NORMAL,
		/* layered */ false,
		/* check_values */ false,
		/* check_sns */ false,
		/* strict_sns */ false,
		/* unstrict_sns */ false,
		/* check_notations */ false,
		/* ignore_issue */ false,
		/* xpath_version */ DYNAMIC
	};

	const char *sopts = "Bb:eI:xvqslw:StupFfNncLTrd:oX:^h?";
	struct option lopts[] = {
		{"version"        , no_argument      , 0, 0},
		{"help"           , no_argument      , 0, 'h'},
		{"default-brex"   , no_argument      , 0, 'B'},
		{"brex"           , required_argument, 0, 'b'},
		{"dir"            , required_argument, 0, 'd'},
		{"ignore-empty"   , no_argument      , 0, 'e'},
		{"include"        , required_argument, 0, 'I'},
		{"xml"            , no_argument      , 0, 'x'},
		{"quiet"          , no_argument      , 0, 'q'},
		{"verbose"        , no_argument      , 0, 'v'},
		{"short"          , no_argument      , 0, 's'},
		{"layered"        , no_argument      , 0, 'l'},
		{"severity-levels", required_argument, 0, 'w'},
		{"sns"            , no_argument      , 0, 'S'},
		{"strict"         , no_argument      , 0, 't'},
		{"unstrict"       , no_argument      , 0, 'u'},
		{"progress"       , no_argument      , 0, 'p'},
		{"valid-filenames", no_argument      , 0, 'F'},
		{"filenames"      , no_argument      , 0, 'f'},
		{"omit-issue"     , no_argument      , 0, 'N'},
		{"notations"      , no_argument      , 0, 'n'},
		{"values"         , no_argument      , 0, 'c'},
		{"list"           , no_argument      , 0, 'L'},
		{"summary"        , no_argument      , 0, 'T'},
		{"recursive"      , no_argument      , 0, 'r'},
		{"output-valid"   , no_argument      , 0, 'o'},
		{"xpath-version"  , required_argument, 0, 'X'},
		{"remove-deleted" , no_argument      , 0, '^'},
		{"zenity-progress", no_argument      , 0, 0},
		LIBXML2_PARSE_LONGOPT_DEFS
		{0, 0, 0, 0}
	};
	int loptind = 0;

#if XPATH2_ENGINE == SAXON
	void *saxon_processor;

	saxon_processor = saxon_new_processor();
#elif XPATH2_ENGINE == XQILLA
	void *xqilla_impl;

	xqilla_impl = xqilla_initialize();

#endif

	search_dir = strdup(".");

	while ((c = getopt_long(argc, argv, sopts, lopts, &loptind)) != -1) {
		switch (c) {
			case 0:
				if (strcmp(lopts[loptind].name, "version") == 0) {
#if XPATH2_ENGINE == SAXON
					show_version(saxon_processor);
#else
					show_version();
#endif
					goto cleanup;
				} else if (strcmp(lopts[loptind].name, "zenity-progress") == 0) {
					show_progress = PROGRESS_ZENITY;
				}
				LIBXML2_PARSE_LONGOPT_HANDLE(lopts, loptind, optarg)
				break;
			case 'B':
				use_default_brex = true;
				break;
			case 'b':
				add_path(&brex_fnames, &num_brex_fnames, &BREX_MAX, optarg, &opts);
				break;
			case 'd':
				free(search_dir);
				search_dir = strdup(optarg);
				break;
			case 'I':
				add_path(&brex_search_paths, &num_brex_search_paths, &BREX_PATH_MAX, optarg, &opts);
				break;
			case 'x': xmlout = true; break;
			case 'q': opts.verbosity = SILENT; break;
			case 'v': opts.verbosity = VERBOSE; break;
			case 's': shortmsg = true; break;
			case 'l': opts.layered = true; break;
			case 'w': brsl_fname = strdup(optarg); break;
			case 'S': opts.check_sns = true; break;
			case 't': opts.strict_sns = true; break;
			case 'u': opts.unstrict_sns = true; break;
			case 'p': show_progress = PROGRESS_CLI; break;
			case 'F': show_fnames = SHOW_VALID; break;
			case 'f': show_fnames = SHOW_INVALID; break;
			case 'N': opts.ignore_issue = true; break;
			case 'n': opts.check_notations = true; break;
			case 'c': opts.check_values = true; break;
			case 'L': is_list = true; break;
			case 'T': show_stats = true; break;
			case 'r': recursive_search = true; break;
			case 'o': output_tree = true; break;
			case 'e': ignore_empty = true; break;
			case 'X': set_xpath_version(&opts, optarg); break;
			case '^':
				rem_delete = true;
				break;
			case 'h':
			case '?':
				show_help();
				goto cleanup;
		}
	}

	if (!brsl_fname) {
		char fname[PATH_MAX];
		if (find_config(fname, DEFAULT_BRSL_FNAME)) {
			brsl_fname = strdup(fname);
		}
	}

	if (optind < argc) {
		for (i = optind; i < argc; ++i) {
			if (is_list) {
				add_dmod_list(argv[i], &dmod_fnames, &num_dmod_fnames, &opts);
			} else {
				add_path(&dmod_fnames, &num_dmod_fnames, &DMOD_MAX, argv[i], &opts);
			}
		}
	} else if (is_list) {
		add_dmod_list(NULL, &dmod_fnames, &num_dmod_fnames, &opts);
	} else {
		strcpy(dmod_fnames[num_dmod_fnames++], "-");
		use_stdin = true;
	}

	if (brsl_fname) {
		brsl = read_xml_doc(brsl_fname);
	}

	outdoc = xmlNewDoc(BAD_CAST "1.0");
	brexCheck = xmlNewNode(NULL, BAD_CAST "brexCheck");
	xmlDocSetRootElement(outdoc, brexCheck);

	/* Add configuration info to XML report. */
	add_config_to_report(brexCheck, &opts);

	for (i = 0; i < num_dmod_fnames; ++i) {
		/* Indicates if a referenced BREX data module is used as
		 * opposed to one specified on the command line.
		 *
		 * The practical difference is that those specified on the
		 * command line are meant to apply to ALL data modules
		 * specified, while a referenced BREX only applies to the data
		 * module which referenced it. */
		bool ref_brex = false;

		xmlDocPtr dmod_doc = read_xml_doc(dmod_fnames[i]);

		if (!dmod_doc) {
			if (ignore_empty) {
				continue;
			} else if (use_stdin) {
				if (opts.verbosity > SILENT) fprintf(stderr, E_NODMOD_STDIN);
			} else {
				if (opts.verbosity > SILENT) fprintf(stderr, E_NODMOD, dmod_fnames[i]);
			}
			exit(EXIT_BAD_DMODULE);
		}

		if (num_brex_fnames == 0) {
			int err;

			strcpy(brex_fnames[0], "");

			/* Override the referenced BREX with a default BREX
			 * based on which issue of the specification a data
			 * module is written to.
			 */
			if (use_default_brex) {
				strcpy(brex_fnames[0], default_brex_dmc(dmod_doc));
			/* Find BREX file from the brexDmRef and store it in the
			 * list of BREX.
			 *
			 * If the object has no brexDmRef or the BREX is not
			 * found, skip it.
			 *
			 * Indicate a BREX error in the exit status code if the
			 * object references a BREX but it couldn't be located.
			 */
			} else if ((err = find_brex_fname_from_doc(
					brex_fnames[0], dmod_doc,
					brex_search_paths,
					num_brex_search_paths,
					dmod_fnames,
					num_dmod_fnames,
					&opts))) {
				if (use_stdin) {
					if (opts.verbosity > SILENT) fprintf(stderr, err == 1 ? E_NOBREX_STDIN : W_NOBREX_STDIN);
				} else {
					if (opts.verbosity > SILENT) fprintf(stderr, err == 1 ? E_NOBREX : W_NOBREX, dmod_fnames[i]);
				}

				/* BREX DM was referenced but not found. */
				if (err == 1) {
					exit(EXIT_BREX_NOT_FOUND);
				}

				xmlFreeDoc(dmod_doc);
				continue;
			}

			num_brex_fnames = 1;
			ref_brex = true;

			/* When using brexDmRef, if the data module is itself a
			 * BREX data module, include it as a BREX. */
			if (strcmp(brex_fnames[0], dmod_fnames[i]) != 0 && firstXPathNode(dmod_doc, NULL, "//brex")) {
				add_path(&brex_fnames, &num_brex_fnames, &BREX_MAX, dmod_fnames[i], &opts);
			}
		}

		if (opts.layered) {
			num_brex_fnames = add_layered_brex(&brex_fnames,
				num_brex_fnames, brex_search_paths,
				num_brex_search_paths,
				dmod_fnames, num_dmod_fnames, dmod_doc, &opts);
		}

#if XPATH2_ENGINE == SAXON
		status += check_brex(dmod_doc, dmod_fnames[i], brex_fnames, num_brex_fnames, brexCheck, &opts, saxon_processor);
#elif XPATH2_ENGINE == XQILLA
		status += check_brex(dmod_doc, dmod_fnames[i], brex_fnames, num_brex_fnames, brexCheck, &opts, xqilla_impl);
#else
		status += check_brex(dmod_doc, dmod_fnames[i], brex_fnames, num_brex_fnames, brexCheck, &opts);
#endif

		xmlFreeDoc(dmod_doc);

		switch (show_progress) {
			case PROGRESS_OFF:
				break;
			case PROGRESS_CLI:
				print_progress_bar(i, num_dmod_fnames);
				break;
			case PROGRESS_ZENITY:
				print_zenity_progress("Performing BREX check...", i, num_dmod_fnames);
				break;
		}

		/* If the referenced BREX was used, reset the BREX data module
		 * list, as each data module may reference a different BREX or
		 * set of BREX. */
		if (ref_brex)
			num_brex_fnames = 0;
	}

	if (num_dmod_fnames) {
		switch (show_progress) {
			case PROGRESS_OFF:
				break;
			case PROGRESS_CLI:
				print_progress_bar(i, num_dmod_fnames);
				break;
			case PROGRESS_ZENITY:
				print_zenity_progress("BREX check complete.", i, num_dmod_fnames);
				break;
		}
	}

	if (xmlout) {
		save_xml_doc(outdoc, "-");
	}

	if (show_stats) {
		print_stats(outdoc);
	}

	xmlFreeDoc(outdoc);

	if (brsl_fname) {
		xmlFreeDoc(brsl);
		free(brsl_fname);
	}

cleanup:
	xsltCleanupGlobals();
	xmlCleanupParser();

	free(brex_fnames);
	free(brex_search_paths);
	free(dmod_fnames);
	free(search_dir);

#if XPATH2_ENGINE == SAXON
	saxon_free_processor(saxon_processor);
	saxon_cleanup();
#elif XPATH2_ENGINE == XQILLA
	xqilla_terminate();
#endif

	if (status > 0) {
		return EXIT_BREX_ERROR;
	} else {
		return EXIT_SUCCESS;
	}
}
#endif


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