/ .. / / -> download
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <getopt.h>
#include <stdbool.h>
#include <libxml/tree.h>
#include <libxslt/transform.h>
#include "s1kd_tools.h"
#include "uom.h"

#define PROG_NAME "s1kd-uom"
#define VERSION "1.20.0"

#define ERR_PREFIX PROG_NAME ": ERROR: "
#define WRN_PREFIX PROG_NAME ": WARNING: "
#define INF_PREFIX PROG_NAME ": INFO: "
#define E_NO_UOM ERR_PREFIX "%s: Unit conversions must be specified as: -u <uom> -t <uom> [-e <expr>] [-F <fmt>]\n"
#define W_BAD_LIST WRN_PREFIX "Could not read list: %s\n"
#define W_NO_CONV WRN_PREFIX "No conversion defined for %s -> %s.\n"
#define W_NO_CONV_TO WRN_PREFIX "No target UOM given for %s.\n"
#define W_NO_PRESET WRN_PREFIX "No such preset: %s.\n"
#define W_BAD_PRESET WRN_PREFIX "Could not read set of conversions: %s\n"
#define I_CONVERT INF_PREFIX "Converting units in %s...\n"
#define EXIT_NO_CONV 1
#define EXIT_NO_UOM 2

static enum verbosity { QUIET, NORMAL, VERBOSE } verbosity = NORMAL;

/* Show usage message. */
static void show_help(void)
{
	puts("Usage: " PROG_NAME " [-dflqv,.h?] [-D <fmt>] [-F <fmt>] [-u <uom> -t <uom> [-e <expr>] [-F <fmt>] ...] [-p <fmt> [-P <path>]] [-s <name>|-S <path> ...] [-U <path>] [<object>...]");
	puts("");
	puts("  -D, --duplicate-format <fmt>  Custom format for duplicate quantities (-d).");
	puts("  -d, --duplicate               Include conversions as duplicate quantities in parenthesis.");
	puts("  -e, --formula <expr>          Specify formula for a conversion.");
	puts("  -F, --format <fmt>            Number format for converted values.");
	puts("  -f, --overwrite               Overwrite input objects.");
	puts("  -h, -?, --help                Show help/usage message.");
	puts("  -l, --list                    Treat input as list of CSDB objects.");
	puts("  -P, --uomdisplay <path>       Use custom UOM display file.");
	puts("  -p, --preformat <fmt>         Preformat quantity data.");
	puts("  -q, --quiet                   Quiet mode.");
	puts("  -S, --set <path>              Apply a custom set of conversions.");
	puts("  -s, --preset <name>           Apply a predefined set of conversions.");
	puts("  -t, --to <uom>                UOM to convert to.");
	puts("  -U, --uom <path>              Use custom .uom file.");
	puts("  -u, --from <uom>              UOM to convert from.");
	puts("  -v, --verbose                 Verbose output.");
	puts("  -,, --dump-uom                Dump default .uom file.");
	puts("  -., --dump-uomdisplay         Dump default UOM preformatting file.");
	puts("  --version                     Show version information.");
	puts("  <object>                      CSDB object to convert quantities in.");
	LIBXML2_PARSE_LONGOPT_HELP
}

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

/* Select specified UOM to convert. */
static void select_uoms(xmlNodePtr uom, xmlNodePtr conversions)
{
	xmlNodePtr cur;

	cur = uom->children;

	while (cur) {
		xmlNodePtr next;

		next = cur->next;

		if (cur->type == XML_ELEMENT_NODE) {
			xmlChar *uom_from, *uom_to;
			xmlNodePtr c;
			bool match = false;

			uom_from = xmlGetProp(cur, BAD_CAST "from");
			uom_to   = xmlGetProp(cur, BAD_CAST "to");

			c = conversions->children;
			while (c) {
				xmlNodePtr n;
				xmlChar *convert_from, *convert_to, *formula, *format;
				bool m;

				n = c->next;

				convert_from = xmlGetProp(c, BAD_CAST "from");
				convert_to   = xmlGetProp(c, BAD_CAST "to");
				formula      = xmlGetProp(c, BAD_CAST "formula");
				format       = xmlGetProp(c, BAD_CAST "format");

				if (!convert_to) {
					convert_to = xmlStrdup(convert_from);
				}

				m = convert_from &&
				    xmlStrcmp(uom_from, convert_from) == 0 &&
				    xmlStrcmp(uom_to, convert_to) == 0;
				
				xmlFree(convert_from);
				xmlFree(convert_to);

				if (m) {
					if (formula) {
						xmlSetProp(cur, BAD_CAST "formula", formula);
					}
					if (format) {
						xmlSetProp(cur, BAD_CAST "format", format);
					}

					xmlUnlinkNode(c);
					xmlFreeNode(c);

					match = true;
					break;
				}

				c = n;

				xmlFree(formula);
				xmlFree(format);
			}

			xmlFree(uom_from);
			xmlFree(uom_to);

			if (!match) {
				xmlUnlinkNode(cur);
				xmlFreeNode(cur);
			}
		} else {
			xmlUnlinkNode(cur);
			xmlFreeNode(cur);
		}

		cur = next;
	}

	cur = conversions->children;
	while (cur) {
		xmlNodePtr next;

		next = cur->next;

		if (xmlHasProp(cur, BAD_CAST "formula")) {
			xmlAddChild(uom, xmlCopyNode(cur, 1));
		} else if (verbosity >= NORMAL) {
			xmlChar *from, *to;

			from = xmlGetProp(cur, BAD_CAST "from");
			to   = xmlGetProp(cur, BAD_CAST "to");

			if (to) {
				fprintf(stderr, W_NO_CONV, (char *) from, (char *) to);
			} else {
				fprintf(stderr, W_NO_CONV_TO, (char *) from);
			}

			xmlFree(from);
			xmlFree(to);
		}

		xmlUnlinkNode(cur);
		xmlFreeNode(cur);

		cur = next;
	}
}

