]> Dogcows Code - chaz/openbox/blob - obt/ddparse.c
8da048d3551f243749e8e3dfd35b88fe74898164
[chaz/openbox] / obt / ddparse.c
1 /* -*- indent-tabs-mode: nil; tab-width: 4; c-basic-offset: 4; -*-
2
3 obt/ddparse.c for the Openbox window manager
4 Copyright (c) 2009 Dana 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 "obt/ddparse.h"
20 #ifdef HAVE_STRING_H
21 #include <string.h>
22 #endif
23 #ifdef HAVE_STDIO_H
24 #include <stdio.h>
25 #endif
26
27 typedef struct _ObtDDParse ObtDDParse;
28
29 /* Parses the value and adds it to the group's key_hash, with the given
30 key */
31 typedef void (*ObtDDParseValueFunc)(gchar *key, const gchar *val,
32 ObtDDParse *parse, gboolean *error);
33
34
35 struct _ObtDDParse {
36 gchar *filename;
37 gulong lineno;
38 ObtDDParseGroup *group;
39 /* the key is a group name, the value is a ObtDDParseGroup */
40 GHashTable *group_hash;
41 };
42
43 struct _ObtDDParseGroup {
44 gchar *name;
45 gboolean seen;
46 ObtDDParseValueFunc value_func;
47 /* the key is a string (a key inside the group in the .desktop).
48 the value is an ObtDDParseValue */
49 GHashTable *key_hash;
50 };
51
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.
54 */
55 static void parse_error(const gchar *m, const ObtDDParse *const parse,
56 gboolean *error)
57 {
58 if (!parse->filename)
59 g_warning("%s at line %lu of input", m, parse->lineno);
60 else
61 g_warning("%s at line %lu of file %s",
62 m, parse->lineno, parse->filename);
63 if (error) *error = TRUE;
64 }
65
66 static void parse_value_free(ObtDDParseValue *v)
67 {
68 switch (v->type) {
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;
76 break;
77 case OBT_DDPARSE_BOOLEAN:
78 break;
79 case OBT_DDPARSE_NUMERIC:
80 break;
81 default:
82 g_assert_not_reached();
83 }
84 g_slice_free(ObtDDParseValue, v);
85 }
86
87 static ObtDDParseGroup* parse_group_new(gchar *name, ObtDDParseValueFunc f)
88 {
89 ObtDDParseGroup *g = g_slice_new(ObtDDParseGroup);
90 g->name = name;
91 g->value_func = f;
92 g->seen = FALSE;
93 g->key_hash = g_hash_table_new_full(g_str_hash, g_str_equal,
94 g_free,
95 (GDestroyNotify)parse_value_free);
96 return g;
97 }
98
99 static void parse_group_free(ObtDDParseGroup *g)
100 {
101 g_free(g->name);
102 g_hash_table_destroy(g->key_hash);
103 g_slice_free(ObtDDParseGroup, g);
104 }
105
106 /*! Reads an input string, strips out invalid stuff, and parses
107 backslash-stuff.
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.
111 */
112 static gchar* parse_value_string(const gchar *in,
113 gboolean locale,
114 gulong *nstrings,
115 const ObtDDParse *const parse,
116 gboolean *error)
117 {
118 const gint bytes = strlen(in);
119 gboolean backslash;
120 gchar *out, *o;
121 const gchar *end, *i;
122
123 g_return_val_if_fail(in != NULL, NULL);
124
125 if (!locale) {
126 end = in + bytes;
127 for (i = in; i < end; ++i) {
128 if ((guchar)*i >= 127 || (guchar)*i < 32) {
129 /* non-control character ascii */
130 end = i;
131 parse_error("Invalid bytes in string", parse, error);
132 break;
133 }
134 }
135 }
136 else if (!g_utf8_validate(in, bytes, &end))
137 parse_error("Invalid bytes in localestring", parse, error);
138
139 if (nstrings) *nstrings = 1;
140
141 out = g_new(char, bytes + 1);
142 i = in; o = out;
143 backslash = FALSE;
144 while (i < end) {
145 const gchar *next = locale ? g_utf8_find_next_char(i, end) : i+1;
146 if (backslash) {
147 switch(*i) {
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;
154 default:
155 parse_error((locale ?
156 "Invalid escape sequence in localestring" :
157 "Invalid escape sequence in string"),
158 parse, error);
159 }
160 backslash = FALSE;
161 }
162 else if (*i == '\\')
163 backslash = TRUE;
164 else if (*i == ';' && nstrings) {
165 ++nstrings;
166 *o = '\0';
167 }
168 else if ((guchar)*i == 127 || (guchar)*i < 32) {
169 /* avoid ascii control characters */
170 parse_error("Found control character in string", parse, error);
171 break;
172 }
173 else {
174 memcpy(o, i, next-i);
175 o += next-i;
176 }
177 i = next;
178 }
179 *o = '\0';
180 return o;
181 }
182
183 static gboolean parse_value_boolean(const gchar *in,
184 const ObtDDParse *const parse,
185 gboolean *error)
186 {
187 if (strcmp(in, "true") == 0)
188 return TRUE;
189 else if (strcmp(in, "false") != 0)
190 parse_error("Invalid boolean value", parse, error);
191 return FALSE;
192 }
193
194 static gfloat parse_value_numeric(const gchar *in,
195 const ObtDDParse *const parse,
196 gboolean *error)
197 {
198 gfloat out = 0;
199 if (sscanf(in, "%f", &out) == 0)
200 parse_error("Invalid numeric value", parse, error);
201 return out;
202 }
203
204 static gboolean parse_file_line(FILE *f, gchar **buf,
205 gulong *size, gulong *read,
206 ObtDDParse *parse, gboolean *error)
207 {
208 const gulong BUFMUL = 80;
209 size_t ret;
210 gulong i, null;
211
212 if (*size == 0) {
213 g_assert(*read == 0);
214 *size = BUFMUL;
215 *buf = g_new(char, *size);
216 }
217
218 /* remove everything up to a null zero already in the buffer and shift
219 the rest to the front */
220 null = *size;
221 for (i = 0; i < *read; ++i) {
222 if (null < *size)
223 (*buf)[i-null-1] = (*buf)[i];
224 else if ((*buf)[i] == '\0')
225 null = i;
226 }
227 if (null < *size)
228 *read -= null + 1;
229
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 */
234 (*buf)[i] = '\0';
235 return TRUE;
236 }
237
238 /* we need to read some more to find a newline */
239 while (TRUE) {
240 gulong eol;
241 gchar *newread;
242
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);
247 return FALSE;
248 }
249 *read += ret;
250
251 /* strip out null zeros in the input and look for an endofline */
252 null = 0;
253 eol = *size;
254 for (i = newread-*buf; i < *read; ++i) {
255 if (null > 0)
256 (*buf)[i] = (*buf)[i+null];
257 if ((*buf)[i] == '\0') {
258 ++null;
259 --(*read);
260 --i; /* try again */
261 }
262 else if ((*buf)[i] == '\n' && eol == *size) {
263 eol = i;
264 /* turn it into a null zero */
265 (*buf)[i] = '\0';
266 }
267 }
268
269 if (eol != *size)
270 /* found an endofline, done */
271 break;
272 else if (feof(f) && *read < *size) {
273 /* found the endoffile, done (if there is space) */
274 if (*read > 0) {
275 /* stick a null zero on if there is test on the last line */
276 (*buf)[(*read)++] = '\0';
277 }
278 break;
279 }
280 else {
281 /* read more */
282 size += BUFMUL;
283 *buf = g_renew(char, *buf, *size);
284 }
285 }
286 return *read > 0;
287 }
288
289 static void parse_group(const gchar *buf, gulong len,
290 ObtDDParse *parse, gboolean *error)
291 {
292 ObtDDParseGroup *g;
293 gchar *group;
294 gulong i;
295
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 */
303 break;
304 }
305
306 /* make sure it's a new group */
307 g = g_hash_table_lookup(parse->group_hash, group);
308 if (g && g->seen) {
309 parse_error("Duplicate group found", parse, error);
310 g_free(group);
311 return;
312 }
313 /* if it's the first group, make sure it's named Desktop Entry */
314 else if (!parse->group && strcmp(group, "Desktop Entry") != 0)
315 {
316 parse_error("Incorrect group found, "
317 "expected [Desktop Entry]",
318 parse, error);
319 g_free(group);
320 return;
321 }
322 else {
323 if (!g) {
324 g = parse_group_new(group, NULL);
325 g_hash_table_insert(parse->group_hash, g->name, g);
326 }
327 else
328 g_free(group);
329
330 g->seen = TRUE;
331 parse->group = g;
332 g_print("Found group %s\n", g->name);
333 }
334 }
335
336 static void parse_key_value(const gchar *buf, gulong len,
337 ObtDDParse *parse, gboolean *error)
338 {
339 gulong i, keyend, valstart, eq;
340 char *key;
341
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 */
349 keyend = i;
350 break;
351 }
352 if (keyend < 1) {
353 parse_error("Empty key", parse, error);
354 return;
355 }
356 /* find the = character */
357 for (i = keyend; i < len; ++i) {
358 if (buf[i] == '=') {
359 eq = i;
360 break;
361 }
362 else if (buf[i] != ' ') {
363 parse_error("Invalid character in key name", parse, error);
364 return ;
365 }
366 }
367 if (i == len) {
368 parse_error("Key without value found", parse, error);
369 return;
370 }
371 /* find the start of the value */
372 for (i = eq+1; i < len; ++i)
373 if (buf[i] != ' ') {
374 valstart = i;
375 break;
376 }
377 if (i == len) {
378 parse_error("Empty value found", parse, error);
379 return;
380 }
381
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);
385 g_free(key);
386 return;
387 }
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);
391 }
392
393 static gboolean parse_file(FILE *f, ObtDDParse *parse)
394 {
395 gchar *buf = NULL;
396 gulong bytes = 0, read = 0;
397 gboolean error = FALSE;
398
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);
408 else
409 /* ignore errors in key-value pairs and continue */
410 parse_key_value(buf, len, parse, NULL);
411 ++parse->lineno;
412 }
413
414 if (buf) g_free(buf);
415 return !error;
416 }
417
418 static void parse_desktop_entry_value(gchar *key, const gchar *val,
419 ObtDDParse *parse, gboolean *error)
420 {
421 ObtDDParseValue v, *pv;
422
423 /* figure out value type */
424 v.type = OBT_DDPARSE_NUM_VALUE_TYPES;
425 /* XXX do this part */
426
427 /* parse the value */
428 switch (v.type) {
429 case OBT_DDPARSE_STRING:
430 v.value.string = parse_value_string(val, FALSE, NULL, parse, error);
431 g_assert(v.value.string);
432 break;
433 case OBT_DDPARSE_LOCALESTRING:
434 v.value.string = parse_value_string(val, TRUE, NULL, parse, error);
435 g_assert(v.value.string);
436 break;
437 case OBT_DDPARSE_STRINGS:
438 v.value.strings.s = parse_value_string(val, FALSE, &v.value.strings.n,
439 parse, error);
440 g_assert(v.value.strings.s);
441 g_assert(v.value.strings.n);
442 break;
443 case OBT_DDPARSE_LOCALESTRINGS:
444 v.value.strings.s = parse_value_string(val, TRUE, &v.value.strings.n,
445 parse, error);
446 g_assert(v.value.strings.s);
447 g_assert(v.value.strings.n);
448 break;
449 case OBT_DDPARSE_BOOLEAN:
450 v.value.boolean = parse_value_boolean(val, parse, error);
451 break;
452 case OBT_DDPARSE_NUMERIC:
453 v.value.numeric = parse_value_numeric(val, parse, error);
454 break;
455 default:
456 g_assert_not_reached();
457 }
458
459 pv = g_slice_new(ObtDDParseValue);
460 *pv = v;
461 g_hash_table_insert(parse->group->key_hash, key, pv);
462 }
463
464 GHashTable* obt_ddparse_file(const gchar *name, GSList *paths)
465 {
466 ObtDDParse parse;
467 ObtDDParseGroup *desktop_entry;
468 GSList *it;
469 FILE *f;
470 gboolean success;
471
472 parse.filename = NULL;
473 parse.lineno = 0;
474 parse.group = NULL;
475 parse.group_hash = g_hash_table_new_full(g_str_hash,
476 g_str_equal,
477 NULL,
478 (GDestroyNotify)parse_group_free);
479
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);
484
485 success = FALSE;
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;
490 parse.lineno = 1;
491 success = parse_file(f, &parse);
492 fclose(f);
493 }
494 g_free(path);
495 }
496 if (!success) {
497 g_hash_table_destroy(parse.group_hash);
498 return NULL;
499 }
500 else
501 return parse.group_hash;
502 }
503
504 GHashTable* obt_ddparse_group_keys(ObtDDParseGroup *g)
505 {
506 return g->key_hash;
507 }
This page took 0.052467 seconds and 3 git commands to generate.