Parent Directory | Revision Log
Option to use float math instead of double
1 | /* |
2 | * Copyright (C) 2008 Till Harbaum <till@harbaum.org>. |
3 | * |
4 | * This file is part of OSM2Go. |
5 | * |
6 | * OSM2Go 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 3 of the License, or |
9 | * (at your option) any later version. |
10 | * |
11 | * OSM2Go 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 | * You should have received a copy of the GNU General Public License |
17 | * along with OSM2Go. If not, see <http://www.gnu.org/licenses/>. |
18 | */ |
19 | |
20 | /* |
21 | * qnd_xml - quick'n dirty xml is a very small and very fast implementation |
22 | * of a xml parser. The idea is to replace the usage of libxml2 |
23 | * by this whenever performance is an issue. This is the case |
24 | * with reading the *.osm files on mobile devices. A powerful |
25 | * desktop will likely still use the libxml as it's just "better" |
26 | */ |
27 | |
28 | #include "appdata.h" |
29 | |
30 | #include <ctype.h> |
31 | int isblank(int c); |
32 | |
33 | typedef struct { |
34 | gpointer userdata; |
35 | |
36 | int total, bytes_read; |
37 | |
38 | char *buffer, *cur; |
39 | |
40 | qnd_xml_stack_t *stack, *sp; |
41 | int mod; // modifier (?, !, /) in element |
42 | gboolean done; |
43 | |
44 | qnd_xml_attribute_t *attributes; |
45 | |
46 | } qnd_xml_context_t; |
47 | |
48 | |
49 | void stack_dump(qnd_xml_context_t *context) { |
50 | qnd_xml_stack_t *stack = context->stack; |
51 | |
52 | printf("Stack:\n"); |
53 | while(stack) { |
54 | if(stack == context->sp) printf(" *"); |
55 | else printf(" "); |
56 | |
57 | printf("%s\n", stack->entry->name); |
58 | stack = stack->next; |
59 | } |
60 | } |
61 | |
62 | void stack_push(qnd_xml_context_t *context, qnd_xml_entry_t *entry) { |
63 | // printf("push %s\n", entry->name); |
64 | |
65 | context->sp->next = g_new0(qnd_xml_stack_t, 1); |
66 | context->sp->next->prev = context->sp; |
67 | context->sp = context->sp->next; |
68 | context->sp->entry = entry; |
69 | |
70 | // stack_dump(context); |
71 | } |
72 | |
73 | qnd_xml_entry_t *stack_pop(qnd_xml_context_t *context) { |
74 | qnd_xml_entry_t *cur = context->sp->entry; |
75 | |
76 | context->sp = context->sp->prev; |
77 | g_free(context->sp->next); |
78 | context->sp->next = NULL; |
79 | |
80 | /* did we just empty the stack? if yes, we're done parsing */ |
81 | if(context->sp == context->stack) { |
82 | printf("done parsing\n"); |
83 | context->done = TRUE; |
84 | } |
85 | |
86 | // printf("popped %s\n", cur->name); |
87 | // stack_dump(context); |
88 | return cur; |
89 | } |
90 | |
91 | /* |
92 | utf8: |
93 | 0xxxxxxx |
94 | 110xxxxx 10xxxxxx |
95 | 1110xxxx 10xxxxxx 10xxxxxx |
96 | 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx |
97 | |
98 | Do we really need to handle this? Internally we are only |
99 | handling ascii characters (e.g. '<', '>', '/', '?' etc.) |
100 | thus it's only important to be able to skip utf8 characters |
101 | correctly. Since a subbyte of utf8 never equals a ascii character |
102 | it should be possible to parse the file correctly when ignoring utf8 |
103 | */ |
104 | |
105 | /* TODO: this needs to be updated to cope with utf8 */ |
106 | inline char current_char(qnd_xml_context_t *context) { |
107 | return *context->cur; |
108 | } |
109 | |
110 | /* TODO: this needs to be updated to cope with utf8 */ |
111 | inline gboolean skip_char(qnd_xml_context_t *context) { |
112 | context->cur++; |
113 | /* TODO: check buffer range */ |
114 | return TRUE; |
115 | } |
116 | |
117 | gboolean skip_to_char(qnd_xml_context_t *context, char *chrs) { |
118 | while(context->cur < context->buffer + context->total) { |
119 | if(strchr(chrs, current_char(context))) { |
120 | return skip_char(context); |
121 | } |
122 | if(!skip_char(context)) return FALSE; |
123 | } |
124 | |
125 | /* if we get here the system was unable to fill the buffer */ |
126 | return FALSE; |
127 | } |
128 | |
129 | gboolean buffer_overflow(qnd_xml_context_t *context) { |
130 | return(!(context->cur < context->buffer + context->total)); |
131 | } |
132 | |
133 | gboolean get_element_name(qnd_xml_context_t *context) { |
134 | |
135 | char *start = context->cur; |
136 | |
137 | if(buffer_overflow(context) || !isalpha(current_char(context))) { |
138 | printf("invalid element name #1 (%c)\n", current_char(context)); |
139 | return FALSE; |
140 | } |
141 | |
142 | while(!buffer_overflow(context) && !isblank(current_char(context)) && |
143 | (current_char(context) != '>')) { |
144 | if(!isalnum(current_char(context))) { |
145 | printf("invalid element name #2 (%c)\n", current_char(context)); |
146 | return FALSE; |
147 | } |
148 | if(!skip_char(context)) return FALSE; |
149 | } |
150 | |
151 | #if 0 |
152 | char *format = g_strdup_printf("Element name = %%.%ds\n", |
153 | context->cur-start); |
154 | printf(format, start); |
155 | g_free(format); |
156 | #endif |
157 | |
158 | /* handle special elements locally */ |
159 | if(context->mod) { |
160 | |
161 | } else { |
162 | qnd_xml_entry_t *entry = context->sp->entry, *hit = NULL; |
163 | |
164 | int i=0; |
165 | for(i=0;!hit && i<entry->num_children;i++) |
166 | if(strncmp(entry->children[i]->name, start, |
167 | strlen(entry->children[i]->name)) == 0) |
168 | hit = entry->children[i]; |
169 | |
170 | if(hit) |
171 | stack_push(context, hit); |
172 | else { |
173 | printf("element search failed\n"); |
174 | return FALSE; |
175 | } |
176 | } |
177 | |
178 | return TRUE; |
179 | } |
180 | |
181 | gboolean get_attribute_name(qnd_xml_context_t *context) { |
182 | |
183 | char *start = context->cur; |
184 | |
185 | if(buffer_overflow(context) || !isalpha(current_char(context))) { |
186 | printf("invalid attribute name\n"); |
187 | return FALSE; |
188 | } |
189 | |
190 | while(!buffer_overflow(context) && !isblank(current_char(context)) && |
191 | !(current_char(context) == '=')) { |
192 | if(!isalnum(current_char(context))) { |
193 | printf("invalid attribute name\n"); |
194 | return FALSE; |
195 | } |
196 | if(!skip_char(context)) return FALSE; |
197 | } |
198 | |
199 | /* attach a new attribute to chain */ |
200 | qnd_xml_attribute_t **attr = &context->attributes; |
201 | while(*attr) attr = &(*attr)->next; |
202 | |
203 | /* terminate name at closing '=' */ |
204 | *context->cur = '\0'; |
205 | |
206 | *attr = g_new0(qnd_xml_attribute_t, 1); |
207 | (*attr)->name = start; |
208 | |
209 | return TRUE; |
210 | } |
211 | |
212 | gboolean get_attribute_value(qnd_xml_context_t *context) { |
213 | |
214 | char *start = context->cur; |
215 | |
216 | while(!buffer_overflow(context) && !(current_char(context) == '\"')) |
217 | if(!skip_char(context)) return FALSE; |
218 | |
219 | /* attach a new attribute to chain */ |
220 | qnd_xml_attribute_t **attr = &context->attributes; |
221 | while((*attr) && (*attr)->next) attr = &(*attr)->next; |
222 | |
223 | if(!(*attr) || (*attr)->value) { |
224 | printf("error storing attribute value\n"); |
225 | return FALSE; |
226 | } |
227 | |
228 | /* terminate value at closing '\"' */ |
229 | *context->cur = '\0'; |
230 | (*attr)->value = start; |
231 | |
232 | return TRUE; |
233 | } |
234 | |
235 | gboolean skip_white(qnd_xml_context_t *context) { |
236 | /* skip all white space */ |
237 | while(!buffer_overflow(context) && isblank(current_char(context))) |
238 | if(!skip_char(context)) return FALSE; |
239 | |
240 | if(isblank(current_char(context))) { |
241 | printf("error skipping white space\n"); |
242 | return FALSE; |
243 | } |
244 | |
245 | return TRUE; |
246 | } |
247 | |
248 | gboolean get_attributes(qnd_xml_context_t *context) { |
249 | /* drop everything before element from buffer */ |
250 | |
251 | if(!skip_white(context)) return FALSE; |
252 | |
253 | while(isalpha(current_char(context))) { |
254 | |
255 | /* get attribute name */ |
256 | if(!get_attribute_name(context)) return FALSE; |
257 | |
258 | if(!skip_to_char(context, "=")) return FALSE; |
259 | if(!skip_to_char(context, "\"")) return FALSE; |
260 | |
261 | if(!get_attribute_value(context)) return FALSE; |
262 | if(!skip_to_char(context, "\"")) return FALSE; |
263 | |
264 | if(!skip_white(context)) return FALSE; |
265 | } |
266 | return TRUE; |
267 | } |
268 | |
269 | void attributes_free(qnd_xml_context_t *context) { |
270 | qnd_xml_attribute_t *attr = context->attributes; |
271 | |
272 | while(attr) { |
273 | qnd_xml_attribute_t *next = attr->next; |
274 | g_free(attr); |
275 | attr = next; |
276 | } |
277 | |
278 | context->attributes = NULL; |
279 | } |
280 | |
281 | void qnd_xml_cleanup(qnd_xml_context_t *context) { |
282 | /* todo: clean stack */ |
283 | |
284 | if(context->buffer) g_free(context->buffer); |
285 | g_free(context); |
286 | } |
287 | |
288 | gboolean get_element(qnd_xml_context_t *context) { |
289 | |
290 | /* skip all text */ |
291 | if(!skip_to_char(context, "<")) return FALSE; |
292 | |
293 | /* handle optional modifier */ |
294 | if(current_char(context) == '?' || current_char(context) == '!') { |
295 | context->mod = current_char(context); |
296 | if(!skip_char(context)) return FALSE; |
297 | } else |
298 | context->mod = 0; |
299 | |
300 | /* check for closing element */ |
301 | if(current_char(context) == '/') { |
302 | context->mod = '/'; |
303 | if(!skip_char(context)) return FALSE; |
304 | } |
305 | |
306 | if(!get_element_name(context)) return FALSE; |
307 | if(!get_attributes(context)) return FALSE; |
308 | |
309 | if(context->mod && context->mod != '/') { |
310 | if(current_char(context) != context->mod) { |
311 | printf("modifier mismatch\n"); |
312 | return FALSE; |
313 | } |
314 | |
315 | /* skip the modifier */ |
316 | if(!skip_char(context)) return FALSE; |
317 | } |
318 | |
319 | if(!skip_white(context)) return FALSE; |
320 | |
321 | /* call callback now since the entry may be taken from stack */ |
322 | if(!context->mod && context->sp->entry->cb) |
323 | if(!context->sp->entry->cb(context->sp, |
324 | context->attributes, context->userdata)) |
325 | return FALSE; |
326 | |
327 | if(context->mod == '/') |
328 | stack_pop(context); |
329 | else { |
330 | /* if this element closes here it's cleaned up immediately */ |
331 | if(current_char(context) == '/') { |
332 | if(!skip_char(context)) return FALSE; |
333 | stack_pop(context); |
334 | } |
335 | } |
336 | |
337 | if(current_char(context) != '>') { |
338 | printf("element closing error\n"); |
339 | return FALSE; |
340 | } |
341 | |
342 | if(!skip_char(context)) return FALSE; |
343 | |
344 | attributes_free(context); |
345 | |
346 | return TRUE; |
347 | } |
348 | |
349 | gpointer qnd_xml_parse(char *name, qnd_xml_entry_t *root, gpointer userdata) { |
350 | FILE *file = NULL; |
351 | qnd_xml_context_t *context = g_new0(qnd_xml_context_t, 1); |
352 | context->cur = context->buffer; |
353 | context->userdata = userdata; |
354 | |
355 | /* init stack by adding root entry */ |
356 | context->sp = context->stack = g_new0(qnd_xml_stack_t, 1); |
357 | context->sp->entry = root; |
358 | |
359 | /* check if file exists and is a regular file */ |
360 | if(!g_file_test(name, G_FILE_TEST_IS_REGULAR)) { |
361 | printf("file doesn't exist or is not a regular file\n"); |
362 | qnd_xml_cleanup(context); |
363 | return FALSE; |
364 | } |
365 | |
366 | /* open file */ |
367 | file = g_fopen(name, "r"); |
368 | if(!file) { |
369 | printf("unable to open file\n"); |
370 | qnd_xml_cleanup(context); |
371 | return FALSE; |
372 | } |
373 | |
374 | printf("file is open\n"); |
375 | |
376 | /* get file length */ |
377 | fseek(file, 0l, SEEK_END); |
378 | context->total = ftell(file); |
379 | fseek(file, 0l, SEEK_SET); |
380 | |
381 | printf("file length is %d bytes\n", context->total); |
382 | |
383 | /* load entire file into buffer */ |
384 | context->buffer = g_malloc(context->total); |
385 | fread(context->buffer, 1l, context->total, file); |
386 | fclose(file); |
387 | context->cur = context->buffer; |
388 | |
389 | setlocale(LC_NUMERIC, "C"); |
390 | |
391 | gboolean error = FALSE; |
392 | do |
393 | error = !get_element(context); |
394 | while(!error && !context->done); |
395 | |
396 | setlocale(LC_NUMERIC, ""); |
397 | |
398 | if(error) printf("parser ended with error\n"); |
399 | else printf("parser ended successfully\n"); |
400 | |
401 | printf("current bytes read: %d of %d\n", |
402 | context->bytes_read, context->total); |
403 | printf("current buffer offset: %d\n", context->cur - context->buffer); |
404 | |
405 | /* user pointer[0] of root element is retval */ |
406 | gpointer retval = error?NULL:context->stack->userdata[0]; |
407 | |
408 | /* close file and cleanup */ |
409 | qnd_xml_cleanup(context); |
410 | |
411 | return retval; |
412 | } |
413 | |
414 | char *qnd_xml_get_prop(qnd_xml_attribute_t *attr, char *name) { |
415 | while(attr) { |
416 | if(strcasecmp(name, attr->name) == 0) |
417 | return attr->value; |
418 | |
419 | attr = attr->next; |
420 | } |
421 | return NULL; |
422 | } |
423 | |
424 | char *qnd_xml_get_prop_str(qnd_xml_attribute_t *attr, char *name) { |
425 | char *value = qnd_xml_get_prop(attr, name); |
426 | if(value) return g_strdup(value); |
427 | return NULL; |
428 | } |
429 | |
430 | /* strtof this is c99 */ |
431 | float strtof(const char *nptr, char **endptr); |
432 | |
433 | gboolean qnd_xml_get_prop_float(qnd_xml_attribute_t *attr, char *name, |
434 | float *dest) { |
435 | char *value = qnd_xml_get_prop(attr, name); |
436 | if(!value) return FALSE; |
437 | *dest = strtof(value, NULL); |
438 | return TRUE; |
439 | } |
440 | |
441 | gboolean qnd_xml_get_prop_double(qnd_xml_attribute_t *attr, char *name, |
442 | double *dest) { |
443 | char *value = qnd_xml_get_prop(attr, name); |
444 | if(!value) return FALSE; |
445 | |
446 | *dest = g_ascii_strtod(value, NULL); |
447 | return TRUE; |
448 | } |
449 | |
450 | gboolean qnd_xml_get_prop_gulong(qnd_xml_attribute_t *attr, char *name, |
451 | gulong *dest) { |
452 | char *value = qnd_xml_get_prop(attr, name); |
453 | if(!value) return FALSE; |
454 | |
455 | *dest = strtoul(value, NULL, 10); |
456 | return TRUE; |
457 | } |
458 | |
459 | gboolean qnd_xml_get_prop_is(qnd_xml_attribute_t *attr, char *name, |
460 | char *ref) { |
461 | char *value = qnd_xml_get_prop(attr, name); |
462 | if(!value) return FALSE; |
463 | |
464 | return g_strcasecmp(ref, value); |
465 | } |