avoid duplicates in the path lists
[mikachu/openbox.git] / parser / parse.c
1 /* -*- indent-tabs-mode: nil; tab-width: 4; c-basic-offset: 4; -*-
2
3    parse.c for the Openbox window manager
4    Copyright (c) 2003        Ben Jansens
5
6    This program is free software; you can redistribute it and/or modify
7    it under the terms of the GNU General Public License as published by
8    the Free Software Foundation; either version 2 of the License, or
9    (at your option) any later version.
10
11    This program is distributed in the hope that it will be useful,
12    but WITHOUT ANY WARRANTY; without even the implied warranty of
13    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14    GNU General Public License for more details.
15
16    See the COPYING file for a copy of the GNU General Public License.
17 */
18
19 #include "parse.h"
20 #include <glib.h>
21 #include <string.h>
22 #include <sys/stat.h>
23 #include <sys/types.h>
24
25 static gboolean xdg_start;
26 static gchar   *xdg_config_home_path;
27 static gchar   *xdg_data_home_path;
28 static GSList  *xdg_config_dir_paths;
29 static GSList  *xdg_data_dir_paths;
30
31 struct Callback {
32     gchar *tag;
33     ParseCallback func;
34     void *data;
35 };
36
37 struct _ObParseInst {
38     GHashTable *callbacks;
39 };
40
41 static void destfunc(struct Callback *c)
42 {
43     g_free(c->tag);
44     g_free(c);
45 }
46
47 ObParseInst* parse_startup()
48 {
49     ObParseInst *i = g_new(ObParseInst, 1);
50     i->callbacks = g_hash_table_new_full(g_str_hash, g_str_equal, NULL,
51                                          (GDestroyNotify)destfunc);
52     return i;
53 }
54
55 void parse_shutdown(ObParseInst *i)
56 {
57     if (i) {
58         g_hash_table_destroy(i->callbacks);
59         g_free(i);
60     }
61 }
62
63 void parse_register(ObParseInst *i, const gchar *tag,
64                     ParseCallback func, void *data)
65 {
66     struct Callback *c;
67
68     if ((c = g_hash_table_lookup(i->callbacks, tag))) {
69         g_warning("tag '%s' already registered", tag);
70         return;
71     }
72
73     c = g_new(struct Callback, 1);
74     c->tag = g_strdup(tag);
75     c->func = func;
76     c->data = data;
77     g_hash_table_insert(i->callbacks, c->tag, c);
78 }
79
80 gboolean parse_load_rc(xmlDocPtr *doc, xmlNodePtr *root)
81 {
82     GSList *it;
83     gchar *path;
84     gboolean r = FALSE;
85
86     for (it = xdg_config_dir_paths; !r && it; it = g_slist_next(it)) {
87         path = g_build_filename(it->data, "openbox", "rc.xml", NULL);
88         r = parse_load(path, "openbox_config", doc, root);
89         g_free(path);
90     }
91     if (!r)
92         g_warning("unable to find a valid config file, using defaults");
93     return r;
94 }
95
96 gboolean parse_load_menu(const gchar *file, xmlDocPtr *doc, xmlNodePtr *root)
97 {
98     GSList *it;
99     gchar *path;
100     gboolean r = FALSE;
101
102     if (file[0] == '/') {
103         r = parse_load(file, "openbox_menu", doc, root);
104     } else {
105         for (it = xdg_config_dir_paths; !r && it; it = g_slist_next(it)) {
106             path = g_build_filename(it->data, "openbox", file, NULL);
107             r = parse_load(path, "openbox_menu", doc, root);
108             g_free(path);
109         }
110     }
111     if (!r)
112         g_warning("unable to find a valid menu file '%s'", file);
113     return r;
114 }
115
116 gboolean parse_load(const gchar *path, const gchar *rootname,
117                     xmlDocPtr *doc, xmlNodePtr *root)
118 {
119     if ((*doc = xmlParseFile(path))) {
120         *root = xmlDocGetRootElement(*doc);
121         if (!*root) {
122             xmlFreeDoc(*doc);
123             *doc = NULL;
124             g_warning("%s is an empty document", path);
125         } else {
126             if (xmlStrcasecmp((*root)->name, (const xmlChar*)rootname)) {
127                 xmlFreeDoc(*doc);
128                 *doc = NULL;
129                 g_warning("document %s is of wrong type. root node is "
130                           "not '%s'", path, rootname);
131             }
132         }
133     }
134     if (!*doc)
135         return FALSE;
136     return TRUE;
137 }
138
139 gboolean parse_load_mem(gpointer data, guint len, const gchar *rootname,
140                         xmlDocPtr *doc, xmlNodePtr *root)
141 {
142     if ((*doc = xmlParseMemory(data, len))) {
143         *root = xmlDocGetRootElement(*doc);
144         if (!*root) {
145             xmlFreeDoc(*doc);
146             *doc = NULL;
147             g_warning("Given memory is an empty document");
148         } else {
149             if (xmlStrcasecmp((*root)->name, (const xmlChar*)rootname)) {
150                 xmlFreeDoc(*doc);
151                 *doc = NULL;
152                 g_warning("document in given memory is of wrong type. root "
153                           "node is not '%s'", rootname);
154             }
155         }
156     }
157     if (!*doc)
158         return FALSE;
159     return TRUE;
160 }
161
162 void parse_close(xmlDocPtr doc)
163 {
164     xmlFreeDoc(doc);
165 }
166
167 void parse_tree(ObParseInst *i, xmlDocPtr doc, xmlNodePtr node)
168 {
169     while (node) {
170         struct Callback *c = g_hash_table_lookup(i->callbacks, node->name);
171
172         if (c)
173             c->func(i, doc, node, c->data);
174
175         node = node->next;
176     }
177 }
178
179 gchar *parse_string(xmlDocPtr doc, xmlNodePtr node)
180 {
181     xmlChar *c = xmlNodeListGetString(doc, node->children, TRUE);
182     gchar *s = g_strdup(c ? (gchar*)c : "");
183     xmlFree(c);
184     return s;
185 }
186
187 gint parse_int(xmlDocPtr doc, xmlNodePtr node)
188 {
189     xmlChar *c = xmlNodeListGetString(doc, node->children, TRUE);
190     gint i = atoi((gchar*)c);
191     xmlFree(c);
192     return i;
193 }
194
195 gboolean parse_bool(xmlDocPtr doc, xmlNodePtr node)
196 {
197     xmlChar *c = xmlNodeListGetString(doc, node->children, TRUE);
198     gboolean b = FALSE;
199     if (!xmlStrcasecmp(c, (const xmlChar*) "true"))
200         b = TRUE;
201     else if (!xmlStrcasecmp(c, (const xmlChar*) "yes"))
202         b = TRUE;
203     else if (!xmlStrcasecmp(c, (const xmlChar*) "on"))
204         b = TRUE;
205     xmlFree(c);
206     return b;
207 }
208
209 gboolean parse_contains(const gchar *val, xmlDocPtr doc, xmlNodePtr node)
210 {
211     xmlChar *c = xmlNodeListGetString(doc, node->children, TRUE);
212     gboolean r;
213     r = !xmlStrcasecmp(c, (const xmlChar*) val);
214     xmlFree(c);
215     return r;
216 }
217
218 xmlNodePtr parse_find_node(const gchar *tag, xmlNodePtr node)
219 {
220     while (node) {
221         if (!xmlStrcasecmp(node->name, (const xmlChar*) tag))
222             return node;
223         node = node->next;
224     }
225     return NULL;
226 }
227
228 gboolean parse_attr_int(const gchar *name, xmlNodePtr node, gint *value)
229 {
230     xmlChar *c = xmlGetProp(node, (const xmlChar*) name);
231     gboolean r = FALSE;
232     if (c) {
233         *value = atoi((gchar*)c);
234         r = TRUE;
235     }
236     xmlFree(c);
237     return r;
238 }
239
240 gboolean parse_attr_string(const gchar *name, xmlNodePtr node, gchar **value)
241 {
242     xmlChar *c = xmlGetProp(node, (const xmlChar*) name);
243     gboolean r = FALSE;
244     if (c) {
245         *value = g_strdup((gchar*)c);
246         r = TRUE;
247     }
248     xmlFree(c);
249     return r;
250 }
251
252 gboolean parse_attr_contains(const gchar *val, xmlNodePtr node,
253                              const gchar *name)
254 {
255     xmlChar *c = xmlGetProp(node, (const xmlChar*) name);
256     gboolean r;
257     r = !xmlStrcasecmp(c, (const xmlChar*) val);
258     xmlFree(c);
259     return r;
260 }
261
262 static gint slist_path_cmp(const gchar *a, const gchar *b)
263 {
264     return strcmp(a, b);
265 }
266
267 typedef GSList* (*GSListFunc) (gpointer list, gconstpointer data);
268
269 static GSList* slist_path_add(GSList *list, gpointer data, GSListFunc func)
270 {
271     g_assert(func);
272
273     if (!data)
274         return list;
275
276     if (!g_slist_find_custom(list, data, (GCompareFunc) slist_path_cmp))
277         list = func(list, data);
278
279     return list;
280 }
281
282 static GSList* split_paths(const gchar *paths)
283 {
284     GSList *list = NULL;
285     gchar **spl, **it;
286
287     if (!paths)
288         return NULL;
289     spl = g_strsplit(paths, ":", -1);
290     for (it = spl; *it; ++it)
291         list = slist_path_add(list, *it, (GSListFunc) g_slist_append);
292     g_free(spl);
293     return list;
294 }
295
296 void parse_paths_startup()
297 {
298     gchar *path;
299
300     if (xdg_start)
301         return;
302     xdg_start = TRUE;
303
304     path = getenv("XDG_CONFIG_HOME");
305     if (path && path[0] != '\0') /* not unset or empty */
306         xdg_config_home_path = g_build_filename(path, NULL);
307     else
308         xdg_config_home_path = g_build_filename(g_get_home_dir(), ".config",
309                                                 NULL);
310
311     path = getenv("XDG_DATA_HOME");
312     if (path && path[0] != '\0') /* not unset or empty */
313         xdg_data_home_path = g_build_filename(path, NULL);
314     else
315         xdg_data_home_path = g_build_filename(g_get_home_dir(), ".local",
316                                               "share", NULL);
317
318     path = getenv("XDG_CONFIG_DIRS");
319     if (path && path[0] != '\0') /* not unset or empty */
320         xdg_config_dir_paths = split_paths(path);
321     else {
322         xdg_config_dir_paths = slist_path_add(xdg_config_dir_paths,
323                                               g_build_filename
324                                               (G_DIR_SEPARATOR_S,
325                                                "etc", "xdg", NULL),
326                                               (GSListFunc) g_slist_append);
327         xdg_config_dir_paths = slist_path_add(xdg_config_dir_paths,
328                                               g_strdup(CONFIGDIR),
329                                               (GSListFunc) g_slist_append);
330     }
331     xdg_config_dir_paths = slist_path_add(xdg_config_dir_paths,
332                                           xdg_config_home_path,
333                                           (GSListFunc) g_slist_prepend);
334     
335     path = getenv("XDG_DATA_DIRS");
336     if (path && path[0] != '\0') /* not unset or empty */
337         xdg_data_dir_paths = split_paths(path);
338     else {
339         xdg_data_dir_paths = slist_path_add(xdg_data_dir_paths,
340                                             g_build_filename
341                                             (G_DIR_SEPARATOR_S,
342                                              "usr", "local", "share", NULL),
343                                             (GSListFunc) g_slist_append);
344         xdg_data_dir_paths = slist_path_add(xdg_data_dir_paths,
345                                             g_build_filename
346                                             (G_DIR_SEPARATOR_S,
347                                              "usr", "share", NULL),
348                                             (GSListFunc) g_slist_append);
349         xdg_data_dir_paths = slist_path_add(xdg_data_dir_paths,
350                                             g_strdup(DATADIR),
351                                             (GSListFunc) g_slist_append);
352     }
353     xdg_data_dir_paths = slist_path_add(xdg_data_dir_paths,
354                                         xdg_data_home_path,
355                                         (GSListFunc) g_slist_prepend);
356 }
357
358 void parse_paths_shutdown()
359 {
360     GSList *it;
361
362     if (!xdg_start)
363         return;
364     xdg_start = FALSE;
365
366     for (it = xdg_config_dir_paths; it; it = g_slist_next(it))
367         g_free(it->data);
368     g_slist_free(xdg_config_dir_paths);
369     xdg_config_dir_paths = NULL;
370     for (it = xdg_data_dir_paths; it; it = g_slist_next(it))
371         g_free(it->data);
372     g_slist_free(xdg_data_dir_paths);
373     xdg_data_dir_paths = NULL;
374 }
375
376 gchar *parse_expand_tilde(const gchar *f)
377 {
378     gchar **spl;
379     gchar *ret;
380
381     if (!f)
382         return NULL;
383     spl = g_strsplit(f, "~", 0);
384     ret = g_strjoinv(g_get_home_dir(), spl);
385     g_strfreev(spl);
386     return ret;
387 }
388
389 void parse_mkdir_path(const gchar *path, gint mode)
390 {
391     gchar *c, *e;
392
393     g_assert(path[0] == '/');
394
395     c = g_strdup(path);
396     e = c;
397     while ((e = strchr(e + 1, '/'))) {
398         *e = '\0';
399         mkdir(c, mode);
400         *e = '/';
401     }
402     mkdir(c, mode);
403     g_free(c);
404 }
405
406 const gchar* parse_xdg_config_home_path()
407 {
408     return xdg_config_home_path;
409 }
410
411 const gchar* parse_xdg_data_home_path()
412 {
413     return xdg_data_home_path;
414 }
415
416 GSList* parse_xdg_config_dir_paths()
417 {
418     return xdg_config_dir_paths;
419 }
420
421 GSList* parse_xdg_data_dir_paths()
422 {
423     return xdg_data_dir_paths;
424 }