#include #include #include #include #include #include #include #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 ] [-o ] [...]"); 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 Set the indentation string."); puts(" -O, --omit-decl Omit XML declaration."); puts(" -o, --out Output to instead of stdout."); puts(" -w, --empty Treat elements containing only whitespace as empty."); puts(" --version Show version information."); puts(" 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; }