/* * librest - RESTful web services access * Copyright (c) 2008, 2009, 2011, Intel Corporation. * * Authors: Rob Bradford * Ross Burton * Tomas Frydrych * * This program is free software; you can redistribute it and/or modify it * under the terms and conditions of the GNU Lesser General Public License, * version 2.1, as published by the Free Software Foundation. * * This program is distributed in the hope it will be useful, but WITHOUT ANY * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for * more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA. * */ #include "rest-xml-node.h" #define G(x) (gchar *)x static RestXmlNode * rest_xml_node_reverse_siblings (RestXmlNode *current) { RestXmlNode *next; RestXmlNode *prev = NULL; while (current) { next = current->next; current->next = prev; prev = current; current = next; } return prev; } void _rest_xml_node_reverse_children_siblings (RestXmlNode *node) { GList *l, *children; RestXmlNode *new_node; children = g_hash_table_get_values (node->children); for (l = children; l; l = l->next) { new_node = rest_xml_node_reverse_siblings ((RestXmlNode *)l->data); g_hash_table_insert (node->children, new_node->name, new_node); } if (children) g_list_free (children); } /* * _rest_xml_node_prepend: * @cur_node: a sibling #RestXmlNode to prepend the new before * @new_node: new #RestXmlNode to prepend to the siblings list * * Prepends new_node to the list of siblings starting at cur_node. * * Return value: (transfer none): returns new start of the sibling list */ RestXmlNode * _rest_xml_node_prepend (RestXmlNode *cur_node, RestXmlNode *new_node) { g_assert (new_node->next == NULL); new_node->next = cur_node; return new_node; } /* * rest_xml_node_append_end: * @cur_node: a member of the sibling #RestXmlNode list * @new_node: new #RestXmlNode to append to the siblings list * * Appends new_node to end of the list of siblings containing cur_node. */ static void rest_xml_node_append_end (RestXmlNode *cur_node, RestXmlNode *new_node) { RestXmlNode *tmp = cur_node; g_return_if_fail (cur_node); while (tmp->next) tmp = tmp->next; tmp->next = new_node; } GType rest_xml_node_get_type (void) { static GType type = 0; if (G_UNLIKELY (type == 0)) { type = g_boxed_type_register_static ("RestXmlNode", (GBoxedCopyFunc)rest_xml_node_ref, (GBoxedFreeFunc)rest_xml_node_unref); } return type; } /* * _rest_xml_node_new: * * Creates a new instance of #RestXmlNode. * * Return value: (transfer full): newly allocated #RestXmlNode. */ RestXmlNode * _rest_xml_node_new () { RestXmlNode *node; node = g_slice_new0 (RestXmlNode); node->ref_count = 1; node->children = g_hash_table_new (NULL, NULL); node->attrs = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free); return node; } /** * rest_xml_node_ref: (skip) * @node: a #RestXmlNode * * Increases the reference count of @node. * * Returns: the same @node. */ RestXmlNode * rest_xml_node_ref (RestXmlNode *node) { g_return_val_if_fail (node, NULL); g_return_val_if_fail (node->ref_count > 0, NULL); g_atomic_int_inc (&node->ref_count); return node; } /** * rest_xml_node_unref: (skip) * @node: a #RestXmlNode * * Decreases the reference count of @node. When its reference count drops to 0, * the node is finalized (i.e. its memory is freed). */ void rest_xml_node_unref (RestXmlNode *node) { GList *l; RestXmlNode *next = NULL; g_return_if_fail (node); g_return_if_fail (node->ref_count > 0); /* Try and unref the chain, this is equivalent to being tail recursively * unreffing the next pointer */ while (node && g_atomic_int_dec_and_test (&node->ref_count)) { /* * Save this pointer now since we are going to free the structure it * contains soon. */ next = node->next; l = g_hash_table_get_values (node->children); while (l) { rest_xml_node_unref ((RestXmlNode *)l->data); l = g_list_delete_link (l, l); } g_hash_table_unref (node->children); g_hash_table_unref (node->attrs); g_free (node->content); g_slice_free (RestXmlNode, node); /* * Free the next in the chain by updating node. If we're at the end or * there are no siblings then the next = NULL definition deals with this * case */ node = next; } } /** * rest_xml_node_get_attr: * @node: a #RestXmlNode * @attr_name: the name of an attribute * * Get the value of the attribute named @attr_name, or %NULL if it doesn't * exist. * * Returns: the attribute value. This string is owned by #RestXmlNode and should * not be freed. */ const gchar * rest_xml_node_get_attr (RestXmlNode *node, const gchar *attr_name) { g_return_val_if_fail (attr_name != NULL, NULL); return g_hash_table_lookup (node->attrs, attr_name); } /** * rest_xml_node_find: * @start: a #RestXmlNode * @tag: the name of a node * * Searches for the first child node of @start named @tag. * * Returns: the first child node, or %NULL if it doesn't exist. */ RestXmlNode * rest_xml_node_find (RestXmlNode *start, const gchar *tag) { RestXmlNode *node; RestXmlNode *tmp; GQueue stack = G_QUEUE_INIT; GList *children, *l; const char *tag_interned; g_return_val_if_fail (start, NULL); g_return_val_if_fail (tag != NULL, NULL); g_return_val_if_fail (start->ref_count > 0, NULL); tag_interned = g_intern_string (tag); g_queue_push_head (&stack, start); while ((node = g_queue_pop_head (&stack)) != NULL) { if ((tmp = g_hash_table_lookup (node->children, tag_interned)) != NULL) { g_queue_clear (&stack); return tmp; } children = g_hash_table_get_values (node->children); for (l = children; l; l = l->next) { g_queue_push_head (&stack, l->data); } g_list_free (children); } g_queue_clear (&stack); return NULL; } /** * rest_xml_node_print: * @node: #RestXmlNode * * Recursively outputs given node and it's children. * * Return value: (transfer full): xml string representing the node. */ char * rest_xml_node_print (RestXmlNode *node) { GHashTableIter iter; gpointer key, value; GList *attrs = NULL; GList *children = NULL; GList *l; GString *xml = g_string_new (NULL); RestXmlNode *n; g_string_append (xml, "<"); g_string_append (xml, node->name); g_hash_table_iter_init (&iter, node->attrs); while (g_hash_table_iter_next (&iter, &key, &value)) { char *attr = g_strdup_printf ("%s=\'%s\'", (char *)key, (char *)value); attrs = g_list_prepend (attrs, attr); } attrs = g_list_sort (attrs, (GCompareFunc) g_strcmp0); for (l = attrs; l; l = l->next) { const char *attr = (const char *) l->data; g_string_append_printf (xml, " %s", attr); } g_string_append (xml, ">"); g_hash_table_iter_init (&iter, node->children); while (g_hash_table_iter_next (&iter, &key, &value)) children = g_list_prepend (children, key); children = g_list_sort (children, (GCompareFunc) g_strcmp0); for (l = children; l; l = l->next) { const char *name = (const char *) l->data; RestXmlNode *value = (RestXmlNode *) g_hash_table_lookup (node->children, name); char *child = rest_xml_node_print ((RestXmlNode *) value); g_string_append (xml, child); g_free (child); } if (node->content) g_string_append (xml, node->content); g_string_append_printf (xml, "", node->name); for (n = node->next; n; n = n->next) { char *sibling = rest_xml_node_print (n); g_string_append (xml, sibling); g_free (sibling); } g_list_free_full (attrs, g_free); g_list_free (children); return g_string_free (xml, FALSE); } /** * rest_xml_node_add_child: * @parent: parent #RestXmlNode, or %NULL for the top-level node * @tag: name of the child node * * Adds a new node to the given parent node; to create the top-level node, * parent should be %NULL. * * Return value: (transfer none): the newly added #RestXmlNode; the node object * is owned by, and valid for the life time of, the #RestXmlCreator. */ RestXmlNode * rest_xml_node_add_child (RestXmlNode *parent, const char *tag) { RestXmlNode *node; char *escaped; g_return_val_if_fail (tag && *tag, NULL); escaped = g_markup_escape_text (tag, -1); node = _rest_xml_node_new (); node->name = (char *) g_intern_string (escaped); if (parent) { RestXmlNode *tmp_node; tmp_node = g_hash_table_lookup (parent->children, node->name); if (tmp_node) { rest_xml_node_append_end (tmp_node, node); } else { g_hash_table_insert (parent->children, G(node->name), node); } } g_free (escaped); return node; } /** * rest_xml_node_add_attr: * @node: #RestXmlNode to add attribute to * @attribute: name of the attribute * @value: value to set attribute to * * Adds attribute to the given node. */ void rest_xml_node_add_attr (RestXmlNode *node, const char *attribute, const char *value) { g_return_if_fail (node); g_return_if_fail (attribute); g_return_if_fail (*attribute); g_return_if_fail (value); g_return_if_fail (*value); g_hash_table_insert (node->attrs, g_markup_escape_text (attribute, -1), g_markup_escape_text (value, -1)); } /** * rest_xml_node_set_content: * @node: #RestXmlNode to set content * @value: the content * * Sets content for the given node. */ void rest_xml_node_set_content (RestXmlNode *node, const char *value) { g_return_if_fail (node); g_return_if_fail (value); g_return_if_fail (*value); g_free (node->content); node->content = g_markup_escape_text (value, -1); }