summaryrefslogtreecommitdiff
path: root/libpurple/xmlnode.c
diff options
context:
space:
mode:
Diffstat (limited to 'libpurple/xmlnode.c')
-rw-r--r--libpurple/xmlnode.c640
1 files changed, 640 insertions, 0 deletions
diff --git a/libpurple/xmlnode.c b/libpurple/xmlnode.c
new file mode 100644
index 0000000000..0e30593939
--- /dev/null
+++ b/libpurple/xmlnode.c
@@ -0,0 +1,640 @@
+/**
+ * @file xmlnode.c XML DOM functions
+ *
+ * gaim
+ *
+ * Gaim is the legal property of its developers, whose names are too numerous
+ * to list here. Please refer to the COPYRIGHT file distributed with this
+ * source distribution.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+/* A lot of this code at least resembles the code in libxode, but since
+ * libxode uses memory pools that we simply have no need for, I decided to
+ * write my own stuff. Also, re-writing this lets me be as lightweight
+ * as I want to be. Thank you libxode for giving me a good starting point */
+
+#include "internal.h"
+
+#include <libxml/parser.h>
+#include <string.h>
+#include <glib.h>
+
+#include "dbus-maybe.h"
+#include "util.h"
+#include "xmlnode.h"
+
+#ifdef _WIN32
+# define NEWLINE_S "\r\n"
+#else
+# define NEWLINE_S "\n"
+#endif
+
+static xmlnode*
+new_node(const char *name, XMLNodeType type)
+{
+ xmlnode *node = g_new0(xmlnode, 1);
+
+ node->name = g_strdup(name);
+ node->type = type;
+
+ GAIM_DBUS_REGISTER_POINTER(node, xmlnode);
+
+ return node;
+}
+
+xmlnode*
+xmlnode_new(const char *name)
+{
+ g_return_val_if_fail(name != NULL, NULL);
+
+ return new_node(name, XMLNODE_TYPE_TAG);
+}
+
+xmlnode *
+xmlnode_new_child(xmlnode *parent, const char *name)
+{
+ xmlnode *node;
+
+ g_return_val_if_fail(parent != NULL, NULL);
+ g_return_val_if_fail(name != NULL, NULL);
+
+ node = new_node(name, XMLNODE_TYPE_TAG);
+
+ xmlnode_insert_child(parent, node);
+
+ return node;
+}
+
+void
+xmlnode_insert_child(xmlnode *parent, xmlnode *child)
+{
+ g_return_if_fail(parent != NULL);
+ g_return_if_fail(child != NULL);
+
+ child->parent = parent;
+
+ if(parent->lastchild) {
+ parent->lastchild->next = child;
+ } else {
+ parent->child = child;
+ }
+
+ parent->lastchild = child;
+}
+
+void
+xmlnode_insert_data(xmlnode *node, const char *data, gssize size)
+{
+ xmlnode *child;
+ gsize real_size;
+
+ g_return_if_fail(node != NULL);
+ g_return_if_fail(data != NULL);
+ g_return_if_fail(size != 0);
+
+ real_size = size == -1 ? strlen(data) : size;
+
+ child = new_node(NULL, XMLNODE_TYPE_DATA);
+
+ child->data = g_memdup(data, real_size);
+ child->data_sz = real_size;
+
+ xmlnode_insert_child(node, child);
+}
+
+void
+xmlnode_remove_attrib(xmlnode *node, const char *attr)
+{
+ xmlnode *attr_node, *sibling = NULL;
+
+ g_return_if_fail(node != NULL);
+ g_return_if_fail(attr != NULL);
+
+ for(attr_node = node->child; attr_node; attr_node = attr_node->next)
+ {
+ if(attr_node->type == XMLNODE_TYPE_ATTRIB &&
+ !strcmp(attr_node->name, attr))
+ {
+ if(node->child == attr_node) {
+ node->child = attr_node->next;
+ } else {
+ sibling->next = attr_node->next;
+ }
+ if (node->lastchild == attr_node) {
+ node->lastchild = sibling;
+ }
+ xmlnode_free(attr_node);
+ return;
+ }
+ sibling = attr_node;
+ }
+}
+
+
+void
+xmlnode_remove_attrib_with_namespace(xmlnode *node, const char *attr, const char *xmlns)
+{
+ xmlnode *attr_node, *sibling = NULL;
+
+ g_return_if_fail(node != NULL);
+ g_return_if_fail(attr != NULL);
+
+ for(attr_node = node->child; attr_node; attr_node = attr_node->next)
+ {
+ if(attr_node->type == XMLNODE_TYPE_ATTRIB &&
+ !strcmp(attr_node->name, attr) &&
+ !strcmp(attr_node->xmlns, xmlns))
+ {
+ if(node->child == attr_node) {
+ node->child = attr_node->next;
+ } else {
+ sibling->next = attr_node->next;
+ }
+ if (node->lastchild == attr_node) {
+ node->lastchild = sibling;
+ }
+ xmlnode_free(attr_node);
+ return;
+ }
+ sibling = attr_node;
+ }
+}
+
+void
+xmlnode_set_attrib(xmlnode *node, const char *attr, const char *value)
+{
+ xmlnode *attrib_node;
+
+ g_return_if_fail(node != NULL);
+ g_return_if_fail(attr != NULL);
+ g_return_if_fail(value != NULL);
+
+ xmlnode_remove_attrib(node, attr);
+
+ attrib_node = new_node(attr, XMLNODE_TYPE_ATTRIB);
+
+ attrib_node->data = g_strdup(value);
+
+ xmlnode_insert_child(node, attrib_node);
+}
+
+void
+xmlnode_set_attrib_with_namespace(xmlnode *node, const char *attr, const char *xmlns, const char *value)
+{
+ xmlnode *attrib_node;
+
+ g_return_if_fail(node != NULL);
+ g_return_if_fail(attr != NULL);
+ g_return_if_fail(value != NULL);
+
+ xmlnode_remove_attrib_with_namespace(node, attr, xmlns);
+
+ attrib_node = new_node(attr, XMLNODE_TYPE_ATTRIB);
+
+ attrib_node->data = g_strdup(value);
+ attrib_node->xmlns = g_strdup(xmlns);
+
+ xmlnode_insert_child(node, attrib_node);
+}
+
+const char *
+xmlnode_get_attrib(xmlnode *node, const char *attr)
+{
+ xmlnode *x;
+
+ g_return_val_if_fail(node != NULL, NULL);
+
+ for(x = node->child; x; x = x->next) {
+ if(x->type == XMLNODE_TYPE_ATTRIB && !strcmp(attr, x->name)) {
+ return x->data;
+ }
+ }
+
+ return NULL;
+}
+
+const char *
+xmlnode_get_attrib_with_namespace(xmlnode *node, const char *attr, const char *xmlns)
+{
+ xmlnode *x;
+
+ g_return_val_if_fail(node != NULL, NULL);
+
+ for(x = node->child; x; x = x->next) {
+ if(x->type == XMLNODE_TYPE_ATTRIB &&
+ !strcmp(attr, x->name) && !strcmp(x->xmlns, xmlns)) {
+ return x->data;
+ }
+ }
+
+ return NULL;
+}
+
+
+void xmlnode_set_namespace(xmlnode *node, const char *xmlns)
+{
+ g_return_if_fail(node != NULL);
+
+ g_free(node->xmlns);
+ node->xmlns = g_strdup(xmlns);
+}
+
+const char *xmlnode_get_namespace(xmlnode *node)
+{
+ g_return_val_if_fail(node != NULL, NULL);
+
+ return node->xmlns;
+}
+
+void
+xmlnode_free(xmlnode *node)
+{
+ xmlnode *x, *y;
+
+ g_return_if_fail(node != NULL);
+
+ x = node->child;
+ while(x) {
+ y = x->next;
+ xmlnode_free(x);
+ x = y;
+ }
+
+ g_free(node->name);
+ g_free(node->data);
+ g_free(node->xmlns);
+
+ GAIM_DBUS_UNREGISTER_POINTER(node);
+ g_free(node);
+}
+
+xmlnode*
+xmlnode_get_child(const xmlnode *parent, const char *name)
+{
+ return xmlnode_get_child_with_namespace(parent, name, NULL);
+}
+
+xmlnode *
+xmlnode_get_child_with_namespace(const xmlnode *parent, const char *name, const char *ns)
+{
+ xmlnode *x, *ret = NULL;
+ char **names;
+ char *parent_name, *child_name;
+
+ g_return_val_if_fail(parent != NULL, NULL);
+ g_return_val_if_fail(name != NULL, NULL);
+
+ names = g_strsplit(name, "/", 2);
+ parent_name = names[0];
+ child_name = names[1];
+
+ for(x = parent->child; x; x = x->next) {
+ const char *xmlns = NULL;
+ if(ns)
+ xmlns = xmlnode_get_namespace(x);
+
+ if(x->type == XMLNODE_TYPE_TAG && name && !strcmp(parent_name, x->name)
+ && (!ns || (xmlns && !strcmp(ns, xmlns)))) {
+ ret = x;
+ break;
+ }
+ }
+
+ if(child_name && ret)
+ ret = xmlnode_get_child(ret, child_name);
+
+ g_strfreev(names);
+ return ret;
+}
+
+char *
+xmlnode_get_data(xmlnode *node)
+{
+ GString *str = NULL;
+ xmlnode *c;
+
+ g_return_val_if_fail(node != NULL, NULL);
+
+ for(c = node->child; c; c = c->next) {
+ if(c->type == XMLNODE_TYPE_DATA) {
+ if(!str)
+ str = g_string_new("");
+ str = g_string_append_len(str, c->data, c->data_sz);
+ }
+ }
+
+ if (str == NULL)
+ return NULL;
+
+ return g_string_free(str, FALSE);
+}
+
+static char *
+xmlnode_to_str_helper(xmlnode *node, int *len, gboolean formatting, int depth)
+{
+ GString *text = g_string_new("");
+ xmlnode *c;
+ char *node_name, *esc, *esc2, *tab = NULL;
+ gboolean need_end = FALSE, pretty = formatting;
+
+ g_return_val_if_fail(node != NULL, NULL);
+
+ if(pretty && depth) {
+ tab = g_strnfill(depth, '\t');
+ text = g_string_append(text, tab);
+ }
+
+ node_name = g_markup_escape_text(node->name, -1);
+ g_string_append_printf(text, "<%s", node_name);
+
+ if (node->xmlns) {
+ if(!node->parent || !node->parent->xmlns || strcmp(node->xmlns, node->parent->xmlns))
+ {
+ char *xmlns = g_markup_escape_text(node->xmlns, -1);
+ g_string_append_printf(text, " xmlns='%s'", xmlns);
+ g_free(xmlns);
+ }
+ }
+ for(c = node->child; c; c = c->next)
+ {
+ if(c->type == XMLNODE_TYPE_ATTRIB) {
+ esc = g_markup_escape_text(c->name, -1);
+ esc2 = g_markup_escape_text(c->data, -1);
+ g_string_append_printf(text, " %s='%s'", esc, esc2);
+ g_free(esc);
+ g_free(esc2);
+ } else if(c->type == XMLNODE_TYPE_TAG || c->type == XMLNODE_TYPE_DATA) {
+ if(c->type == XMLNODE_TYPE_DATA)
+ pretty = FALSE;
+ need_end = TRUE;
+ }
+ }
+
+ if(need_end) {
+ g_string_append_printf(text, ">%s", pretty ? NEWLINE_S : "");
+
+ for(c = node->child; c; c = c->next)
+ {
+ if(c->type == XMLNODE_TYPE_TAG) {
+ int esc_len;
+ esc = xmlnode_to_str_helper(c, &esc_len, pretty, depth+1);
+ text = g_string_append_len(text, esc, esc_len);
+ g_free(esc);
+ } else if(c->type == XMLNODE_TYPE_DATA && c->data_sz > 0) {
+ esc = g_markup_escape_text(c->data, c->data_sz);
+ text = g_string_append(text, esc);
+ g_free(esc);
+ }
+ }
+
+ if(tab && pretty)
+ text = g_string_append(text, tab);
+ g_string_append_printf(text, "</%s>%s", node_name, formatting ? NEWLINE_S : "");
+ } else {
+ g_string_append_printf(text, "/>%s", formatting ? NEWLINE_S : "");
+ }
+
+ g_free(node_name);
+
+ g_free(tab);
+
+ if(len)
+ *len = text->len;
+
+ return g_string_free(text, FALSE);
+}
+
+char *
+xmlnode_to_str(xmlnode *node, int *len)
+{
+ return xmlnode_to_str_helper(node, len, FALSE, 0);
+}
+
+char *
+xmlnode_to_formatted_str(xmlnode *node, int *len)
+{
+ char *xml, *xml_with_declaration;
+
+ g_return_val_if_fail(node != NULL, NULL);
+
+ xml = xmlnode_to_str_helper(node, len, TRUE, 0);
+ xml_with_declaration =
+ g_strdup_printf("<?xml version='1.0' encoding='UTF-8' ?>" NEWLINE_S NEWLINE_S "%s", xml);
+ g_free(xml);
+
+ return xml_with_declaration;
+}
+
+struct _xmlnode_parser_data {
+ xmlnode *current;
+ gboolean error;
+};
+
+static void
+xmlnode_parser_element_start_libxml(void *user_data,
+ const xmlChar *element_name, const xmlChar *prefix, const xmlChar *xmlns,
+ int nb_namespaces, const xmlChar **namespaces,
+ int nb_attributes, int nb_defaulted, const xmlChar **attributes)
+{
+ struct _xmlnode_parser_data *xpd = user_data;
+ xmlnode *node;
+ int i;
+
+ if(!element_name || xpd->error) {
+ return;
+ } else {
+ if(xpd->current)
+ node = xmlnode_new_child(xpd->current, (const char*) element_name);
+ else
+ node = xmlnode_new((const char *) element_name);
+
+ xmlnode_set_namespace(node, (const char *) xmlns);
+
+ for(i=0; i < nb_attributes * 5; i+=5) {
+ char *txt;
+ int attrib_len = attributes[i+4] - attributes[i+3];
+ char *attrib = g_malloc(attrib_len + 1);
+ memcpy(attrib, attributes[i+3], attrib_len);
+ attrib[attrib_len] = '\0';
+ txt = attrib;
+ attrib = gaim_unescape_html(txt);
+ g_free(txt);
+ xmlnode_set_attrib(node, (const char*) attributes[i], attrib);
+ g_free(attrib);
+ }
+
+ xpd->current = node;
+ }
+}
+
+static void
+xmlnode_parser_element_end_libxml(void *user_data, const xmlChar *element_name,
+ const xmlChar *prefix, const xmlChar *xmlns)
+{
+ struct _xmlnode_parser_data *xpd = user_data;
+
+ if(!element_name || !xpd->current || xpd->error)
+ return;
+
+ if(xpd->current->parent) {
+ if(!xmlStrcmp((xmlChar*) xpd->current->name, element_name))
+ xpd->current = xpd->current->parent;
+ }
+}
+
+static void
+xmlnode_parser_element_text_libxml(void *user_data, const xmlChar *text, int text_len)
+{
+ struct _xmlnode_parser_data *xpd = user_data;
+
+ if(!xpd->current || xpd->error)
+ return;
+
+ if(!text || !text_len)
+ return;
+
+ xmlnode_insert_data(xpd->current, (const char*) text, text_len);
+}
+
+static void
+xmlnode_parser_error_libxml(void *user_data, const char *msg, ...)
+{
+ struct _xmlnode_parser_data *xpd = user_data;
+ xpd->error = TRUE;
+}
+
+static xmlSAXHandler xmlnode_parser_libxml = {
+ .internalSubset = NULL,
+ .isStandalone = NULL,
+ .hasInternalSubset = NULL,
+ .hasExternalSubset = NULL,
+ .resolveEntity = NULL,
+ .getEntity = NULL,
+ .entityDecl = NULL,
+ .notationDecl = NULL,
+ .attributeDecl = NULL,
+ .elementDecl = NULL,
+ .unparsedEntityDecl = NULL,
+ .setDocumentLocator = NULL,
+ .startDocument = NULL,
+ .endDocument = NULL,
+ .startElement = NULL,
+ .endElement = NULL,
+ .reference = NULL,
+ .characters = xmlnode_parser_element_text_libxml,
+ .ignorableWhitespace = NULL,
+ .processingInstruction = NULL,
+ .comment = NULL,
+ .warning = NULL,
+ .error = xmlnode_parser_error_libxml,
+ .fatalError = NULL,
+ .getParameterEntity = NULL,
+ .cdataBlock = NULL,
+ .externalSubset = NULL,
+ .initialized = XML_SAX2_MAGIC,
+ ._private = NULL,
+ .startElementNs = xmlnode_parser_element_start_libxml,
+ .endElementNs = xmlnode_parser_element_end_libxml,
+ .serror = NULL
+};
+
+xmlnode *
+xmlnode_from_str(const char *str, gssize size)
+{
+ struct _xmlnode_parser_data *xpd;
+ xmlnode *ret;
+ gsize real_size;
+
+ g_return_val_if_fail(str != NULL, NULL);
+
+ real_size = size < 0 ? strlen(str) : size;
+ xpd = g_new0(struct _xmlnode_parser_data, 1);
+
+ if (xmlSAXUserParseMemory(&xmlnode_parser_libxml, xpd, str, real_size) < 0) {
+ while(xpd->current && xpd->current->parent)
+ xpd->current = xpd->current->parent;
+ if(xpd->current)
+ xmlnode_free(xpd->current);
+ xpd->current = NULL;
+ }
+ ret = xpd->current;
+ if (xpd->error) {
+ ret = NULL;
+ if (xpd->current)
+ xmlnode_free(xpd->current);
+ }
+
+ g_free(xpd);
+ return ret;
+}
+
+xmlnode *
+xmlnode_copy(xmlnode *src)
+{
+ xmlnode *ret;
+ xmlnode *child;
+ xmlnode *sibling = NULL;
+
+ g_return_val_if_fail(src != NULL, NULL);
+
+ ret = new_node(src->name, src->type);
+ if(src->data) {
+ if(src->data_sz) {
+ ret->data = g_memdup(src->data, src->data_sz);
+ ret->data_sz = src->data_sz;
+ } else {
+ ret->data = g_strdup(src->data);
+ }
+ }
+
+ for(child = src->child; child; child = child->next) {
+ if(sibling) {
+ sibling->next = xmlnode_copy(child);
+ sibling = sibling->next;
+ } else {
+ ret->child = xmlnode_copy(child);
+ sibling = ret->child;
+ }
+ sibling->parent = ret;
+ }
+
+ ret->lastchild = sibling;
+
+ return ret;
+}
+
+xmlnode *
+xmlnode_get_next_twin(xmlnode *node)
+{
+ xmlnode *sibling;
+ const char *ns = xmlnode_get_namespace(node);
+
+ g_return_val_if_fail(node != NULL, NULL);
+ g_return_val_if_fail(node->type == XMLNODE_TYPE_TAG, NULL);
+
+ for(sibling = node->next; sibling; sibling = sibling->next) {
+ const char *xmlns = NULL;
+ if(ns)
+ xmlns = xmlnode_get_namespace(sibling);
+
+ if(sibling->type == XMLNODE_TYPE_TAG && !strcmp(node->name, sibling->name) &&
+ (!ns || (xmlns && !strcmp(ns, xmlns))))
+ return sibling;
+ }
+
+ return NULL;
+}