#include "xml-template.h"
#include <string.h>
+#include <libxml/parser.h>
using namespace std;
+namespace {
+
+void clean_node(xmlNode *node)
+{
+ if (node->type != XML_ELEMENT_NODE) {
+ return;
+ }
+ if (node->ns != NULL &&
+ strcmp(reinterpret_cast<const char *>(node->ns->href), "http://template.sesse.net/") == 0) {
+ xmlNode *frag = xmlNewDocFragment(node->doc);
+ xmlReplaceNode(node, frag);
+ frag->children = node->children;
+ frag->last = node->last;
+
+ node->children = node->last = NULL;
+ xmlFreeNode(node);
+ }
+}
+
+// Does A begin with B?
+bool begins_with(const string &a, const string &b)
+{
+ return (a.compare(0, b.size(), b) == 0);
+}
+
+} // namespace
+
+Directive::~Directive() {}
+
+string Directive::get_contents() { return ""; }
+
Replace::Replace(const string &str)
: str(str) {}
void Replace::process(xmlNode *node, bool clean) {
- node->children = xmlNewTextLen(reinterpret_cast<const xmlChar *>(str.data()), str.size());
+ xmlNodeSetContentLen(node, reinterpret_cast<const xmlChar *>(str.data()), str.size());
+ if (clean) {
+ clean_node(node);
+ }
+}
+
+string Replace::get_contents() { return str; }
+
+ReplaceInclude::ReplaceInclude(xmlNodePtr included_node)
+ : included_node(included_node),
+ included_doc(NULL) {}
+
+ReplaceInclude::ReplaceInclude(xmlDocPtr included_doc)
+ : included_node(xmlDocGetRootElement(included_doc)),
+ included_doc(included_doc) {}
+
+ReplaceInclude::~ReplaceInclude()
+{
+ if (included_doc != NULL) {
+ xmlFreeDoc(included_doc);
+ } else {
+ xmlFreeNode(included_node);
+ }
+}
+
+void ReplaceInclude::process(xmlNode *node, bool clean) {
+ xmlFreeNodeList(node->children);
+ node->content = NULL;
+ node->children = node->last = NULL;
+
+ xmlNode *new_node;
+ xmlDOMWrapCloneNode(NULL, NULL, included_node, &new_node, node->doc, node, 1, 0);
+ xmlAddChild(node, new_node);
+
+ if (clean) {
+ Substitute dummy_subs {};
+ dummy_subs.process(new_node, clean);
+ clean_node(node);
+ }
+}
+
+Clone::Clone(const vector<Directive *> &subdirectives)
+ : subdirectives(subdirectives) {}
+
+Clone::Clone(const vector<Substitute *> &subdirectives_subs)
+{
+ for (auto it : subdirectives_subs) {
+ subdirectives.push_back(static_cast<Directive *>(it));
+ }
+}
+
+Clone::Clone(initializer_list<Directive *> subdirectives)
+ : subdirectives(subdirectives) {}
+
+Clone::~Clone()
+{
+ for (auto it : subdirectives) {
+ delete it;
+ }
+}
+
+void Clone::process(xmlNode *node, bool clean)
+{
+ // We can't use xmlNewDocFragment, since xmlDOMWrapCloneNode only knows
+ // how to clone elements.
+ vector<xmlNode *> new_nodes;
+
+ for (auto it : subdirectives) {
+ if (it == NULL) {
+ continue;
+ }
+ xmlNode *new_node;
+ xmlDOMWrapCloneNode(NULL, node->doc, node, &new_node, node->doc, NULL, 1, 0);
+ it->process(new_node, clean);
+ while (new_node->children != NULL) {
+ xmlNode *child = new_node->children;
+ xmlUnlinkNode(child);
+ new_nodes.push_back(child);
+ }
+ xmlFreeNode(new_node);
+ }
+
+ xmlFreeNodeList(node->children);
+ node->content = NULL;
+ node->children = node->last = NULL;
+
+ for (auto child : new_nodes) {
+ xmlAddChild(node, child);
+ }
+ if (clean) {
+ clean_node(node);
+ }
+}
+
+Alternate::Alternate(const string &attribute,
+ const vector<Substitute *> &subdirectives_subs,
+ const vector<string> &alternatives)
+ : Clone(subdirectives_subs)
+{
+ unsigned jx = 0;
+ for (unsigned ix = 0; ix < subdirectives_subs.size(); ++ix) {
+ if (subdirectives_subs[ix] == NULL) {
+ continue;
+ }
+ string value = alternatives[jx++ % alternatives.size()];
+ subdirectives_subs[ix]->substitution_map.insert(make_pair(
+ attribute,
+ new Replace { value }));
+ }
}
Substitute::Substitute(const unordered_map<string, Directive*> &substitution_map)
: substitution_map(substitution_map) {}
+
+Substitute::Substitute(initializer_list<pair<const string, Directive*>> substitution_map)
+ : substitution_map(substitution_map) {}
+
+Substitute::~Substitute()
+{
+ for (auto it : substitution_map) {
+ delete it.second;
+ }
+}
-void Substitute::process(xmlNode *node, bool clean) {
- for (xmlNode *child = node->children; child != NULL; child = child->next) {
+void Substitute::process(xmlNode *node, bool clean)
+{
+ xmlNode *next_child;
+ for (xmlNode *child = node->children; child != NULL; child = next_child) {
+ next_child = child->next;
bool processed = false;
if (child->type == XML_ELEMENT_NODE) {
// Find the ID, if any.
string id;
+ xmlAttr *id_attr = NULL;
for (xmlAttr *attr = child->properties; attr != NULL; attr = attr->next) {
- if (strcmp(reinterpret_cast<const char *>(attr->ns->href), "http://template.sesse.net/") == 0 &&
+ if (attr->ns != NULL &&
+ strcmp(reinterpret_cast<const char *>(attr->ns->href), "http://template.sesse.net/") == 0 &&
strcmp(reinterpret_cast<const char *>(attr->name), "id") == 0) {
- id = reinterpret_cast<const char *>(xmlNodeGetContent(attr->children));
+ xmlChar *id_buf = xmlNodeGetContent(attr->children);
+ id = reinterpret_cast<const char *>(id_buf);
+ xmlFree(id_buf);
+ id_attr = attr;
}
}
+ if (clean && id_attr != NULL) {
+ xmlRemoveProp(id_attr);
+ }
// Check all substitutions to see if we found anything appropriate.
for (auto it : substitution_map) {
- if (it.first == reinterpret_cast<const char *>(child->name) ||
+ string tag = reinterpret_cast<const char *>(child->name);
+
+ // Attribute substitution.
+ if (begins_with(it.first, tag + "/")) {
+ const xmlChar *attr_key = reinterpret_cast<const xmlChar *>(
+ it.first.c_str() + tag.size() + 1);
+ const xmlChar *attr_value = reinterpret_cast<const xmlChar *>(
+ it.second->get_contents().c_str());
+ xmlSetProp(child, attr_key, attr_value);
+ } else if ((!id.empty() && begins_with(it.first, "#" + id + "/"))) {
+ const xmlChar *attr_key = reinterpret_cast<const xmlChar *>(
+ it.first.c_str() + tag.size() + 2);
+ const xmlChar *attr_value = reinterpret_cast<const xmlChar *>(
+ it.second->get_contents().c_str());
+ xmlSetProp(child, attr_key, attr_value);
+ }
+
+ if (processed) {
+ continue;
+ }
+
+ // Regular substitution.
+ if (it.first == tag ||
(!id.empty() && it.first == ("#" + id))) {
it.second->process(child, clean);
processed = true;
- break;
}
}
}
-
+
if (!processed) {
process(child, clean);
}
}
+ if (clean) {
+ clean_node(node);
+ }
+}
+
+xmlDocPtr process_file(const string &input_filename,
+ Directive *root_directive,
+ bool clean)
+{
+ LIBXML_TEST_VERSION
+
+ xmlDocPtr doc = xmlParseFile(input_filename.c_str());
+ root_directive->process(xmlDocGetRootElement(doc), clean);
+ xmlCleanupParser();
+ xmlMemoryDump();
+ return doc;
+}
+
+void output_to_fd_and_free(xmlDocPtr doc, int fd)
+{
+ xmlOutputBufferPtr buf = xmlOutputBufferCreateFd(fd, NULL);
+ xmlSaveFileTo(buf, doc, NULL);
+ xmlFreeDoc(doc);
}