+
+ 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;
+}
+
+
+/**
+ * @brief Process GTK <xi:include> elements in an SVG buffer.
+ *
+ * GTK is very cute. Its gtk-encode-symbolic-svg tool wraps your SVG
+ * in its own boilerplate, but then rather than including your SVG
+ * data verbatim, it includes it via a sketchy XInclude that looks
+ * like,
+ *
+ * <xi:include href="data:text/xml;base64,PD94bWwgd..."/>
+ *
+ * Librsvg knows how to parse that, but libxml2 doesn't (the latter
+ * can handle an XInclude, just not a base64-encoded data reference).
+ * Fortunately, you can read the source to gtk-encode-symbolic-svg,
+ * and just see what the format of that line will be. Here we're going
+ * to parse out the base64 data, decode it, strip out its opening
+ * <xml> and <svg> tags, and then replace the original <xi:include>
+ * element with the result.
+ *
+ * @param buffer
+ * A buffer containing SVG file data.
+ *
+ * @param buf_size
+ * The size of @c buffer (which may not be NULL-terminated).
+ *
+ * @param new_size
+ * A pointer to the size of the new buffer, valid only if the
+ * return value is non-NULL.
+ *
+ * @return A pointer to a buffer where the <xi:include> has been
+ * processed. If no replacements were made, the result will be @c
+ * NULL; otherwise, you are expected to @c free it when you are done.
+ */
+static gchar* process_gtk_symbolic_svg_xinclude(const gchar* buffer,
+ gsize buf_size,
+ gsize* new_size) {
+
+ xmlDoc* doc = xmlReadMemory(buffer,buf_size,"symbolic.xml",NULL,0);
+ if (doc == NULL) {
+ return NULL;
+ }
+
+ xmlNode* root_element = xmlDocGetRootElement(doc);
+ if (root_element == NULL) {
+ return NULL;
+ }
+
+ gchar* result = NULL;
+ if (process_child_xincludes(root_element)) {
+ /* If we actually replaced something, we need to return the new
+ document in a buffer. */
+ xmlChar *xmlbuf;
+ int xmlbuf_size;
+ xmlDocDumpFormatMemory(doc, &xmlbuf, &xmlbuf_size, 1);
+ /* We're going to free() this later on with g_free() instead of
+ xmlFree(), so the two "byte" types had better be the same
+ size. */
+ g_assert(sizeof(xmlChar) == sizeof(gchar));
+ *new_size = (gsize)xmlbuf_size;
+ result = (gchar*)xmlbuf;
+ }
+
+ xmlFreeDoc(doc);
+ xmlCleanupParser();
+ return result;