applied changes from fb4435d514398a0b1febebe8bf46339e2c2b52b6
[pidgin-git.git] / libpurple / xmlnode.c
blob74ec874f41ad2ea1006b3ad4c4f96b1375b30600
1 /**
2 * @file xmlnode.c XML DOM functions
3 */
5 /* purple
7 * Purple is the legal property of its developers, whose names are too numerous
8 * to list here. Please refer to the COPYRIGHT file distributed with this
9 * source distribution.
11 * This program is free software; you can redistribute it and/or modify
12 * it under the terms of the GNU General Public License as published by
13 * the Free Software Foundation; either version 2 of the License, or
14 * (at your option) any later version.
16 * This program is distributed in the hope that it will be useful,
17 * but WITHOUT ANY WARRANTY; without even the implied warranty of
18 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19 * GNU General Public License for more details.
21 * You should have received a copy of the GNU General Public License
22 * along with this program; if not, write to the Free Software
23 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA
26 /* A lot of this code at least resembles the code in libxode, but since
27 * libxode uses memory pools that we simply have no need for, I decided to
28 * write my own stuff. Also, re-writing this lets me be as lightweight
29 * as I want to be. Thank you libxode for giving me a good starting point */
30 #define _PURPLE_XMLNODE_C_
32 #include "internal.h"
33 #include "debug.h"
35 #include <libxml/parser.h>
36 #include <string.h>
37 #include <glib.h>
39 #include "dbus-maybe.h"
40 #include "util.h"
41 #include "xmlnode.h"
43 #ifdef _WIN32
44 # define NEWLINE_S "\r\n"
45 #else
46 # define NEWLINE_S "\n"
47 #endif
49 static xmlnode*
50 new_node(const char *name, XMLNodeType type)
52 xmlnode *node = g_new0(xmlnode, 1);
54 node->name = g_strdup(name);
55 node->type = type;
57 PURPLE_DBUS_REGISTER_POINTER(node, xmlnode);
59 return node;
62 xmlnode*
63 xmlnode_new(const char *name)
65 g_return_val_if_fail(name != NULL, NULL);
67 return new_node(name, XMLNODE_TYPE_TAG);
70 xmlnode *
71 xmlnode_new_child(xmlnode *parent, const char *name)
73 xmlnode *node;
75 g_return_val_if_fail(parent != NULL, NULL);
76 g_return_val_if_fail(name != NULL, NULL);
78 node = new_node(name, XMLNODE_TYPE_TAG);
80 xmlnode_insert_child(parent, node);
82 return node;
85 void
86 xmlnode_insert_child(xmlnode *parent, xmlnode *child)
88 g_return_if_fail(parent != NULL);
89 g_return_if_fail(child != NULL);
91 child->parent = parent;
93 if(parent->lastchild) {
94 parent->lastchild->next = child;
95 } else {
96 parent->child = child;
99 parent->lastchild = child;
102 void
103 xmlnode_insert_data(xmlnode *node, const char *data, gssize size)
105 xmlnode *child;
106 gsize real_size;
108 g_return_if_fail(node != NULL);
109 g_return_if_fail(data != NULL);
110 g_return_if_fail(size != 0);
112 real_size = size == -1 ? strlen(data) : size;
114 child = new_node(NULL, XMLNODE_TYPE_DATA);
116 child->data = g_memdup(data, real_size);
117 child->data_sz = real_size;
119 xmlnode_insert_child(node, child);
122 void
123 xmlnode_remove_attrib(xmlnode *node, const char *attr)
125 xmlnode *attr_node, *sibling = NULL;
127 g_return_if_fail(node != NULL);
128 g_return_if_fail(attr != NULL);
130 attr_node = node->child;
131 while (attr_node) {
132 if(attr_node->type == XMLNODE_TYPE_ATTRIB &&
133 purple_strequal(attr_node->name, attr))
135 if (node->lastchild == attr_node) {
136 node->lastchild = sibling;
138 if (sibling == NULL) {
139 node->child = attr_node->next;
140 xmlnode_free(attr_node);
141 attr_node = node->child;
142 } else {
143 sibling->next = attr_node->next;
144 sibling = attr_node->next;
145 xmlnode_free(attr_node);
146 attr_node = sibling;
149 else
151 attr_node = attr_node->next;
153 sibling = attr_node;
157 void
158 xmlnode_remove_attrib_with_namespace(xmlnode *node, const char *attr, const char *xmlns)
160 xmlnode *attr_node, *sibling = NULL;
162 g_return_if_fail(node != NULL);
163 g_return_if_fail(attr != NULL);
165 for(attr_node = node->child; attr_node; attr_node = attr_node->next)
167 if(attr_node->type == XMLNODE_TYPE_ATTRIB &&
168 purple_strequal(attr, attr_node->name) &&
169 purple_strequal(xmlns, attr_node->xmlns))
171 if(sibling == NULL) {
172 node->child = attr_node->next;
173 } else {
174 sibling->next = attr_node->next;
176 if (node->lastchild == attr_node) {
177 node->lastchild = sibling;
179 xmlnode_free(attr_node);
180 return;
182 sibling = attr_node;
186 void
187 xmlnode_set_attrib(xmlnode *node, const char *attr, const char *value)
189 xmlnode_remove_attrib(node, attr);
190 xmlnode_set_attrib_full(node, attr, NULL, NULL, value);
193 void
194 xmlnode_set_attrib_with_namespace(xmlnode *node, const char *attr, const char *xmlns, const char *value)
196 xmlnode_set_attrib_full(node, attr, xmlns, NULL, value);
199 void
200 xmlnode_set_attrib_with_prefix(xmlnode *node, const char *attr, const char *prefix, const char *value)
202 xmlnode_set_attrib_full(node, attr, NULL, prefix, value);
205 void
206 xmlnode_set_attrib_full(xmlnode *node, const char *attr, const char *xmlns, const char *prefix, const char *value)
208 xmlnode *attrib_node;
210 g_return_if_fail(node != NULL);
211 g_return_if_fail(attr != NULL);
212 g_return_if_fail(value != NULL);
214 xmlnode_remove_attrib_with_namespace(node, attr, xmlns);
215 attrib_node = new_node(attr, XMLNODE_TYPE_ATTRIB);
217 attrib_node->data = g_strdup(value);
218 attrib_node->xmlns = g_strdup(xmlns);
219 attrib_node->prefix = g_strdup(prefix);
221 xmlnode_insert_child(node, attrib_node);
225 const char *
226 xmlnode_get_attrib(const xmlnode *node, const char *attr)
228 xmlnode *x;
230 g_return_val_if_fail(node != NULL, NULL);
231 g_return_val_if_fail(attr != NULL, NULL);
233 for(x = node->child; x; x = x->next) {
234 if(x->type == XMLNODE_TYPE_ATTRIB && purple_strequal(attr, x->name)) {
235 return x->data;
239 return NULL;
242 const char *
243 xmlnode_get_attrib_with_namespace(const xmlnode *node, const char *attr, const char *xmlns)
245 const xmlnode *x;
247 g_return_val_if_fail(node != NULL, NULL);
248 g_return_val_if_fail(attr != NULL, NULL);
250 for(x = node->child; x; x = x->next) {
251 if(x->type == XMLNODE_TYPE_ATTRIB &&
252 purple_strequal(attr, x->name) &&
253 purple_strequal(xmlns, x->xmlns)) {
254 return x->data;
258 return NULL;
262 void xmlnode_set_namespace(xmlnode *node, const char *xmlns)
264 g_return_if_fail(node != NULL);
266 g_free(node->xmlns);
267 node->xmlns = g_strdup(xmlns);
270 const char *xmlnode_get_namespace(xmlnode *node)
272 g_return_val_if_fail(node != NULL, NULL);
274 return node->xmlns;
277 void xmlnode_set_prefix(xmlnode *node, const char *prefix)
279 g_return_if_fail(node != NULL);
281 g_free(node->prefix);
282 node->prefix = g_strdup(prefix);
285 const char *xmlnode_get_prefix(const xmlnode *node)
287 g_return_val_if_fail(node != NULL, NULL);
288 return node->prefix;
291 xmlnode *xmlnode_get_parent(const xmlnode *child)
293 g_return_val_if_fail(child != NULL, NULL);
294 return child->parent;
297 void
298 xmlnode_free(xmlnode *node)
300 xmlnode *x, *y;
302 g_return_if_fail(node != NULL);
304 /* if we're part of a tree, remove ourselves from the tree first */
305 if(NULL != node->parent) {
306 if(node->parent->child == node) {
307 node->parent->child = node->next;
308 if (node->parent->lastchild == node)
309 node->parent->lastchild = node->next;
310 } else {
311 xmlnode *prev = node->parent->child;
312 while(prev && prev->next != node) {
313 prev = prev->next;
315 if(prev) {
316 prev->next = node->next;
317 if (node->parent->lastchild == node)
318 node->parent->lastchild = prev;
323 /* now free our children */
324 x = node->child;
325 while(x) {
326 y = x->next;
327 xmlnode_free(x);
328 x = y;
331 /* now dispose of ourselves */
332 g_free(node->name);
333 g_free(node->data);
334 g_free(node->xmlns);
335 g_free(node->prefix);
337 if(node->namespace_map)
338 g_hash_table_destroy(node->namespace_map);
340 PURPLE_DBUS_UNREGISTER_POINTER(node);
341 g_free(node);
344 xmlnode*
345 xmlnode_get_child(const xmlnode *parent, const char *name)
347 return xmlnode_get_child_with_namespace(parent, name, NULL);
350 xmlnode *
351 xmlnode_get_child_with_namespace(const xmlnode *parent, const char *name, const char *ns)
353 xmlnode *x, *ret = NULL;
354 char **names;
355 char *parent_name, *child_name;
357 g_return_val_if_fail(parent != NULL, NULL);
358 g_return_val_if_fail(name != NULL, NULL);
360 names = g_strsplit(name, "/", 2);
361 parent_name = names[0];
362 child_name = names[1];
364 for(x = parent->child; x; x = x->next) {
365 /* XXX: Is it correct to ignore the namespace for the match if none was specified? */
366 const char *xmlns = NULL;
367 if(ns)
368 xmlns = xmlnode_get_namespace(x);
370 if(x->type == XMLNODE_TYPE_TAG && purple_strequal(parent_name, x->name)
371 && purple_strequal(ns, xmlns)) {
372 ret = x;
373 break;
377 if(child_name && ret)
378 ret = xmlnode_get_child(ret, child_name);
380 g_strfreev(names);
381 return ret;
384 char *
385 xmlnode_get_data(const xmlnode *node)
387 GString *str = NULL;
388 xmlnode *c;
390 g_return_val_if_fail(node != NULL, NULL);
392 for(c = node->child; c; c = c->next) {
393 if(c->type == XMLNODE_TYPE_DATA) {
394 if(!str)
395 str = g_string_new_len(c->data, c->data_sz);
396 else
397 str = g_string_append_len(str, c->data, c->data_sz);
401 if (str == NULL)
402 return NULL;
404 return g_string_free(str, FALSE);
407 char *
408 xmlnode_get_data_unescaped(const xmlnode *node)
410 char *escaped = xmlnode_get_data(node);
412 char *unescaped = escaped ? purple_unescape_html(escaped) : NULL;
414 g_free(escaped);
416 return unescaped;
419 static void
420 xmlnode_to_str_foreach_append_ns(const char *key, const char *value,
421 GString *buf)
423 if (*key) {
424 g_string_append_printf(buf, " xmlns:%s='%s'", key, value);
425 } else {
426 g_string_append_printf(buf, " xmlns='%s'", value);
430 static char *
431 xmlnode_to_str_helper(const xmlnode *node, int *len, gboolean formatting, int depth)
433 GString *text = g_string_new("");
434 const char *prefix;
435 const xmlnode *c;
436 char *node_name, *esc, *esc2, *tab = NULL;
437 gboolean need_end = FALSE, pretty = formatting;
439 g_return_val_if_fail(node != NULL, NULL);
441 if(pretty && depth) {
442 tab = g_strnfill(depth, '\t');
443 text = g_string_append(text, tab);
446 node_name = g_markup_escape_text(node->name, -1);
447 prefix = xmlnode_get_prefix(node);
449 if (prefix) {
450 g_string_append_printf(text, "<%s:%s", prefix, node_name);
451 } else {
452 g_string_append_printf(text, "<%s", node_name);
455 if (node->namespace_map) {
456 g_hash_table_foreach(node->namespace_map,
457 (GHFunc)xmlnode_to_str_foreach_append_ns, text);
458 } else if (node->xmlns) {
459 if(!node->parent || !purple_strequal(node->xmlns, node->parent->xmlns))
461 char *xmlns = g_markup_escape_text(node->xmlns, -1);
462 g_string_append_printf(text, " xmlns='%s'", xmlns);
463 g_free(xmlns);
466 for(c = node->child; c; c = c->next)
468 if(c->type == XMLNODE_TYPE_ATTRIB) {
469 const char *aprefix = xmlnode_get_prefix(c);
470 esc = g_markup_escape_text(c->name, -1);
471 esc2 = g_markup_escape_text(c->data, -1);
472 if (aprefix) {
473 g_string_append_printf(text, " %s:%s='%s'", aprefix, esc, esc2);
474 } else {
475 g_string_append_printf(text, " %s='%s'", esc, esc2);
477 g_free(esc);
478 g_free(esc2);
479 } else if(c->type == XMLNODE_TYPE_TAG || c->type == XMLNODE_TYPE_DATA) {
480 if(c->type == XMLNODE_TYPE_DATA)
481 pretty = FALSE;
482 need_end = TRUE;
486 if(need_end) {
487 g_string_append_printf(text, ">%s", pretty ? NEWLINE_S : "");
489 for(c = node->child; c; c = c->next)
491 if(c->type == XMLNODE_TYPE_TAG) {
492 int esc_len;
493 esc = xmlnode_to_str_helper(c, &esc_len, pretty, depth+1);
494 text = g_string_append_len(text, esc, esc_len);
495 g_free(esc);
496 } else if(c->type == XMLNODE_TYPE_DATA && c->data_sz > 0) {
497 esc = g_markup_escape_text(c->data, c->data_sz);
498 text = g_string_append(text, esc);
499 g_free(esc);
503 if(tab && pretty)
504 text = g_string_append(text, tab);
505 if (prefix) {
506 g_string_append_printf(text, "</%s:%s>%s", prefix, node_name, formatting ? NEWLINE_S : "");
507 } else {
508 g_string_append_printf(text, "</%s>%s", node_name, formatting ? NEWLINE_S : "");
510 } else {
511 g_string_append_printf(text, "/>%s", formatting ? NEWLINE_S : "");
514 g_free(node_name);
516 g_free(tab);
518 if(len)
519 *len = text->len;
521 return g_string_free(text, FALSE);
524 char *
525 xmlnode_to_str(const xmlnode *node, int *len)
527 return xmlnode_to_str_helper(node, len, FALSE, 0);
530 char *
531 xmlnode_to_formatted_str(const xmlnode *node, int *len)
533 char *xml, *xml_with_declaration;
535 g_return_val_if_fail(node != NULL, NULL);
537 xml = xmlnode_to_str_helper(node, len, TRUE, 0);
538 xml_with_declaration =
539 g_strdup_printf("<?xml version='1.0' encoding='UTF-8' ?>" NEWLINE_S NEWLINE_S "%s", xml);
540 g_free(xml);
542 if (len)
543 *len += sizeof("<?xml version='1.0' encoding='UTF-8' ?>" NEWLINE_S NEWLINE_S) - 1;
545 return xml_with_declaration;
548 struct _xmlnode_parser_data {
549 xmlnode *current;
550 gboolean error;
553 static void
554 xmlnode_parser_element_start_libxml(void *user_data,
555 const xmlChar *element_name, const xmlChar *prefix, const xmlChar *xmlns,
556 int nb_namespaces, const xmlChar **namespaces,
557 int nb_attributes, int nb_defaulted, const xmlChar **attributes)
559 struct _xmlnode_parser_data *xpd = user_data;
560 xmlnode *node;
561 int i, j;
563 if(!element_name || xpd->error) {
564 return;
565 } else {
566 if(xpd->current)
567 node = xmlnode_new_child(xpd->current, (const char*) element_name);
568 else
569 node = xmlnode_new((const char *) element_name);
571 xmlnode_set_namespace(node, (const char *) xmlns);
572 xmlnode_set_prefix(node, (const char *)prefix);
574 if (nb_namespaces != 0) {
575 node->namespace_map = g_hash_table_new_full(
576 g_str_hash, g_str_equal, g_free, g_free);
578 for (i = 0, j = 0; i < nb_namespaces; i++, j += 2) {
579 const char *key = (const char *)namespaces[j];
580 const char *val = (const char *)namespaces[j + 1];
581 g_hash_table_insert(node->namespace_map,
582 g_strdup(key ? key : ""), g_strdup(val ? val : ""));
586 for(i=0; i < nb_attributes * 5; i+=5) {
587 const char *name = (const char *)attributes[i];
588 const char *prefix = (const char *)attributes[i+1];
589 char *txt;
590 int attrib_len = attributes[i+4] - attributes[i+3];
591 char *attrib = g_strndup((const char *)attributes[i+3], attrib_len);
592 txt = attrib;
593 attrib = purple_unescape_text(txt);
594 g_free(txt);
595 xmlnode_set_attrib_full(node, name, NULL, prefix, attrib);
596 g_free(attrib);
599 xpd->current = node;
603 static void
604 xmlnode_parser_element_end_libxml(void *user_data, const xmlChar *element_name,
605 const xmlChar *prefix, const xmlChar *xmlns)
607 struct _xmlnode_parser_data *xpd = user_data;
609 if(!element_name || !xpd->current || xpd->error)
610 return;
612 if(xpd->current->parent) {
613 if(!xmlStrcmp((xmlChar*) xpd->current->name, element_name))
614 xpd->current = xpd->current->parent;
618 static void
619 xmlnode_parser_element_text_libxml(void *user_data, const xmlChar *text, int text_len)
621 struct _xmlnode_parser_data *xpd = user_data;
623 if(!xpd->current || xpd->error)
624 return;
626 if(!text || !text_len)
627 return;
629 xmlnode_insert_data(xpd->current, (const char*) text, text_len);
632 static void
633 xmlnode_parser_error_libxml(void *user_data, const char *msg, ...)
635 struct _xmlnode_parser_data *xpd = user_data;
636 char errmsg[2048];
637 va_list args;
639 xpd->error = TRUE;
641 va_start(args, msg);
642 vsnprintf(errmsg, sizeof(errmsg), msg, args);
643 va_end(args);
645 purple_debug_error("xmlnode", "Error parsing xml file: %s", errmsg);
648 static void
649 xmlnode_parser_structural_error_libxml(void *user_data, xmlErrorPtr error)
651 struct _xmlnode_parser_data *xpd = user_data;
653 if (error && (error->level == XML_ERR_ERROR ||
654 error->level == XML_ERR_FATAL)) {
655 xpd->error = TRUE;
656 purple_debug_error("xmlnode", "XML parser error for xmlnode %p: "
657 "Domain %i, code %i, level %i: %s",
658 user_data, error->domain, error->code, error->level,
659 error->message ? error->message : "(null)\n");
660 } else if (error)
661 purple_debug_warning("xmlnode", "XML parser error for xmlnode %p: "
662 "Domain %i, code %i, level %i: %s",
663 user_data, error->domain, error->code, error->level,
664 error->message ? error->message : "(null)\n");
665 else
666 purple_debug_warning("xmlnode", "XML parser error for xmlnode %p\n",
667 user_data);
670 static xmlSAXHandler xmlnode_parser_libxml = {
671 NULL, /* internalSubset */
672 NULL, /* isStandalone */
673 NULL, /* hasInternalSubset */
674 NULL, /* hasExternalSubset */
675 NULL, /* resolveEntity */
676 NULL, /* getEntity */
677 NULL, /* entityDecl */
678 NULL, /* notationDecl */
679 NULL, /* attributeDecl */
680 NULL, /* elementDecl */
681 NULL, /* unparsedEntityDecl */
682 NULL, /* setDocumentLocator */
683 NULL, /* startDocument */
684 NULL, /* endDocument */
685 NULL, /* startElement */
686 NULL, /* endElement */
687 NULL, /* reference */
688 xmlnode_parser_element_text_libxml, /* characters */
689 NULL, /* ignorableWhitespace */
690 NULL, /* processingInstruction */
691 NULL, /* comment */
692 NULL, /* warning */
693 xmlnode_parser_error_libxml, /* error */
694 NULL, /* fatalError */
695 NULL, /* getParameterEntity */
696 NULL, /* cdataBlock */
697 NULL, /* externalSubset */
698 XML_SAX2_MAGIC, /* initialized */
699 NULL, /* _private */
700 xmlnode_parser_element_start_libxml, /* startElementNs */
701 xmlnode_parser_element_end_libxml, /* endElementNs */
702 xmlnode_parser_structural_error_libxml, /* serror */
705 xmlnode *
706 xmlnode_from_str(const char *str, gssize size)
708 struct _xmlnode_parser_data *xpd;
709 xmlnode *ret;
710 gsize real_size;
712 g_return_val_if_fail(str != NULL, NULL);
714 real_size = size < 0 ? strlen(str) : size;
715 xpd = g_new0(struct _xmlnode_parser_data, 1);
717 if (xmlSAXUserParseMemory(&xmlnode_parser_libxml, xpd, str, real_size) < 0) {
718 while(xpd->current && xpd->current->parent)
719 xpd->current = xpd->current->parent;
720 if(xpd->current)
721 xmlnode_free(xpd->current);
722 xpd->current = NULL;
724 ret = xpd->current;
725 if (xpd->error) {
726 ret = NULL;
727 if (xpd->current)
728 xmlnode_free(xpd->current);
731 g_free(xpd);
732 return ret;
735 xmlnode *
736 xmlnode_from_file(const char *dir,const char *filename, const char *description, const char *process)
738 gchar *filename_full;
739 GError *error = NULL;
740 gchar *contents = NULL;
741 gsize length;
742 xmlnode *node = NULL;
744 g_return_val_if_fail(dir != NULL, NULL);
746 purple_debug_info(process, "Reading file %s from directory %s\n",
747 filename, dir);
749 filename_full = g_build_filename(dir, filename, NULL);
751 if (!g_file_test(filename_full, G_FILE_TEST_EXISTS))
753 purple_debug_info(process, "File %s does not exist (this is not "
754 "necessarily an error)\n", filename_full);
755 g_free(filename_full);
756 return NULL;
759 if (!g_file_get_contents(filename_full, &contents, &length, &error))
761 purple_debug_error(process, "Error reading file %s: %s\n",
762 filename_full, error->message);
763 g_error_free(error);
766 if ((contents != NULL) && (length > 0))
768 node = xmlnode_from_str(contents, length);
770 /* If we were unable to parse the file then save its contents to a backup file */
771 if (node == NULL)
773 gchar *filename_temp, *filename_temp_full;
775 filename_temp = g_strdup_printf("%s~", filename);
776 filename_temp_full = g_build_filename(dir, filename_temp, NULL);
778 purple_debug_error("util", "Error parsing file %s. Renaming old "
779 "file to %s\n", filename_full, filename_temp);
780 purple_util_write_data_to_file_absolute(filename_temp_full, contents, length);
782 g_free(filename_temp_full);
783 g_free(filename_temp);
786 g_free(contents);
789 /* If we could not parse the file then show the user an error message */
790 if (node == NULL)
792 gchar *title, *msg;
793 title = g_strdup_printf(_("Error Reading %s"), filename);
794 msg = g_strdup_printf(_("An error was encountered reading your "
795 "%s. The file has not been loaded, and the old file "
796 "has been renamed to %s~."), description, filename_full);
797 purple_notify_error(NULL, NULL, title, msg);
798 g_free(title);
799 g_free(msg);
802 g_free(filename_full);
804 return node;
807 static void
808 xmlnode_copy_foreach_ns(gpointer key, gpointer value, gpointer user_data)
810 GHashTable *ret = (GHashTable *)user_data;
811 g_hash_table_insert(ret, g_strdup(key), g_strdup(value));
814 xmlnode *
815 xmlnode_copy(const xmlnode *src)
817 xmlnode *ret;
818 xmlnode *child;
819 xmlnode *sibling = NULL;
821 g_return_val_if_fail(src != NULL, NULL);
823 ret = new_node(src->name, src->type);
824 ret->xmlns = g_strdup(src->xmlns);
825 if (src->data) {
826 if (src->data_sz) {
827 ret->data = g_memdup(src->data, src->data_sz);
828 ret->data_sz = src->data_sz;
829 } else {
830 ret->data = g_strdup(src->data);
833 ret->prefix = g_strdup(src->prefix);
834 if (src->namespace_map) {
835 ret->namespace_map = g_hash_table_new_full(g_str_hash, g_str_equal,
836 g_free, g_free);
837 g_hash_table_foreach(src->namespace_map, xmlnode_copy_foreach_ns, ret->namespace_map);
840 for (child = src->child; child; child = child->next) {
841 if (sibling) {
842 sibling->next = xmlnode_copy(child);
843 sibling = sibling->next;
844 } else {
845 ret->child = xmlnode_copy(child);
846 sibling = ret->child;
848 sibling->parent = ret;
851 ret->lastchild = sibling;
853 return ret;
856 xmlnode *
857 xmlnode_get_next_twin(xmlnode *node)
859 xmlnode *sibling;
860 const char *ns = xmlnode_get_namespace(node);
862 g_return_val_if_fail(node != NULL, NULL);
863 g_return_val_if_fail(node->type == XMLNODE_TYPE_TAG, NULL);
865 for(sibling = node->next; sibling; sibling = sibling->next) {
866 /* XXX: Is it correct to ignore the namespace for the match if none was specified? */
867 const char *xmlns = NULL;
868 if(ns)
869 xmlns = xmlnode_get_namespace(sibling);
871 if(sibling->type == XMLNODE_TYPE_TAG && purple_strequal(node->name, sibling->name) &&
872 purple_strequal(ns, xmlns))
873 return sibling;
876 return NULL;