+
+/**
+ * @brief Replace one GTK <xi:include> element by its data.
+ *
+ * @param node
+ * A pointer to an <xi:include> element node.
+ *
+ * @return TRUE if we replaced the node, and FALSE otherwise.
+ *
+ */
+static gboolean process_one_xinclude(xmlNode* node) {
+ xmlChar* href;
+
+ href = xmlGetProp(node, BAD_CAST "href");
+ if (href == NULL) {
+ /* We only process XIncludes with base64 data hrefs */
+ return FALSE;
+ }
+
+ if (xmlStrncmp(href, BAD_CAST "data:text/xml;base64,", 21)) {
+ /* We only process XIncludes with base64 data hrefs */
+ return FALSE;
+ }
+
+ xmlChar* hrefdata = href+21;
+
+ /* Verify that hrefdata is base64-encoded (and that it's safe to
+ cast to a signed gchar pointer). I'm assuming that everyone is
+ using the RFC 4648 encoding? */
+ for (unsigned int i=0; i < xmlStrlen(hrefdata); i++) {
+ if (hrefdata[i] > 'z') {
+ return FALSE;
+ }
+ if (hrefdata[i] < '0' && hrefdata[i] != '+' && hrefdata[i] != '/') {
+ return FALSE;
+ }
+ }
+
+ /* WARNING: the xmlChar and guchar types here are compatible, but
+ the decoded data is not necessarily NULL-terminated, while all of
+ the libxml2 functions that operate on a xmlChar pointer assume
+ that they are. */
+ gsize decoded_size;
+ xmlChar* decoded = g_base64_decode((const gchar*)hrefdata, &decoded_size);
+
+ /* This cast is safe because signed and unsigned chars are the same size,
+ and xmlReadMemory is going to treat the data as binary anyway. */
+ xmlDoc* xinc_doc = xmlReadMemory((const char*)decoded,
+ decoded_size,
+ "xinclude.xml",
+ NULL,
+ 0);
+ g_free(decoded);
+
+ if (xinc_doc == NULL) {
+ return FALSE;
+ }
+
+ xmlNode* xinc_root = xmlDocGetRootElement(xinc_doc);
+ if (xinc_root == NULL || xmlStrcmp(xinc_root->name, BAD_CAST "svg")) {
+ return FALSE;
+ }
+
+ /* Replace the original xinclude "node" with the children of this
+ "svg" node. Do the order of the nodes in an SVG matter? I don't
+ know, but we go to a little bit of extra trouble here to ensure
+ that we put the replacement in the right place, i.e. after its
+ previous sibling (if there is one). */
+
+ xmlNode* p = xmlPreviousElementSibling(node);
+ xmlNode* cur_node;
+
+ /* If there is no previous sibling element, do one AddChild()
+ first. Then we're back to the case of a previous sibling. */
+ if (p) {
+ cur_node = xmlFirstElementChild(xinc_root);
+ }
+ else {
+ p = node->parent;
+ cur_node = xmlFirstElementChild(xinc_root);
+ if (cur_node) {
+ /* Without the xmlCopyNode, I get segfaults, and I don't care to
+ investigate why. */
+ p = xmlAddChild(p, xmlCopyNode(cur_node,1));
+ xmlReconciliateNs(p->doc, p);
+
+ cur_node = xmlNextElementSibling(cur_node);
+ }
+ }
+
+ g_assert(p != NULL); /* xmlAddChild didn't fail */
+
+ xmlUnlinkNode(node);
+ xmlFreeNode(node);
+
+ while (cur_node) {
+ p = xmlAddNextSibling(p, xmlCopyNode(cur_node,1));
+ xmlReconciliateNs(p->doc, p);
+ cur_node = xmlNextElementSibling(cur_node);
+ }
+
+ xmlFreeDoc(xinc_doc);
+
+ return TRUE;
+}
+
+/**
+ * @brief Replace all GTK <xi:include> elements in a tree by their data.
+ *
+ * @param node
+ * A node pointer, to the root of the tree.
+ *
+ * @return TRUE if we replaced any <xi:include> element nodes, and
+ * FALSE otherwise.
+ *
+ */
+static gboolean process_child_xincludes(xmlNode* a_node) {
+ gboolean result = FALSE;
+ xmlNode* cur_node = a_node;
+ xmlNode* next_node;
+
+ g_assert(cur_node == NULL || cur_node->type == XML_ELEMENT_NODE);
+
+ while (cur_node) {
+ if (!xmlStrcmp(cur_node->name, BAD_CAST "include")) {
+ /* process_one_xinclude() clobbers this node, so we need
+ to get its successor before calling that function. */
+ next_node = xmlNextElementSibling(cur_node);
+ if (process_one_xinclude(cur_node)) {
+ result = TRUE;
+ }
+ cur_node = next_node;
+ continue;
+ }
+
+ if (process_child_xincludes(xmlFirstElementChild(cur_node))) {
+ result = TRUE;
+ }
+ cur_node = xmlNextElementSibling(cur_node);
+ }
+
+ return result;
+}
+
+