/* Transform a document with a pre-parsed stylesheet. */
static void transform_doc_with(xmlDocPtr doc, xmlDocPtr styledoc, const char **params)
{
	xmlDocPtr src, res, sdoc;
	xsltStylesheetPtr style;
	xmlNodePtr old;

	src = xmlCopyDoc(doc, 1);
	sdoc = xmlCopyDoc(styledoc, 1);

	style = xsltParseStylesheetDoc(sdoc);
	res = xsltApplyStylesheet(style, src, params);

	old = xmlDocSetRootElement(doc, xmlCopyNode(xmlDocGetRootElement(res), 1));
	xmlFreeNode(old);

	xmlFreeDoc(src);
	xmlFreeDoc(res);
	xsltFreeStylesheet(style);
}

/* Transform a document with an in-memory stylesheet. */
static void transform_doc(xmlDocPtr doc, unsigned char *xsl, unsigned int len, const char **params)
{
	xmlDocPtr styledoc;
	styledoc = read_xml_mem((const char *) xsl, len);
	transform_doc_with(doc, styledoc, params);
	xmlFreeDoc(styledoc);
}

/* Read the custom duplicate format to obtain the prefix and postfix. */
static void read_dupl_fmt(const char *duplfmt, char *prefix, char *postfix, int n)
{
	int i, j, s = n - 3;

	prefix[0] = '"';

	for (i = 0, j = 1; duplfmt[i] && j < s; ++i, ++j) {
		if (duplfmt[i] == '\\' && duplfmt[i + 1]) {
			switch (duplfmt[i + 1]) {
				case 'n': prefix[j] = '\n'; break;
				case 't': prefix[j] = '\t'; break;
				default:  prefix[j] = duplfmt[i + 1];
			}
			++i;
		} else if (duplfmt[i] == '%') {
			break;
		} else {
			prefix[j] = duplfmt[i];
		}
	}

	prefix[j++] = '"';
	prefix[j] = '\0';

	postfix[0] = '"';

	for (i = i + 1, j = 1; duplfmt[i] && j < s; ++i, ++j) {
		if (duplfmt[i] == '\\' && duplfmt[i + 1]) {
			switch (duplfmt[i + 1]) {
				case 'n': postfix[j] = '\n'; break;
				case 't': postfix[j] = '\t'; break;
				default:  postfix[j] = duplfmt[i + 1];
			}
			++i;
		} else {
			postfix[j] = duplfmt[i];
		}
	}

	postfix[j++] = '"';
	postfix[j] = '\0';
}

