Fix FSF address (Tobias Mueller, #470445)
[platform/upstream/evolution-data-server.git] / libedataserver / e-xml-hash-utils.c
1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
2 /* 
3  * Copyright (C) 2001-2003 Ximian, Inc.
4  *
5  * This program is free software; you can redistribute it and/or
6  * modify it under the terms of version 2 of the GNU Lesser General Public
7  * License as published by the Free Software Foundation.
8  *
9  * This program is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12  * General Public License for more details.
13  *
14  * You should have received a copy of the GNU Lesser General Public
15  * License along with this program; if not, write to the
16  * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
17  * Boston, MA 02110-1301, USA.
18  */
19
20 #include "config.h"
21
22 #include <stdlib.h>
23 #include <string.h>
24 #include <unistd.h>
25
26 #include <libxml/entities.h>
27 #include <libxml/tree.h>
28 #include <libxml/xmlmemory.h>
29
30 #include <glib.h>
31 #include <glib/gstdio.h>
32
33 #include "e-xml-hash-utils.h"
34 #include "e-xml-utils.h"
35
36 /**
37  * e_xml_to_hash:
38  * @doc: The #xmlDoc to store in a hash table. 
39  * @type: The value type to use as a key in the hash table.
40  *
41  * Creates a #GHashTable representation of the #xmlDoc @doc.
42  * If @type is * @E_XML_HASH_TYPE_PROPERTY, all XML nodes will be
43  * indexed in the #GHashTable by name. If @type is
44  * %E_XML_HASH_TYPE_OBJECT_UID, then XML objects will be indexed in
45  * the hash by their UID (other nodes will still be indexed by name).
46  *
47  * Returns: The newly-created #GHashTable representation of @doc.
48  **/
49 GHashTable *
50 e_xml_to_hash (xmlDoc *doc, EXmlHashType type)
51 {
52         xmlNode *root, *node;
53         xmlChar *key, *value;
54         GHashTable *hash;
55
56         hash = g_hash_table_new (g_str_hash, g_str_equal);
57
58         root = xmlDocGetRootElement (doc);
59         for (node = root->xmlChildrenNode; node; node = node->next) {
60                 if (node->name == NULL || node->type != XML_ELEMENT_NODE)
61                         continue;
62
63                 if (type == E_XML_HASH_TYPE_OBJECT_UID &&
64                     !strcmp ((char*)node->name, "object"))
65                         key = xmlGetProp (node, (xmlChar*)"uid");
66                 else
67                         key = xmlStrdup (node->name);
68
69                 if (!key) {
70                         g_warning ("Key not found!!");
71                         continue;
72                 }
73
74                 value = xmlNodeListGetString (doc, node->xmlChildrenNode, 1);
75                 if (!value) {
76                         xmlFree (key);
77                         g_warning ("Found a key with no value!!");
78                         continue;
79                 }
80
81                 g_hash_table_insert (hash, g_strdup ((char*)key), g_strdup ((char*)value));
82                 xmlFree (key);
83                 xmlFree (value);
84         }
85
86         return hash;
87 }
88
89
90 struct save_data {
91         EXmlHashType type;
92         xmlDoc *doc;
93         xmlNode *root;
94 };
95
96 static void
97 foreach_save_func (gpointer key, gpointer value, gpointer user_data)
98 {
99         struct save_data *sd = user_data;
100         xmlNodePtr new_node;
101         xmlChar *enc;
102
103         if (sd->type == E_XML_HASH_TYPE_OBJECT_UID) {
104                 new_node = xmlNewNode (NULL, (xmlChar*)"object");
105                 xmlNewProp (new_node, (xmlChar*)"uid", (const xmlChar *) key);
106         } else
107                 new_node = xmlNewNode (NULL, (const xmlChar *) key);
108
109         enc = xmlEncodeEntitiesReentrant (sd->doc, value);
110         xmlNodeSetContent (new_node, enc);
111         xmlFree (enc);
112
113         xmlAddChild (sd->root, new_node);
114 }
115
116 /**
117  * e_xml_from_hash:
118  * @hash: The #GHashTable to extract the XML from.
119  * @type: The #EXmlHashType used to store the XML.
120  * @root_name: The name to call the new #xmlDoc.
121  *
122  * Uses the key/value pair representation of an XML structure in @hash
123  * to build an equivalent #xmlDoc. This is the reverse of
124  * e_xml_to_hash().
125  *
126  * Returns: The #xmlDoc created from the data in @hash.
127  **/
128 xmlDoc *
129 e_xml_from_hash (GHashTable *hash, EXmlHashType type, const char *root_name)
130 {
131         xmlDoc *doc;
132         struct save_data sd;
133
134         doc = xmlNewDoc ((xmlChar*)"1.0");
135         doc->encoding = xmlStrdup ((xmlChar*)"UTF-8");
136         sd.type = type;
137         sd.doc = doc;
138         sd.root = xmlNewDocNode (doc, NULL, (xmlChar*)root_name, NULL);
139         xmlDocSetRootElement (doc, sd.root);
140
141         g_hash_table_foreach (hash, foreach_save_func, &sd);
142         return doc;
143 }
144
145 static void
146 free_values (gpointer key, gpointer value, gpointer data)
147 {
148         g_free (key);
149         g_free (value);
150 }
151
152 /**
153  * e_xml_destroy_hash:
154  * @hash: The #GHashTable to destroy.
155  *
156  * Frees the memory used by @hash and its contents.
157  **/
158 void
159 e_xml_destroy_hash (GHashTable *hash)
160 {
161         g_hash_table_foreach (hash, free_values, NULL);
162         g_hash_table_destroy (hash);
163 }
164
165
166 /**
167  * EXmlHash:
168  *
169  * A hash table representation of an XML file.
170  **/
171 struct EXmlHash {
172         char *filename;
173         GHashTable *objects;
174 };
175
176 /**
177  * e_xmlhash_new:
178  * @filename: The name of an XML file.
179  *
180  * Creates a new #EXmlHash from the file @filename. If @filename does
181  * not already exist, an empty #EXmlHash will be created.
182  *
183  * Returns: The new #EXmlHash structure, or %NULL if unable to parse
184  *          @filename.
185  **/
186 EXmlHash *
187 e_xmlhash_new (const char *filename)
188 {
189         EXmlHash *hash;
190         xmlDoc *doc = NULL;
191
192         g_return_val_if_fail (filename != NULL, NULL);
193
194         hash = g_new0 (EXmlHash, 1);
195         hash->filename = g_strdup (filename);
196
197         if (g_file_test (filename, G_FILE_TEST_EXISTS)) {
198                 doc = e_xml_parse_file (filename);
199                 if (!doc) {
200                         e_xmlhash_destroy (hash);
201                         
202                         return NULL;
203                 }
204                 hash->objects = e_xml_to_hash (doc, E_XML_HASH_TYPE_OBJECT_UID);
205                 xmlFreeDoc (doc);
206         } else {
207                 hash->objects = g_hash_table_new (g_str_hash, g_str_equal);
208         }
209         
210         return hash;
211 }
212
213 /**
214  * e_xmlhash_add:
215  * @hash: The #EXmlHash to add an entry to.
216  * @key: The key to use for the entry.
217  * @data: The value of the new entry.
218  *
219  * Adds a new key/value pair to the #EXmlHash @hash.
220  **/
221 void
222 e_xmlhash_add (EXmlHash *hash, const char *key, const char *data)
223 {
224         g_return_if_fail (hash != NULL);
225         g_return_if_fail (key != NULL);
226         g_return_if_fail (data != NULL);
227
228         e_xmlhash_remove (hash, key);
229         g_hash_table_insert (hash->objects, g_strdup (key), g_strdup (data));
230 }
231
232 /**
233  * e_xmlhash_remove:
234  * @hash: The #EXmlHash to remove an entry from.
235  * @key: The key of the entry to remove.
236  *
237  * Remove the entry in @hash with key equal to @key, if it exists.
238  **/
239 void
240 e_xmlhash_remove (EXmlHash *hash, const char *key)
241 {
242         gpointer orig_key;
243         gpointer orig_value;
244
245         g_return_if_fail (hash != NULL);
246         g_return_if_fail (key != NULL);
247
248         if (g_hash_table_lookup_extended (hash->objects, key, &orig_key, &orig_value)) {
249                 g_hash_table_remove (hash->objects, key);
250                 g_free (orig_key);
251                 g_free (orig_value);
252         }
253 }
254
255 /**
256  * e_xmlhash_compare:
257  * @hash: The #EXmlHash to compare against.
258  * @key: The key of the hash entry to compare with.
259  * @compare_data: The data to compare against the hash entry.
260  *
261  * Compares the value with key equal to @key in @hash against
262  * @compare_data.
263  *
264  * Returns: E_XMLHASH_STATUS_SAME if the value and @compare_data are
265  *          equal,E_XMLHASH_STATUS_DIFFERENT if they are different, or
266  *          E_XMLHASH_STATUS_NOT_FOUND if there is no entry in @hash with 
267  *          its key equal to @key.
268  **/
269 EXmlHashStatus
270 e_xmlhash_compare (EXmlHash *hash, const char *key, const char *compare_data)
271 {
272         char *data;
273         int rc;
274
275         g_return_val_if_fail (hash != NULL, E_XMLHASH_STATUS_NOT_FOUND);
276         g_return_val_if_fail (key != NULL, E_XMLHASH_STATUS_NOT_FOUND);
277         g_return_val_if_fail (compare_data != NULL, E_XMLHASH_STATUS_NOT_FOUND);
278
279         data = g_hash_table_lookup (hash->objects, key);
280         if (!data)
281                 return E_XMLHASH_STATUS_NOT_FOUND;
282
283         rc = strcmp (data, compare_data);
284         if (rc == 0)
285                 return E_XMLHASH_STATUS_SAME;
286
287         return E_XMLHASH_STATUS_DIFFERENT;
288 }
289
290 typedef struct {
291         EXmlHashFunc func;
292         gpointer user_data;
293 } foreach_data_t;
294
295 static void
296 foreach_hash_func (gpointer  key, gpointer value, gpointer user_data)
297 {
298         foreach_data_t *data = (foreach_data_t *) user_data;
299
300         data->func ((const char *) key, (const char *) value, data->user_data);
301 }
302
303 /**
304  * e_xmlhash_foreach_key:
305  * @hash: An #EXmlHash.
306  * @func: The #EXmlHashFunc to execute on the data in @hash.
307  * @user_data: The data to pass to @func.
308  *
309  * Executes @func against each key/value pair in @hash.
310  **/
311 void
312 e_xmlhash_foreach_key (EXmlHash *hash, EXmlHashFunc func, gpointer user_data)
313 {
314         foreach_data_t data;
315
316         g_return_if_fail (hash != NULL);
317         g_return_if_fail (func != NULL);
318
319         data.func = func;
320         data.user_data = user_data;
321         g_hash_table_foreach (hash->objects, foreach_hash_func, &data);
322 }
323
324 void
325 e_xmlhash_foreach_key_remove (EXmlHash *hash, EXmlHashRemoveFunc func, gpointer user_data)
326 {
327         g_return_if_fail (hash != NULL);
328         g_return_if_fail (func != NULL);
329
330         g_hash_table_foreach_remove (hash->objects, (GHRFunc)func, user_data);
331 }
332
333 /**
334  * e_xmlhash_write:
335  * @hash: The #EXmlHash to write.
336  *
337  * Writes the XML represented by @hash to the file originally passed
338  * to e_xmlhash_new().
339  **/
340 void
341 e_xmlhash_write (EXmlHash *hash)
342 {
343         xmlDoc *doc;
344
345         g_return_if_fail (hash != NULL);
346
347         doc = e_xml_from_hash (hash->objects, E_XML_HASH_TYPE_OBJECT_UID, "xmlhash");
348
349         e_xml_save_file (hash->filename, doc);
350
351         xmlFreeDoc (doc);
352 }
353
354 /**
355  * e_xmlhash_destroy:
356  * @hash: The #EXmlHash to destroy.
357  *
358  * Frees the memory associated with @hash.
359  **/
360 void
361 e_xmlhash_destroy (EXmlHash *hash)
362 {
363         g_return_if_fail (hash != NULL);
364
365         g_free (hash->filename);
366         if (hash->objects)
367                 e_xml_destroy_hash (hash->objects);
368
369         g_free (hash);
370 }