1 /* -*- indent-tabs-mode: nil; tab-width: 4; c-basic-offset: 4; -*-
3 obt/ddparse.c for the Openbox window manager
4 Copyright (c) 2009 Dana Jansens
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.
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.
16 See the COPYING file for a copy of the GNU General Public License.
19 #include "obt/ddparse.h"
27 typedef struct _ObtDDParse ObtDDParse;
29 /* Parses the value and adds it to the group's key_hash, with the given
31 typedef void (*ObtDDParseValueFunc)(gchar *key, const gchar *val,
32 ObtDDParse *parse, gboolean *error);
38 ObtDDParseGroup *group;
39 /* the key is a group name, the value is a ObtDDParseGroup */
40 GHashTable *group_hash;
43 struct _ObtDDParseGroup {
46 ObtDDParseValueFunc value_func;
47 /* the key is a string (a key inside the group in the .desktop).
48 the value is an ObtDDParseValue */
52 /* Displays a warning message including the file name and line number, and
53 sets the boolean @error to true if it points to a non-NULL address.
55 static void parse_error(const gchar *m, const ObtDDParse *const parse,
59 g_warning("%s at line %lu of input", m, parse->lineno);
61 g_warning("%s at line %lu of file %s",
62 m, parse->lineno, parse->filename);
63 if (error) *error = TRUE;
66 static void parse_value_free(ObtDDParseValue *v)
69 case OBT_DDPARSE_STRING:
70 case OBT_DDPARSE_LOCALESTRING:
71 g_free(v->value.string); break;
72 case OBT_DDPARSE_STRINGS:
73 case OBT_DDPARSE_LOCALESTRINGS:
74 g_free(v->value.strings.s);
75 v->value.strings.n = 0;
77 case OBT_DDPARSE_BOOLEAN:
79 case OBT_DDPARSE_NUMERIC:
82 g_assert_not_reached();
84 g_slice_free(ObtDDParseValue, v);
87 static ObtDDParseGroup* parse_group_new(gchar *name, ObtDDParseValueFunc f)
89 ObtDDParseGroup *g = g_slice_new(ObtDDParseGroup);
93 g->key_hash = g_hash_table_new_full(g_str_hash, g_str_equal,
95 (GDestroyNotify)parse_value_free);
99 static void parse_group_free(ObtDDParseGroup *g)
102 g_hash_table_destroy(g->key_hash);
103 g_slice_free(ObtDDParseGroup, g);
106 /*! Reads an input string, strips out invalid stuff, and parses
108 If @nstrings is not NULL, then it splits the output string at ';'
109 characters. They are all returned in the same string with null zeros
110 between them, @nstrings is set to the number of such strings.
112 static gchar* parse_value_string(const gchar *in,
115 const ObtDDParse *const parse,
118 const gint bytes = strlen(in);
121 const gchar *end, *i;
123 g_return_val_if_fail(in != NULL, NULL);
127 for (i = in; i < end; ++i) {
128 if ((guchar)*i >= 127 || (guchar)*i < 32) {
129 /* non-control character ascii */
131 parse_error("Invalid bytes in string", parse, error);
136 else if (!g_utf8_validate(in, bytes, &end))
137 parse_error("Invalid bytes in localestring", parse, error);
139 if (nstrings) *nstrings = 1;
141 out = g_new(char, bytes + 1);
145 const gchar *next = locale ? g_utf8_find_next_char(i, end) : i+1;
148 case 's': *o++ = ' '; break;
149 case 'n': *o++ = '\n'; break;
150 case 't': *o++ = '\t'; break;
151 case 'r': *o++ = '\r'; break;
152 case ';': *o++ = ';'; break;
153 case '\\': *o++ = '\\'; break;
155 parse_error((locale ?
156 "Invalid escape sequence in localestring" :
157 "Invalid escape sequence in string"),
164 else if (*i == ';' && nstrings) {
168 else if ((guchar)*i == 127 || (guchar)*i < 32) {
169 /* avoid ascii control characters */
170 parse_error("Found control character in string", parse, error);
174 memcpy(o, i, next-i);
183 static gboolean parse_value_boolean(const gchar *in,
184 const ObtDDParse *const parse,
187 if (strcmp(in, "true") == 0)
189 else if (strcmp(in, "false") != 0)
190 parse_error("Invalid boolean value", parse, error);
194 static gfloat parse_value_numeric(const gchar *in,
195 const ObtDDParse *const parse,
199 if (sscanf(in, "%f", &out) == 0)
200 parse_error("Invalid numeric value", parse, error);
204 static gboolean parse_file_line(FILE *f, gchar **buf,
205 gulong *size, gulong *read,
206 ObtDDParse *parse, gboolean *error)
208 const gulong BUFMUL = 80;
213 g_assert(*read == 0);
215 *buf = g_new(char, *size);
218 /* remove everything up to a null zero already in the buffer and shift
219 the rest to the front */
221 for (i = 0; i < *read; ++i) {
223 (*buf)[i-null-1] = (*buf)[i];
224 else if ((*buf)[i] == '\0')
230 /* is there already a newline in the buffer? */
231 for (i = 0; i < *read; ++i)
232 if ((*buf)[i] == '\n') {
233 /* turn it into a null zero and done */
238 /* we need to read some more to find a newline */
243 newread = *buf + *read;
244 ret = fread(newread, sizeof(char), *size-*read, f);
245 if (ret < *size - *read && !feof(f)) {
246 parse_error("Error reading", parse, error);
251 /* strip out null zeros in the input and look for an endofline */
254 for (i = newread-*buf; i < *read; ++i) {
256 (*buf)[i] = (*buf)[i+null];
257 if ((*buf)[i] == '\0') {
262 else if ((*buf)[i] == '\n' && eol == *size) {
264 /* turn it into a null zero */
270 /* found an endofline, done */
272 else if (feof(f) && *read < *size) {
273 /* found the endoffile, done (if there is space) */
275 /* stick a null zero on if there is test on the last line */
276 (*buf)[(*read)++] = '\0';
283 *buf = g_renew(char, *buf, *size);
289 static void parse_group(const gchar *buf, gulong len,
290 ObtDDParse *parse, gboolean *error)
296 /* get the group name */
297 group = g_strndup(buf+1, len-2);
298 for (i = 0; i < len-2; ++i)
299 if ((guchar)group[i] < 32 || (guchar)group[i] >= 127) {
300 /* valid ASCII only */
301 parse_error("Invalid character found", parse, NULL);
302 group[i] = '\0'; /* stopping before this character */
306 /* make sure it's a new group */
307 g = g_hash_table_lookup(parse->group_hash, group);
309 parse_error("Duplicate group found", parse, error);
313 /* if it's the first group, make sure it's named Desktop Entry */
314 else if (!parse->group && strcmp(group, "Desktop Entry") != 0)
316 parse_error("Incorrect group found, "
317 "expected [Desktop Entry]",
324 g = parse_group_new(group, NULL);
325 g_hash_table_insert(parse->group_hash, g->name, g);
332 g_print("Found group %s\n", g->name);
336 static void parse_key_value(const gchar *buf, gulong len,
337 ObtDDParse *parse, gboolean *error)
339 gulong i, keyend, valstart, eq;
342 /* find the end of the key */
343 for (i = 0; i < len; ++i)
344 if (!(((guchar)buf[i] >= 'A' && (guchar)buf[i] <= 'Z') ||
345 ((guchar)buf[i] >= 'a' && (guchar)buf[i] <= 'z') ||
346 ((guchar)buf[i] >= '0' && (guchar)buf[i] <= '9') ||
347 ((guchar)buf[i] == '-'))) {
348 /* not part of the key */
353 parse_error("Empty key", parse, error);
356 /* find the = character */
357 for (i = keyend; i < len; ++i) {
362 else if (buf[i] != ' ') {
363 parse_error("Invalid character in key name", parse, error);
368 parse_error("Key without value found", parse, error);
371 /* find the start of the value */
372 for (i = eq+1; i < len; ++i)
378 parse_error("Empty value found", parse, error);
382 key = g_strndup(buf, keyend);
383 if (g_hash_table_lookup(parse->group->key_hash, key)) {
384 parse_error("Duplicate key found", parse, error);
388 g_print("Found key/value %s=%s.\n", key, buf+valstart);
389 if (parse->group->value_func)
390 parse->group->value_func(key, buf+valstart, parse, error);
393 static gboolean parse_file(FILE *f, ObtDDParse *parse)
396 gulong bytes = 0, read = 0;
397 gboolean error = FALSE;
399 while (!error && parse_file_line(f, &buf, &bytes, &read, parse, &error)) {
400 gulong len = strlen(buf);
401 if (buf[0] == '#' || buf[0] == '\0')
402 ; /* ignore comment lines */
403 else if (buf[0] == '[' && buf[len-1] == ']')
404 parse_group(buf, len, parse, &error);
405 else if (!parse->group)
406 /* just ignore keys outside of groups */
407 parse_error("Key found before group", parse, NULL);
409 /* ignore errors in key-value pairs and continue */
410 parse_key_value(buf, len, parse, NULL);
414 if (buf) g_free(buf);
418 static void parse_desktop_entry_value(gchar *key, const gchar *val,
419 ObtDDParse *parse, gboolean *error)
421 ObtDDParseValue v, *pv;
423 /* figure out value type */
424 v.type = OBT_DDPARSE_NUM_VALUE_TYPES;
425 /* XXX do this part */
427 /* parse the value */
429 case OBT_DDPARSE_STRING:
430 v.value.string = parse_value_string(val, FALSE, NULL, parse, error);
431 g_assert(v.value.string);
433 case OBT_DDPARSE_LOCALESTRING:
434 v.value.string = parse_value_string(val, TRUE, NULL, parse, error);
435 g_assert(v.value.string);
437 case OBT_DDPARSE_STRINGS:
438 v.value.strings.s = parse_value_string(val, FALSE, &v.value.strings.n,
440 g_assert(v.value.strings.s);
441 g_assert(v.value.strings.n);
443 case OBT_DDPARSE_LOCALESTRINGS:
444 v.value.strings.s = parse_value_string(val, TRUE, &v.value.strings.n,
446 g_assert(v.value.strings.s);
447 g_assert(v.value.strings.n);
449 case OBT_DDPARSE_BOOLEAN:
450 v.value.boolean = parse_value_boolean(val, parse, error);
452 case OBT_DDPARSE_NUMERIC:
453 v.value.numeric = parse_value_numeric(val, parse, error);
456 g_assert_not_reached();
459 pv = g_slice_new(ObtDDParseValue);
461 g_hash_table_insert(parse->group->key_hash, key, pv);
464 GHashTable* obt_ddparse_file(const gchar *name, GSList *paths)
467 ObtDDParseGroup *desktop_entry;
472 parse.filename = NULL;
475 parse.group_hash = g_hash_table_new_full(g_str_hash,
478 (GDestroyNotify)parse_group_free);
480 /* set up the groups (there's only one right now) */
481 desktop_entry = parse_group_new(g_strdup("Desktop Entry"),
482 parse_desktop_entry_value);
483 g_hash_table_insert(parse.group_hash, desktop_entry->name, desktop_entry);
486 for (it = paths; it && !success; it = g_slist_next(it)) {
487 gchar *path = g_strdup_printf("%s/%s", (char*)it->data, name);
488 if ((f = fopen(path, "r"))) {
489 parse.filename = path;
491 success = parse_file(f, &parse);
497 g_hash_table_destroy(parse.group_hash);
501 return parse.group_hash;
504 GHashTable* obt_ddparse_group_keys(ObtDDParseGroup *g)