..
/
download
#include <stdio.h>
#include <unistd.h>
#include <getopt.h>
#include <stdbool.h>
#include <string.h>
#include <errno.h>
#include <libxml/tree.h>
#include <libxml/xpath.h>
#include <libxml/xmlsave.h>
#include <libxml/xpathInternals.h>
#include <libxslt/xslt.h>
#include <libxslt/transform.h>
#include <libxslt/xsltutils.h>
#include <libexslt/exslt.h>
#include "xml-utils.h"
#include "identity.h"
#define PROG_NAME "xml-transform"
#define VERSION "1.4.0"
#define INF_PREFIX PROG_NAME ": INFO: "
#define ERR_PREFIX PROG_NAME ": ERROR: "
#define I_TRANSFORM INF_PREFIX "Transforming %s...\n"
#define E_BAD_LIST ERR_PREFIX "Could not read list: %s: %s\n"
#define E_FILE_NO_WRITE ERR_PREFIX "Could not open file for writing: %s: %s\n"
#define EXIT_OS_ERROR 1
static enum verbosity { QUIET, NORMAL, VERBOSE } verbosity = NORMAL;
static bool preserve_dtd = false;
static bool use_xml_stylesheets = false;
static xmlNodePtr global_params;
/* Add identity template to stylesheet. */
static void add_identity(xmlDocPtr style)
{
xmlDocPtr identity;
xmlNodePtr stylesheet, first, template;
identity = read_xml_mem((const char *) identity_xsl, identity_xsl_len);
template = xmlFirstElementChild(xmlDocGetRootElement(identity));
stylesheet = xmlDocGetRootElement(style);
first = xmlFirstElementChild(stylesheet);
if (first) {
xmlAddPrevSibling(first, xmlCopyNode(template, 1));
} else {
xmlAddChild(stylesheet, xmlCopyNode(template, 1));
}
xmlFreeDoc(identity);
}
/* Apply stylesheets to a doc, preserving the original DTD. */
static xmlDocPtr transform_doc_preserve_dtd(xmlDocPtr doc, xmlNodePtr stylesheets)
{
xmlDocPtr src;
xmlNodePtr cur, new;
src = xmlCopyDoc(doc, 1);
for (cur = stylesheets->children; cur; cur = cur->next) {
xmlDocPtr res;
xsltStylesheetPtr style;
const char **params;
/* Select cached stylesheet/params. */
style = (xsltStylesheetPtr) cur->doc;
params = (const char **) cur->children;
res = xsltApplyStylesheet(style, doc, params);
xmlFreeDoc(doc);
doc = res;
}
/* If the result has a root element, copy it in place of the root
* element of the original document to preserve the original DTD. */
if ((new = xmlDocGetRootElement(doc))) {
xmlNodePtr old;
old = xmlDocSetRootElement(src, xmlCopyNode(new, 1));
xmlFreeNode(old);
/* Otherwise, copy the whole doc to keep non-XML results. */
} else {
xmlFreeDoc(src);
src = xmlCopyDoc(doc, 1);
}
xmlFreeDoc(doc);
return src;
}
/* Apply stylesheets to a doc. */
static xmlDocPtr transform_doc(xmlDocPtr doc, xmlNodePtr stylesheets)
{
xmlNodePtr cur;
for (cur = stylesheets->children; cur; cur = cur->next) {
xmlDocPtr res;
xsltStylesheetPtr style;
const char **params;
/* Select cached stylesheet/params. */
style = (xsltStylesheetPtr) cur->doc;
params = (const char **) cur->children;
res = xsltApplyStylesheet(style, doc, params);
xmlFreeDoc(doc);
doc = res;
}
return doc;
}
/* Save a document using the output settings of the specified stylesheet. */
static void save_doc(xmlDocPtr doc, const char *path, xsltStylesheetPtr style)
{
if (xmlStrcmp(style->method, BAD_CAST "text") == 0) {
FILE *f;
if (strcmp(path, "-") == 0) {
f = stdout;
} else {
f = fopen(path, "w");
}
if (!f) {
fprintf(stderr, E_FILE_NO_WRITE, path, strerror(errno));
exit(EXIT_OS_ERROR);
}
if (doc && doc->children && doc->children->content) {
fprintf(f, "%s", (char *) doc->children->content);
}
if (f != stdout) {
fclose(f);
}
} else {
xmlSaveCtxtPtr save;
int saveopts = 0;
if (xmlStrcmp(style->method, BAD_CAST "html") == 0) {
saveopts |= XML_SAVE_AS_HTML;
}
if (style->omitXmlDeclaration == 1) {
saveopts |= XML_SAVE_NO_DECL;
}
if (style->indent == 1) {
saveopts |= XML_SAVE_FORMAT;
}
save = xmlSaveToFilename(path, (char *) style->encoding, saveopts);
xmlSaveDoc(save, doc);
xmlSaveClose(save);
}
}
static xmlNodePtr xml_stylesheet_node(const xmlNodePtr pi)
{
xmlChar *content;
xmlChar *xml;
int n;
xmlDocPtr d;
xmlNodePtr root, node;
content = pi->content;
n = xmlStrlen(content) + 6;
xml = malloc(n * sizeof(xmlChar));
xmlStrPrintf(xml, n, "<x %s/>", content);
d = xmlParseDoc(xml);
xmlFree(xml);
root = xmlDocGetRootElement(d);
node = xmlCopyNode(root, 1);
xmlFreeDoc(d);
return node;
}
/* Read param from XML and encode in params list. */
static void read_param(const char **params, int *n, xmlNodePtr param)
{
char *name, *value;
name = (char *) xmlGetProp(param, BAD_CAST "name");
value = (char *) xmlGetProp(param, BAD_CAST "value");
params[(*n)++] = name;
params[(*n)++] = value;
}
/* Load stylesheet from disk and cache. */
static void load_stylesheet(xmlNodePtr cur, const bool include_identity)
{
xmlChar *path;
xmlDocPtr doc;
xsltStylesheetPtr style;
unsigned short nparams;
const char **params = NULL;
path = xmlGetProp(cur, BAD_CAST "path");
doc = read_xml_doc((char *) path);
xmlFree(path);
if (include_identity) {
add_identity(doc);
}
style = xsltParseStylesheetDoc(doc);
if (style == NULL) {
xmlFreeDoc(doc);
xmlUnlinkNode(cur);
xmlFreeNode(cur);
return;
}
cur->doc = (xmlDocPtr) style;
if ((nparams = xmlChildElementCount(cur) + xmlChildElementCount(global_params)) > 0) {
xmlNodePtr param;
int n = 0;
params = malloc((nparams * 2 + 1) * sizeof(char *));
param = cur->children;
while (param) {
xmlNodePtr next = param->next;
read_param(params, &n, param);
xmlFreeNode(param);
param = next;
}
param = global_params->children;
while (param) {
xmlNodePtr next = param->next;
read_param(params, &n, param);
param = next;
}
params[n] = NULL;
}
cur->children = (xmlNodePtr) params;
cur->line = nparams;
}
/* Load stylesheets from disk and cache. */
static void load_stylesheets(xmlNodePtr stylesheets, const bool include_identity)
{
xmlNodePtr cur;
if (stylesheets == NULL) {
return;
}
cur = stylesheets->children;
while (cur) {
xmlNodePtr next = cur->next;
load_stylesheet(cur, include_identity);
cur = next;
}
}
/* Get stylesheets from xml-stylesheet instructions. */
static xmlNodePtr get_xml_stylesheets(xmlDocPtr doc)
{
xmlXPathContextPtr ctx;
xmlXPathObjectPtr obj;
xmlNodePtr stylesheets;
stylesheets = xmlNewNode(NULL, BAD_CAST "stylesheets");
ctx = xmlXPathNewContext(doc);
obj = xmlXPathEval(BAD_CAST "//processing-instruction('xml-stylesheet')", ctx);
if (!xmlXPathNodeSetIsEmpty(obj->nodesetval)) {
int i;
for (i = 0; i < obj->nodesetval->nodeNr; ++i) {
xmlNodePtr xml_stylesheet, style;
xmlChar *href;
xml_stylesheet = xml_stylesheet_node(obj->nodesetval->nodeTab[i]);
href = xmlGetProp(xml_stylesheet, BAD_CAST "href");
style = xmlNewChild(stylesheets, NULL, BAD_CAST "stylesheet", NULL);
xmlSetProp(style, BAD_CAST "path", href);
xmlFree(href);
xmlFreeNode(xml_stylesheet);
}
load_stylesheets(stylesheets, false);
}
xmlXPathFreeObject(obj);
xmlXPathFreeContext(ctx);
return stylesheets;
}
/* Free a cached stylesheet. */
static void free_stylesheet(xmlNodePtr cur)
{
const char **params;
int i;
unsigned short nparams;
xsltFreeStylesheet((xsltStylesheetPtr) cur->doc);
cur->doc = NULL;
params = (const char **) cur->children;
nparams = cur->line;
for (i = 0; i < nparams * 2; ++i) {
xmlFree((char *) params[i]);
}
free(params);
cur->children = NULL;
}
/* Free cached stylesheets. */
static void free_stylesheets(xmlNodePtr stylesheets)
{
xmlNodePtr cur;
if (stylesheets == NULL) {
return;
}
cur = stylesheets->children;
while (cur) {
xmlNodePtr next = cur->next;
free_stylesheet(cur);
cur = next;
}
xmlFreeNode(stylesheets);
}
/* Apply stylesheets to a file. */
static void transform_file(const char *path, xmlNodePtr stylesheets, const char *out, bool overwrite)
{
xmlDocPtr doc;
xsltStylesheetPtr last = NULL;
xmlNodePtr xml_stylesheets = NULL;
if (verbosity >= VERBOSE) {
fprintf(stderr, I_TRANSFORM, path);
}
doc = read_xml_doc(path);
/* Transform using associated xml-stylesheets. */
if (use_xml_stylesheets) {
xml_stylesheets = get_xml_stylesheets(doc);
if (xml_stylesheets->children != NULL) {
if (preserve_dtd) {
doc = transform_doc_preserve_dtd(doc, xml_stylesheets);
} else {
doc = transform_doc(doc, xml_stylesheets);
}
}
}
/* Transform using user-specified stylesheets. */
if (preserve_dtd) {
doc = transform_doc_preserve_dtd(doc, stylesheets);
} else {
doc = transform_doc(doc, stylesheets);
}
/* Use the output settings of the last stylesheet to determine how to
* save the end result. */
if (stylesheets != NULL && stylesheets->last != NULL) {
last = (xsltStylesheetPtr) stylesheets->last->doc;
} else if (xml_stylesheets != NULL && xml_stylesheets->last != NULL) {
last = (xsltStylesheetPtr) xml_stylesheets->last->doc;
}
if (last != NULL) {
if (overwrite) {
save_doc(doc, path, last);
} else {
save_doc(doc, out, last);
}
/* If no stylesheets are specified, save as-is. */
} else {
if (overwrite) {
save_xml_doc(doc, path);
} else {
save_xml_doc(doc, out);
}
}
if (use_xml_stylesheets) {
free_stylesheets(xml_stylesheets);
}
xmlFreeDoc(doc);
}
/* Apply stylesheets to a list of files. */
static void transform_list(const char *path, xmlNodePtr stylesheets, const char *out, bool overwrite)
{
FILE *f;
char line[PATH_MAX];
if (path) {
if (!(f = fopen(path, "r"))) {
if (verbosity >= NORMAL) {
fprintf(stderr, E_BAD_LIST, path, strerror(errno));
}
return;
}
} else {
f = stdin;
}
while (fgets(line, PATH_MAX, f)) {
strtok(line, "\t\r\n");
transform_file(line, stylesheets, out, overwrite);
}
if (path) {
fclose(f);
}
}
/* Add a parameter to a stylesheet. */
static void add_param(xmlNodePtr stylesheet, char *s)
{
char *n, *v;
xmlNodePtr p;
n = strtok(s, "=");
v = strtok(NULL, "");
p = xmlNewChild(stylesheet, NULL, BAD_CAST "param", NULL);
xmlSetProp(p, BAD_CAST "name", BAD_CAST n);
xmlSetProp(p, BAD_CAST "value", BAD_CAST v);
}
/* Combine a single file into the combined document. */
static void combine_file(xmlNodePtr combined, const char *path)
{
xmlDocPtr doc = read_xml_doc(path);
xmlAddChild(combined, xmlCopyNode(xmlDocGetRootElement(doc), 1));
xmlFreeDoc(doc);
}
/* Combine a list of files into the combined document. */
static void combine_file_list(xmlNodePtr combined, const char *path)
{
FILE *f;
char line[PATH_MAX];
if (path) {
if (!(f = fopen(path, "r"))) {
if (verbosity >= NORMAL) {
fprintf(stderr, E_BAD_LIST, path, strerror(errno));
}
return;
}
} else {
f = stdin;
}
while (fgets(line, PATH_MAX, f)) {
strtok(line, "\t\r\n");
combine_file(combined, line);
}
if (path) {
fclose(f);
}
}
/* Transform input files as as combined document. */
static void transform_combined(int argc, char **argv, bool islist, const char *out, xmlNodePtr stylesheets)
{
xmlDocPtr doc;
xmlNodePtr combined;
doc = xmlNewDoc(BAD_CAST "1.0");
combined = xmlNewNode(NULL, BAD_CAST "combined");
xmlDocSetRootElement(doc, combined);
/* Combine all input files into a single document. */
if (optind < argc) {
int i;
for (i = optind; i < argc; ++i) {
if (islist) {
combine_file_list(combined, argv[i]);
} else {
combine_file(combined, argv[i]);
}
}
} else if (islist) {
combine_file_list(combined, NULL);
} else {
combine_file(combined, "-");
}
doc = transform_doc(doc, stylesheets);
/* Use the output settings of the last stylesheet to determine how to
* save the end result. */
if (stylesheets->last) {
xsltStylesheetPtr last;
last = (xsltStylesheetPtr) stylesheets->last->doc;
save_doc(doc, out, last);
/* If no stylesheets were specified, save as-is. */
} else {
save_xml_doc(doc, out);
}
xmlFreeDoc(doc);
}
/* Show help/usage message. */
static void show_help(void)
{
puts("Usage: " PROG_NAME " [-s <stylesheet> [-p <name>=<value> ...] ...] [-o <file>] [-cdfilqSvh?] [<file>...]");
puts("");
puts("Options:");
puts(" -c, --combine Combine input files into a single document.");
puts(" -d, --preserve-dtd Preserve the original DTD.");
puts(" -f, --overwrite Overwrite input files.");
puts(" -h, -?, --help Show usage message.");
puts(" -i, --identity Include identity template in stylesheets.");
puts(" -l, --list Treat input as list of files.");
puts(" -o, --out <file> Output result of transformation to <path>.");
puts(" -p, --param <name>=<value> Pass parameters to stylesheets.");
puts(" -q, --quiet Quiet mode.");
puts(" -S, --xml-stylesheets Apply associated stylesheets.");
puts(" -s, --stylesheet <stylesheet> Apply XSLT stylesheet to XML documents.");
puts(" -v, --verbose Verbose output.");
puts(" --version Show version information.");
puts(" <file> XML documents to apply transformations to.");
LIBXML2_PARSE_LONGOPT_HELP
}
/* Show version information. */
static void show_version(void)
{
printf("%s (xml-utils) %s\n", PROG_NAME, VERSION);
printf("Using libxml %s, libxslt %s and libexslt %s\n",
xmlParserVersion, xsltEngineVersion, exsltLibraryVersion);
}
int main(int argc, char **argv)
{
int i;
xmlNodePtr stylesheets, last_style = NULL;
char *out = strdup("-");
bool overwrite = false;
bool islist = false;
bool include_identity = false;
bool combine = false;
const char *sopts = "cdSs:ilo:p:qfvh?";
struct option lopts[] = {
{"version" , no_argument , 0, 0},
{"combine" , no_argument , 0, 'c'},
{"preserve-dtd" , no_argument , 0, 'd'},
{"help" , no_argument , 0, 'h'},
{"identity" , no_argument , 0, 'i'},
{"list" , no_argument , 0, 'l'},
{"out" , required_argument, 0, 'o'},
{"param" , required_argument, 0, 'p'},
{"quiet" , no_argument , 0, 'q'},
{"xml-stylesheets", no_argument , 0, 'S'},
{"stylesheet" , required_argument, 0, 's'},
{"verbose" , no_argument , 0, 'v'},
LIBXML2_PARSE_LONGOPT_DEFS
{0, 0, 0, 0}
};
int loptind = 0;
exsltRegisterAll();
stylesheets = xmlNewNode(NULL, BAD_CAST "stylesheets");
global_params = xmlNewNode(NULL, BAD_CAST "params");
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 'c':
combine = true;
break;
case 'd':
preserve_dtd = true;
break;
case 'S':
use_xml_stylesheets = true;
break;
case 's':
last_style = xmlNewChild(stylesheets, NULL, BAD_CAST "stylesheet", NULL);
xmlSetProp(last_style, BAD_CAST "path", BAD_CAST optarg);
break;
case 'i':
include_identity = true;
break;
case 'l':
islist = true;
break;
case 'o':
free(out);
out = strdup(optarg);
break;
case 'p':
if (last_style == NULL) {
add_param(global_params, optarg);
} else {
add_param(last_style, optarg);
}
break;
case 'q':
--verbosity;
break;
case 'f':
overwrite = true;
break;
case 'v':
++verbosity;
break;
case 'h':
case '?':
show_help();
return 0;
}
}
load_stylesheets(stylesheets, include_identity);
if (combine) {
transform_combined(argc, argv, islist, out, stylesheets);
} else {
if (optind < argc) {
for (i = optind; i < argc; ++i) {
if (islist) {
transform_list(argv[i], stylesheets, out, overwrite);
} else {
transform_file(argv[i], stylesheets, out, overwrite);
}
}
} else if (islist) {
transform_list(NULL, stylesheets, out, overwrite);
} else {
transform_file("-", stylesheets, out, false);
}
}
if (out) {
free(out);
}
free_stylesheets(stylesheets);
xmlFreeNode(global_params);
xsltCleanupGlobals();
xmlCleanupParser();
return 0;
}
gopher://khzae.net/0/s1000d/xml/xml-utils/src/utils/xml-transform/xml-transform.c