..
/
download
#include <stdio.h>
#include <stdbool.h>
#include <string.h>
#include <unistd.h>
#include <getopt.h>
#include <libxml/tree.h>
#include <libxml/xmlsave.h>
#include "xml-utils.h"
#define PROG_NAME "xml-format"
#define VERSION "2.6.0"
/* Formatter options */
#define FORMAT_OVERWRITE 0x01
#define FORMAT_REMWSONLY 0x02
#define FORMAT_OMIT_DECL 0x04
#define FORMAT_COMPACT 0x08
#define FORMAT_INDENT 0x10
/* Determine whether the node is a textual node. Textual nodes are never
* subject to formatting or indenting.
*/
static bool is_text(const xmlNodePtr node)
{
return
node->type == XML_TEXT_NODE ||
node->type == XML_CDATA_SECTION_NODE;
}
/* The blank text node children in an element are considered removable only if
* ALL the text node children of that element are blank (no mixed content), or
* there are no text node children at all.
*
* If there is only a single blank text node child, it is considered removable
* only if FORMAT_REMWSONLY is set.
*/
static bool blanks_are_removable(xmlNodePtr node, int opts)
{
xmlNodePtr cur;
int i;
if (xmlNodeGetSpacePreserve(node) == 1) {
return false;
}
for (cur = node->children, i = 0; cur; cur = cur->next, ++i) {
if (is_text(cur) && !xmlIsBlankNode(cur)) {
return false;
}
}
return i > 1 || (node->children && !is_text(node->children)) || optset(opts, FORMAT_REMWSONLY);
}
/* Remove blank children. */
static void remove_blanks(xmlNodePtr node)
{
xmlNodePtr cur;
cur = node->children;
while (cur) {
xmlNodePtr next;
next = cur->next;
if (xmlIsBlankNode(cur)) {
xmlUnlinkNode(cur);
xmlFreeNode(cur);
}
cur = next;
}
}
/* Add indentation to nodes within non-blank nodes. */
static void indent(xmlNodePtr node)
{
xmlNodePtr cur;
int n = 0, i;
for (cur = node->parent; cur; cur = cur->parent) {
++n;
}
if (!node->prev) {
xmlAddPrevSibling(node, xmlNewText(BAD_CAST "\n"));
for (i = 1; i < n; ++i) {
xmlAddPrevSibling(node, xmlNewText(BAD_CAST xmlTreeIndentString));
}
}
for (i = node->next ? 1 : 2; i < n; ++i) {
xmlAddNextSibling(node, xmlNewText(BAD_CAST xmlTreeIndentString));
}
xmlAddNextSibling(node, xmlNewText(BAD_CAST "\n"));
}
/* Format XML nodes. */
static void format(xmlNodePtr node, int opts)
{
bool remblanks;
if ((remblanks = blanks_are_removable(node, opts))) {
remove_blanks(node);
}
if (remblanks || optset(opts, FORMAT_INDENT)) {
xmlNodePtr cur = node->children;
while (cur) {
xmlNodePtr next = cur->next;
format(cur, opts);
if (remblanks && optset(opts, FORMAT_INDENT) && !optset(opts, FORMAT_COMPACT)) {
indent(cur);
}
cur = next;
}
}
}
/* Format an XML file. */
static void format_file(const char *path, const char *out, int opts)
{
xmlDocPtr doc;
xmlSaveCtxtPtr save;
int saveopts = 0;
if (!(doc = read_xml_doc(path))) {
return;
}
format(xmlDocGetRootElement(doc), opts);
if (!optset(opts, FORMAT_COMPACT)) {
saveopts |= XML_SAVE_FORMAT;
}
if (optset(opts, FORMAT_OMIT_DECL)) {
saveopts |= XML_SAVE_NO_DECL;
}
if (out) {
save = xmlSaveToFilename(out, NULL, saveopts);
} else if (optset(opts, FORMAT_OVERWRITE)) {
save = xmlSaveToFilename(path, NULL, saveopts);
} else {
save = xmlSaveToFilename("-", NULL, saveopts);
}
xmlSaveDoc(save, doc);
xmlSaveClose(save);
xmlFreeDoc(doc);
}
/* Show usage message. */
static void show_help(void)
{
puts("Usage: " PROG_NAME " [-cfIOwh?] [-i <str>] [-o <path>] [<file>...]");
puts("");
puts("Options:");
puts(" -c, --compact Compact output.");
puts(" -f, --overwrite Overwrite input XML files.");
puts(" -h, -?, --help Show usage message.");
puts(" -I, --indent-all Indent nodes within non-blank nodes.");
puts(" -i, --indent <str> Set the indentation string.");
puts(" -O, --omit-decl Omit XML declaration.");
puts(" -o, --out <path> Output to <path> instead of stdout.");
puts(" -w, --empty Treat elements containing only whitespace as empty.");
puts(" --version Show version information.");
puts(" <file> XML file(s) to format. Otherwise, read from stdin.");
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\n", xmlParserVersion);
}
int main(int argc, char **argv)
{
int i;
const char *sopts = "cfIi:Oo:wh?";
struct option lopts[] = {
{"version" , no_argument , 0, 0},
{"help" , no_argument , 0, 'h'},
{"compact" , no_argument , 0, 'c'},
{"overwrite" , no_argument , 0, 'f'},
{"indent-all", no_argument , 0, 'I'},
{"indent" , required_argument, 0, 'i'},
{"omit-decl" , no_argument , 0, 'O'},
{"out" , required_argument, 0, 'o'},
{"empty" , no_argument , 0, 'w'},
LIBXML2_PARSE_LONGOPT_DEFS
{0, 0, 0, 0}
};
int loptind = 0;
int opts = 0;
char *indent = NULL;
char *out = NULL;
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':
opts |= FORMAT_COMPACT;
break;
case 'f':
opts |= FORMAT_OVERWRITE;
break;
case 'I':
opts |= FORMAT_INDENT;
break;
case 'i':
indent = strdup(optarg);
break;
case 'O':
opts |= FORMAT_OMIT_DECL;
break;
case 'o':
out = strdup(optarg);
break;
case 'w':
opts |= FORMAT_REMWSONLY;
break;
case 'h':
case '?':
show_help();
return 0;
}
}
if (indent) {
xmlTreeIndentString = indent;
}
if (optind < argc) {
for (i = optind; i < argc; ++i) {
format_file(argv[i], out, opts);
}
} else {
format_file("-", out, opts);
}
free(indent);
free(out);
xmlCleanupParser();
return 0;
}
gopher://khzae.net/0/s1kd/xml/xml-utils/src/utils/xml-format/xml-format.c