pidgin/pidgin

1966704b3e42
merge of '77693555855fe9cd3215414f79964dba346cc5fa'
and '19a87e98e5857ad0289f2c760d460f7f1dbbb42d'
/**
* @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;
}