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
)