/* Convert UOM for a single data module. */
static void convert_uoms(const char *path, xmlDocPtr uom, const char *format, xmlDocPtr uomdisp, const char *dispfmt, xmlDocPtr dupl, const char *duplfmt, bool overwrite)
{
	xmlDocPtr doc;
	char *params[5];
	int i = 0;
	char *s;

	if (verbosity >= VERBOSE) {
		fprintf(stderr, I_CONVERT, path ? path : "-");
	}

	if (path) {
		doc = read_xml_doc(path);
	} else {
		doc = read_xml_doc("-");
	}

	if (!doc) {
		return;
	}

	if (dupl) {
		char *duplparams[5];

		if (duplfmt) {
			char prefix[256], postfix[256];

			read_dupl_fmt(duplfmt, prefix, postfix, 256);

			duplparams[0] = "prefix";
			duplparams[1] = prefix;
			duplparams[2] = "postfix";
			duplparams[3] = postfix;
			duplparams[4] = NULL;
		} else {
			duplparams[0] = NULL;
		}

		transform_doc(doc, dupl_xsl, dupl_xsl_len, (const char **) duplparams);

		params[i++] = "duplicate";
		params[i++] = "true()";
	}

	if (format) {
		params[i++] = "user-format";
		s = malloc(strlen(format) + 3);
		sprintf(s, "\"%s\"", format);
		params[i++] = s;
	}
	params[i++] = NULL;

	transform_doc(uom, uom_xsl, uom_xsl_len, (const char **) params);
	transform_doc_with(doc, uom, NULL);

	if (format) {
		free(s);
	}

	if (dupl) {
		transform_doc(doc, undupl_xsl, undupl_xsl_len, NULL);
	}

	if (dispfmt) {
		i = 0;
		params[i++] = "format";
		s = malloc(strlen(dispfmt) + 3);
		sprintf(s, "\"%s\"", dispfmt);
		params[i++] = s;
		params[i++] = NULL;

		transform_doc(uomdisp, uomdisplay_xsl, uomdisplay_xsl_len, (const char **) params);
		transform_doc_with(doc, uomdisp, NULL);

		free(s);
	}

	if (overwrite) {
		save_xml_doc(doc, path);
	} else {
		save_xml_doc(doc, "-");
	}

	xmlFreeDoc(doc);
}

/* Convert UOM for data modules given in a list. */
static void convert_uoms_list(const char *path, xmlDocPtr uom, const char *format, xmlDocPtr uomdisp, const char *dispfmt, xmlDocPtr dupl, const char *duplfmt, bool overwrite)
{
	FILE *f;
	char line[PATH_MAX];

	if (path) {
		if (!(f = fopen(path, "r"))) {
			if (verbosity >= NORMAL) {
				fprintf(stderr, W_BAD_LIST, path);
			}
			return;
		}
	} else {
		f = stdin;
	}

	while (fgets(line, PATH_MAX, f)) {
		strtok(line, "\t\r\n");
		convert_uoms(line, uom, format, uomdisp, dispfmt, dupl, duplfmt, overwrite);
	}

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

/* Load a predefined set of conversions. */
static void load_presets(xmlNodePtr convs, const char *preset, bool file)
{
	xmlDocPtr doc;
	xmlNodePtr cur;

	if (file) {
		doc = read_xml_doc(preset);
	} else {
		unsigned char *xml;
		unsigned int len;

		if (strcmp(preset, "SI") == 0) {
			xml = presets_SI_xml;
			len = presets_SI_xml_len;
		} else if (strcmp(preset, "imperial") == 0) {
			xml = presets_imperial_xml;
			len = presets_imperial_xml_len;
		} else if (strcmp(preset, "US") == 0) {
			xml = presets_US_xml;
			len = presets_US_xml_len;
		} else {
			if (verbosity >= NORMAL) {
				fprintf(stderr, W_NO_PRESET, preset);
			}
			return;
		}

		doc = read_xml_mem((const char *) xml, len);
	}

	if (!doc) {
		if (verbosity >= NORMAL) {
			fprintf(stderr, W_BAD_PRESET, preset);
		}
		return;
	}

	for (cur = xmlDocGetRootElement(doc)->children; cur; cur = cur->next) {
		if (xmlStrcmp(cur->name, BAD_CAST "convert") == 0) {
			xmlAddChild(convs, xmlCopyNode(cur, 1));
		}
	}

	xmlFreeDoc(doc);
}

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

	const char *sopts = "D:de:F:flP:p:qS:s:t:U:u:v,.h?";
	struct option lopts[] = {
		{"version"         , no_argument      , 0, 0},
		{"help"            , no_argument      , 0, 'h'},
		{"duplicate-format", required_argument, 0, 'D'},
		{"duplicate"       , no_argument      , 0, 'd'},
		{"formula"         , required_argument, 0, 'e'},
		{"format"          , required_argument, 0, 'F'},
		{"overwrite"       , no_argument      , 0, 'f'},
		{"list"            , no_argument      , 0, 'l'},
		{"uomdisplay"      , required_argument, 0, 'P'},
		{"preformat"       , required_argument, 0, 'p'},
		{"quiet"           , no_argument      , 0, 'q'},
		{"to"              , required_argument, 0, 't'},
		{"uom"             , required_argument, 0, 'U'},
		{"from"            , required_argument, 0, 'u'},
		{"set"             , required_argument, 0, 'S'},
		{"preset"          , required_argument, 0, 's'},
		{"verbose"         , no_argument      , 0, 'v'},
		{"dump-uom"        , no_argument      , 0, ','},
		{"dump-uomdisplay" , no_argument      , 0 ,'.'},
		LIBXML2_PARSE_LONGOPT_DEFS
		{0, 0, 0, 0}
	};
	int loptind = 0;

	bool list = false;
	bool overwrite = false;
	bool dump_uom = false;

	xmlDocPtr uom = NULL;
	xmlNodePtr conversions, cur = NULL;

	char *format = NULL;
	char uom_fname[PATH_MAX] = "";

	xmlDocPtr uomdisp = NULL;
	char uomdisp_fname[PATH_MAX] = "";
	char *dispfmt = NULL;
	bool dump_uomdisp = false;

	xmlDocPtr dupl = NULL;
	char *duplfmt = NULL;

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

	while ((i = getopt_long(argc, argv, sopts, lopts, &loptind)) != -1) {
		switch (i) {
			case 0:
				if (strcmp(lopts[loptind].name, "version") == 0) {
					show_version();
					return 0;
				}
				LIBXML2_PARSE_LONGOPT_HANDLE(lopts, loptind, optarg)
				break;
			case 'D':
				if (!duplfmt) {
					duplfmt = strdup(optarg);
				}
			case 'd':
				if (!dupl) {
					dupl = read_xml_mem((const char *) dupl_xsl, dupl_xsl_len);
				}
				break;
			case 'e':
				if (!cur) {
					if (verbosity >= NORMAL) {
						fprintf(stderr, E_NO_UOM, "-e");
					}
					exit(EXIT_NO_UOM);
				}
				xmlSetProp(cur, BAD_CAST "formula", BAD_CAST optarg);
				break;
			case 'F':
				if (cur) {
					xmlSetProp(cur, BAD_CAST "format", BAD_CAST optarg);
				} else {
					format = strdup(optarg);
				}
				break;
			case 'f':
				overwrite = true;
				break;
			case 'l':
				list = true;
				break;
			case 'P':
				strncpy(uomdisp_fname, optarg, PATH_MAX - 1);
				break;
			case 'p':
				dispfmt = strdup(optarg);
				break;
			case 'q':
				--verbosity;
				break;
			case 't':
				if (!cur) {
					if (verbosity >= NORMAL) {
						fprintf(stderr, E_NO_UOM, "-t");
					}
					exit(EXIT_NO_UOM);
				}
				xmlSetProp(cur, BAD_CAST "to", BAD_CAST optarg);
				break;
			case 'U':
				strncpy(uom_fname, optarg, PATH_MAX - 1);
				break;
			case 'u':
				cur = xmlNewChild(conversions, NULL, BAD_CAST "convert", NULL);
				xmlSetProp(cur, BAD_CAST "from", BAD_CAST optarg);
				break;
			case 'S':
				load_presets(conversions, optarg, true);
				break;
			case 's':
				load_presets(conversions, optarg, false);
				break;
			case 'v':
				++verbosity;
				break;
			case ',':
				dump_uom = true;
				break;
			case '.':
				dump_uomdisp = true;
				break;
			case 'h':
			case '?':
				show_help();
				return 0;
		}
	}

	/* Load .uom configuration file (or built-in copy). */
	if (!dump_uom && (strcmp(uom_fname, "") != 0 || find_config(uom_fname, DEFAULT_UOM_FNAME))) {
		uom = read_xml_doc(uom_fname);
	}
	if (!uom) {
		uom = read_xml_mem((const char *) uom_xml, uom_xml_len);
	}

	/* Load .uomdisplay configuration file (or built-in copy). */
	if (!dump_uomdisp && (strcmp(uomdisp_fname, "") != 0 || find_config(uomdisp_fname, DEFAULT_UOMDISP_FNAME))) {
		uomdisp = read_xml_doc(uomdisp_fname);
	}
	if (!uomdisp) {
		uomdisp = read_xml_mem((const char *) uomdisplay_xml, uomdisplay_xml_len);
	}

	if (conversions->children) {
		select_uoms(xmlDocGetRootElement(uom), conversions);
	}

	if (dump_uom) {
		save_xml_doc(uom, "-");
	} else if (dump_uomdisp) {
		save_xml_doc(uomdisp, "-");
	} else if (optind < argc) {
		for (i = optind; i < argc; ++i) {
			if (list) {
				convert_uoms_list(argv[i], uom, format, uomdisp, dispfmt, dupl, duplfmt, overwrite);
			} else {
				convert_uoms(argv[i], uom, format, uomdisp, dispfmt, dupl, duplfmt, overwrite);
			}
		}
	} else if (list) {
		convert_uoms_list(NULL, uom, format, uomdisp, dispfmt, dupl, duplfmt, overwrite);
	} else {
		convert_uoms(NULL, uom, format, uomdisp, dispfmt, dupl, duplfmt, false);
	}
	
	free(format);
	free(dispfmt);

	xmlFreeDoc(uom);
	xmlFreeNode(conversions);
	xmlFreeDoc(uomdisp);

	xmlFreeDoc(dupl);
	free(duplfmt);

	xsltCleanupGlobals();
	xmlCleanupParser();

	return 0;
}


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