2 * Navit, a modular navigation system.
3 * Copyright (C) 2005-2008 Navit Team
5 * This program is free software; you can redistribute it and/or
6 * modify it under the terms of the GNU General Public License
7 * version 2 as published by the Free Software Foundation.
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
14 * You should have received a copy of the GNU General Public License
15 * along with this program; if not, write to the
16 * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
17 * Boston, MA 02110-1301, USA.
20 /* see http://library.gnome.org/devel/glib/stable/glib-Simple-XML-Subset-Parser.html
21 * for details on how the xml file parser works.
25 #include <glib/gprintf.h>
33 #include "projection.h"
35 #include "navigation.h"
48 #include "announcement.h"
49 #include "vehicleprofile.h"
50 #include "roadprofile.h"
51 #include "xmlconfig.h"
54 #define ATTR_DISTANCE 1
57 #define ATTR_DISTANCE 2
58 #define G_MARKUP_ERROR 0
59 #define G_MARKUP_ERROR_INVALID_CONTENT 0
60 #define G_MARKUP_ERROR_PARSE 0
61 #define G_MARKUP_ERROR_UNKNOWN_ELEMENT 0
62 typedef void * GMarkupParseContext;
66 struct xistate *parent;
67 struct xistate *child;
69 const gchar **attribute_names;
70 const gchar **attribute_values;
75 const gchar *xpointer;
77 struct xistate *first;
85 const gchar **attribute_names;
86 const gchar **attribute_values;
87 struct xmlstate *parent;
88 struct attr element_attr;
91 struct element_func *func;
92 struct object_func *object_func;
93 struct xmldocument *document;
102 static struct attr ** convert_to_attrs(struct xmlstate *state, struct attr_fixme *fixme)
104 const gchar **attribute_name=state->attribute_names;
105 const gchar **attribute_value=state->attribute_values;
109 static int fixme_count;
111 while (*attribute_name) {
115 ret=g_new(struct attr *, count+1);
116 attribute_name=state->attribute_names;
118 while (*attribute_name) {
119 name=*attribute_name;
121 char **attr_fixme=fixme->attr_fixme;
122 while (attr_fixme[0]) {
123 if (! strcmp(name, attr_fixme[0])) {
125 if (fixme_count++ < 10)
126 dbg(0,"Please change attribute '%s' to '%s' in <%s />\n", attr_fixme[0], attr_fixme[1], fixme->element);
132 ret[count]=attr_new_from_text(name,*attribute_value);
135 else if (strcmp(*attribute_name,"enabled"))
136 dbg(0,"failed to create attribute '%s' with value '%s'\n", *attribute_name,*attribute_value);
141 dbg(1,"ret=%p\n", ret);
146 static const char * find_attribute(struct xmlstate *state, const char *attribute, int required)
148 const gchar **attribute_name=state->attribute_names;
149 const gchar **attribute_value=state->attribute_values;
150 while(*attribute_name) {
151 if(! g_ascii_strcasecmp(attribute,*attribute_name))
152 return *attribute_value;
157 g_set_error(state->error,G_MARKUP_ERROR,G_MARKUP_ERROR_INVALID_CONTENT, "element '%s' is missing attribute '%s'", state->element, attribute);
162 find_boolean(struct xmlstate *state, const char *attribute, int deflt, int required)
166 value=find_attribute(state, attribute, required);
169 if (g_ascii_strcasecmp(value,"no") && g_ascii_strcasecmp(value,"0") && g_ascii_strcasecmp(value,"false"))
175 * * Convert a string number to int
177 * * @param val the string value to convert
178 * * @returns int value of converted string
181 convert_number(const char *val)
184 return g_ascii_strtoull(val,NULL,0);
190 xmlconfig_config(struct xmlstate *state)
192 state->element_attr.u.data = (void *)1;
197 xmlconfig_announce(struct xmlstate *state)
199 const char *type,*value;
203 enum item_type itype;
204 char *tok, *type_str, *str;
206 type=find_attribute(state, "type", 1);
209 for (i = 0 ; i < 3 ; i++) {
210 sprintf(key,"level%d", i);
211 value=find_attribute(state, key, 0);
213 level[i]=convert_number(value);
217 type_str=g_strdup(type);
219 while ((tok=strtok(str, ","))) {
220 itype=item_from_name(tok);
221 navigation_set_announce(state->parent->element_attr.u.data, itype, level);
228 * * Define the elements in our config
232 #define NEW(x) (void *(*)(struct attr *, struct attr **))(x)
233 #define GET(x) (int (*)(void *, enum attr_type type, struct attr *attr, struct attr_iter *iter))(x)
234 #define ITERN(x) (struct attr_iter * (*)(void *))(x)
235 #define ITERD(x) (void (*)(struct attr_iter *iter))(x)
236 #define SET(x) (int (*)(void *, struct attr *attr))(x)
237 #define ADD(x) (int (*)(void *, struct attr *attr))(x)
238 #define REMOVE(x) (int (*)(void *, struct attr *attr))(x)
239 #define INIT(x) (int (*)(void *))(x)
240 #define DESTROY(x) (void (*)(void *))(x)
242 static struct object_func object_funcs[] = {
243 { attr_announcement,NEW(announcement_new), GET(announcement_get_attr), NULL, NULL, SET(announcement_set_attr), ADD(announcement_add_attr) },
244 { attr_arrows, NEW(arrows_new)},
245 { attr_circle, NEW(circle_new), NULL, NULL, NULL, NULL, ADD(element_add_attr)},
246 { attr_coord, NEW(coord_new_from_attrs)},
247 { attr_cursor, NEW(cursor_new), NULL, NULL, NULL, NULL, ADD(cursor_add_attr)},
248 { attr_debug, NEW(debug_new)},
249 { attr_graphics, NEW(graphics_new)},
250 { attr_gui, NEW(gui_new), GET(gui_get_attr)},
251 { attr_icon, NEW(icon_new), NULL, NULL, NULL, NULL, ADD(element_add_attr)},
252 { attr_image, NEW(image_new)},
253 { attr_itemgra, NEW(itemgra_new), NULL, NULL, NULL, NULL, ADD(itemgra_add_attr)},
254 { attr_layer, NEW(layer_new), NULL, NULL, NULL, NULL, ADD(layer_add_attr)},
255 { attr_layout, NEW(layout_new), NULL, NULL, NULL, NULL, ADD(layout_add_attr)},
256 { attr_log, NEW(log_new)},
257 { attr_map, NEW(map_new)},
258 { attr_mapset, NEW(mapset_new), NULL, NULL, NULL, NULL, ADD(mapset_add_attr)},
259 { attr_navigation, NEW(navigation_new), GET(navigation_get_attr)},
260 { attr_navit, NEW(navit_new), GET(navit_get_attr), ITERN(navit_attr_iter_new), ITERD(navit_attr_iter_destroy), SET(navit_set_attr), ADD(navit_add_attr), REMOVE(navit_remove_attr), INIT(navit_init), DESTROY(navit_destroy)},
261 { attr_osd, NEW(osd_new)},
262 { attr_plugins, NEW(plugins_new), NULL, NULL, NULL, NULL, NULL, NULL, INIT(plugins_init)},
263 { attr_plugin, NEW(plugin_new)},
264 { attr_polygon, NEW(polygon_new), NULL, NULL, NULL, NULL, ADD(element_add_attr)},
265 { attr_polyline, NEW(polyline_new), NULL, NULL, NULL, NULL, ADD(element_add_attr)},
266 { attr_roadprofile,NEW(roadprofile_new), GET(roadprofile_get_attr), NULL, NULL, SET(roadprofile_set_attr), ADD(roadprofile_add_attr) },
267 { attr_route, NEW(route_new), GET(route_get_attr)},
268 { attr_speech, NEW(speech_new), GET(speech_get_attr), NULL, NULL, SET(speech_set_attr)},
269 { attr_text, NEW(text_new)},
270 { attr_tracking, NEW(tracking_new)},
271 { attr_vehicle, NEW(vehicle_new), GET(vehicle_get_attr), NULL, NULL, NULL, ADD(vehicle_add_attr) },
272 { attr_vehicleprofile, NEW(vehicleprofile_new), GET(vehicleprofile_get_attr), NULL, NULL, SET(vehicleprofile_set_attr), ADD(vehicleprofile_add_attr) },
276 object_func_lookup(enum attr_type type)
279 for (i = 0 ; i < sizeof(object_funcs)/sizeof(struct object_func); i++) {
280 if (object_funcs[i].type == type)
281 return &object_funcs[i];
286 struct element_func {
289 int (*func)(struct xmlstate *state);
292 { "config", NULL, xmlconfig_config},
293 { "announce", "navigation", xmlconfig_announce},
294 { "speech", "navit", NULL, attr_speech},
295 { "tracking", "navit", NULL, attr_tracking},
296 { "route", "navit", NULL, attr_route},
297 { "mapset", "navit", NULL, attr_mapset},
298 { "map", "mapset", NULL, attr_map},
299 { "debug", "config", NULL, attr_debug},
300 { "osd", "navit", NULL, attr_osd},
301 { "navigation", "navit", NULL, attr_navigation},
302 { "navit", "config", NULL, attr_navit},
303 { "graphics", "navit", NULL, attr_graphics},
304 { "gui", "navit", NULL, attr_gui},
305 { "layout", "navit", NULL, attr_layout},
306 { "layer", "layout", NULL, attr_layer},
307 { "itemgra", "layer", NULL, attr_itemgra},
308 { "circle", "itemgra", NULL, attr_circle},
309 { "coord", "circle", NULL, attr_coord},
310 { "icon", "itemgra", NULL, attr_icon},
311 { "coord", "icon", NULL, attr_coord},
312 { "image", "itemgra", NULL, attr_image},
313 { "text", "itemgra", NULL, attr_text},
314 { "polygon", "itemgra", NULL, attr_polygon},
315 { "coord", "polygon", NULL, attr_coord},
316 { "polyline", "itemgra", NULL, attr_polyline},
317 { "coord", "polyline", NULL, attr_coord},
318 { "arrows", "itemgra", NULL, attr_arrows},
319 { "vehicle", "navit", NULL, attr_vehicle},
320 { "vehicleprofile", "navit", NULL, attr_vehicleprofile},
321 { "roadprofile", "vehicleprofile", NULL, attr_roadprofile},
322 { "announcement", "roadprofile", NULL, attr_announcement},
323 { "cursor", "vehicle", NULL, attr_cursor},
324 { "itemgra", "cursor", NULL, attr_itemgra},
325 { "log", "vehicle", NULL, attr_log},
326 { "log", "navit", NULL, attr_log},
327 { "plugins", "config", NULL, attr_plugins},
328 { "plugin", "plugins", NULL, attr_plugin},
333 static char *attr_fixme_itemgra[]={
338 static char *attr_fixme_text[]={
339 "label_size","text_size",
343 static char *attr_fixme_circle[]={
344 "label_size","text_size",
348 static struct attr_fixme attr_fixmes[]={
349 {"item",attr_fixme_itemgra},
350 {"itemgra",attr_fixme_itemgra},
351 {"text",attr_fixme_text},
352 {"label",attr_fixme_text},
353 {"circle",attr_fixme_circle},
358 static char *element_fixmes[]={
364 * * Parse the opening tag of a config element
366 * * @param context document parse context
367 * * @param element_name the current tag name
368 * * @param attribute_names ptr to return the set of attribute names
369 * * @param attribute_values ptr return the set of attribute values
370 * * @param user_data ptr to xmlstate structure
371 * * @param error ptr return error context
376 start_element(GMarkupParseContext *context,
377 const gchar *element_name,
378 const gchar **attribute_names,
379 const gchar **attribute_values,
383 struct xmlstate *new=NULL, **parent = user_data;
384 struct element_func *e=elements,*func=NULL;
385 struct attr_fixme *attr_fixme=attr_fixmes;
386 char **element_fixme=element_fixmes;
388 static int fixme_count;
389 const char *parent_name=NULL;
390 char *s,*sep="",*possible_parents;
391 dbg(2,"name='%s' parent='%s'\n", element_name, *parent ? (*parent)->element:NULL);
393 /* determine if we have to fix any attributes */
394 while (attr_fixme[0].element) {
395 if (!strcmp(element_name,attr_fixme[0].element))
399 if (!attr_fixme[0].element)
402 /* tell user to fix deprecated element names */
403 while (element_fixme[0]) {
404 if (!strcmp(element_name,element_fixme[0])) {
405 element_name=element_fixme[1];
406 if (fixme_count++ < 10)
407 dbg(0,"Please change <%s /> to <%s /> in config file\n", element_fixme[0], element_fixme[1]);
411 /* validate that this element is valid
412 * and that the element has a valid parent */
413 possible_parents=g_strdup("");
415 parent_name=(*parent)->element;
417 if (!g_ascii_strcasecmp(element_name, e->name)) {
419 s=g_strconcat(possible_parents,sep,e->parent,NULL);
420 g_free(possible_parents);
423 if ((parent_name && e->parent && !g_ascii_strcasecmp(parent_name, e->parent)) ||
424 (!parent_name && !e->parent))
430 g_set_error(error,G_MARKUP_ERROR,G_MARKUP_ERROR_UNKNOWN_ELEMENT,
431 "Unknown element '%s'", element_name);
432 g_free(possible_parents);
436 g_set_error(error,G_MARKUP_ERROR,G_MARKUP_ERROR_INVALID_CONTENT,
437 "Element '%s' within unexpected context '%s'. Expected '%s'%s",
438 element_name, parent_name, possible_parents, ! strcmp(possible_parents, "config") ? "\nPlease add <config> </config> tags at the beginning/end of your navit.xml": "");
439 g_free(possible_parents);
442 g_free(possible_parents);
444 new=g_new(struct xmlstate, 1);
445 new->attribute_names=attribute_names;
446 new->attribute_values=attribute_values;
448 new->element_attr.u.data=NULL;
449 new->element=element_name;
452 new->object_func=NULL;
454 if (!find_boolean(new, "enabled", 1, 0))
456 if (new->parent && !new->parent->element_attr.u.data)
459 if (!func->func(new)) {
465 new->object_func=object_func_lookup(func->type);
466 if (! new->object_func)
468 attrs=convert_to_attrs(new,attr_fixme);
469 new->element_attr.type=attr_none;
470 new->element_attr.u.data = new->object_func->new(&new->parent->element_attr, attrs);
471 if (! new->element_attr.u.data)
473 new->element_attr.type=attr_from_name(element_name);
474 if (new->element_attr.type == attr_none)
475 dbg(0,"failed to create object of type '%s'\n", element_name);
476 if (new->parent->object_func && new->parent->object_func->add_attr)
477 new->parent->object_func->add_attr(new->parent->element_attr.u.data, &new->element_attr);
483 /* Called for close tags </foo> */
485 end_element (GMarkupParseContext *context,
486 const gchar *element_name,
490 struct xmlstate *curr, **state = user_data;
492 dbg(2,"name='%s'\n", element_name);
494 if (curr->object_func && curr->object_func->init)
495 curr->object_func->init(curr->element_attr.u.data);
500 static gboolean parse_file(struct xmldocument *document, xmlerror **error);
503 xinclude(GMarkupParseContext *context, const gchar **attribute_names, const gchar **attribute_values, struct xmldocument *doc_old, xmlerror **error)
505 struct xmldocument doc_new;
506 struct file_wordexp *we;
508 const char *href=NULL;
511 if (doc_old->level >= 16) {
512 g_set_error(error,G_MARKUP_ERROR,G_MARKUP_ERROR_INVALID_CONTENT, "xi:include recursion too deep");
515 memset(&doc_new, 0, sizeof(doc_new));
517 while (attribute_names[i]) {
518 if(!g_ascii_strcasecmp("href", attribute_names[i])) {
520 href=attribute_values[i];
522 g_set_error(error,G_MARKUP_ERROR,G_MARKUP_ERROR_INVALID_CONTENT, "xi:include has more than one href");
525 } else if(!g_ascii_strcasecmp("xpointer", attribute_names[i])) {
526 if (!doc_new.xpointer)
527 doc_new.xpointer=attribute_values[i];
529 g_set_error(error,G_MARKUP_ERROR,G_MARKUP_ERROR_INVALID_CONTENT, "xi:include has more than one xpointer");
533 g_set_error(error,G_MARKUP_ERROR,G_MARKUP_ERROR_INVALID_CONTENT, "xi:include has invalid attributes");
538 if (!doc_new.xpointer && !href) {
539 g_set_error(error,G_MARKUP_ERROR,G_MARKUP_ERROR_INVALID_CONTENT, "xi:include has neither href nor xpointer");
542 doc_new.level=doc_old->level+1;
543 doc_new.user_data=doc_old->user_data;
545 dbg(1,"no href, using '%s'\n", doc_old->href);
546 doc_new.href=doc_old->href;
547 parse_file(&doc_new, error);
549 dbg(1,"expanding '%s'\n", href);
550 we=file_wordexp_new(href);
551 we_files=file_wordexp_get_array(we);
552 count=file_wordexp_get_count(we);
553 dbg(1,"%d results\n", count);
554 if (count != 1 || file_exists(we_files[0])) {
555 for (i = 0 ; i < count ; i++) {
556 dbg(1,"result[%d]='%s'\n", i, we_files[i]);
557 doc_new.href=we_files[i];
558 parse_file(&doc_new, error);
561 file_wordexp_destroy(we);
567 strncmp_len(const char *s1, int s1len, const char *s2)
572 strncpy(c, s1, s1len);
574 dbg(0,"'%s' vs '%s'\n", c, s2);
577 ret=strncmp(s1, s2, s1len);
580 return strlen(s2)-s1len;
584 xpointer_value(const char *test, int len, struct xistate *elem, const char **out, int out_len)
587 if (len <= 0 || out_len <= 0) {
590 if (!(strncmp_len(test,len,"name(.)"))) {
591 out[0]=elem->element;
594 if (test[0] == '@') {
596 while (elem->attribute_names[i] && out_len > 0) {
597 if (!strncmp_len(test+1,len-1,elem->attribute_names[i])) {
598 out[ret++]=elem->attribute_values[i];
609 xpointer_test(const char *test, int len, struct xistate *elem)
611 int eq,i,count,vlen,cond_req=1,cond=0;
617 strncpy(test2, test, len);
619 dbg(0,"%s\n", test2);
624 if (c != '\'' && c != '"')
626 eq=strcspn(test, "=");
627 if (eq >= len || test[eq+1] != c)
630 if (eq > 0 && test[eq-1] == '!') {
634 count=xpointer_value(test,vlen,elem,tmp,16);
635 for (i = 0 ; i < count ; i++) {
636 if (!strncmp_len(test+eq+2,len-eq-3, tmp[i]))
639 if (cond == cond_req)
645 xpointer_element_match(const char *xpointer, int len, struct xistate *elem)
647 int start,tlen,tlen2;
651 strncpy(test2, xpointer, len);
653 dbg(0,"%s\n", test2);
655 start=strcspn(xpointer, "[");
658 if (strncmp_len(xpointer, start, elem->element) && (start != 1 || xpointer[0] != '*'))
662 if (xpointer[len-1] != ']')
667 tlen2=strcspn(xpointer+start,"]");
668 if (start + tlen2 > len)
670 if (!xpointer_test(xpointer+start, tlen2, elem))
677 xpointer_xpointer_match(const char *xpointer, int len, struct xistate *first)
681 dbg(2,"%s\n", xpointer);
682 if (xpointer[0] != '/')
690 if (! xpointer_element_match(c, s, first))
695 } while (len > 0 && first);
702 xpointer_match(const char *xpointer, struct xistate *first)
704 char *prefix="xpointer(";
708 len=strlen(xpointer);
709 if (strncmp(xpointer,prefix,strlen(prefix)))
711 if (xpointer[len-1] != ')')
713 return xpointer_xpointer_match(xpointer+strlen(prefix), len-strlen(prefix)-1, first);
718 xi_start_element(GMarkupParseContext *context,
719 const gchar *element_name,
720 const gchar **attribute_names,
721 const gchar **attribute_values,
725 struct xmldocument *doc=user_data;
726 struct xistate *xistate;
728 while (attribute_names[count++*ATTR_DISTANCE]);
729 xistate=g_new0(struct xistate, 1);
730 xistate->element=element_name;
731 xistate->attribute_names=g_new0(const char *, count);
732 xistate->attribute_values=g_new0(const char *, count);
733 for (i = 0 ; i < count ; i++) {
734 if (attribute_names[i*ATTR_DISTANCE] && attribute_values[i*ATTR_DISTANCE]) {
735 xistate->attribute_names[i]=g_strdup(attribute_names[i*ATTR_DISTANCE]);
736 xistate->attribute_values[i]=g_strdup(attribute_values[i*ATTR_DISTANCE]);
739 xistate->parent=doc->last;
742 doc->last->child=xistate;
746 if (doc->active > 0 || xpointer_match(doc->xpointer, doc->first)) {
747 if(!g_ascii_strcasecmp("xi:include", element_name)) {
748 xinclude(context, xistate->attribute_names, xistate->attribute_values, doc, error);
751 start_element(context, element_name, xistate->attribute_names, xistate->attribute_values, doc->user_data, error);
757 * * Reached closing tag of a config element
760 * * @param element name
761 * * @param user_data ptr to xmldocument
762 * * @param error ptr to struct for error information
767 xi_end_element (GMarkupParseContext *context,
768 const gchar *element_name,
772 struct xmldocument *doc=user_data;
773 struct xistate *xistate=doc->last;
775 doc->last=doc->last->parent;
779 doc->last->child=NULL;
780 if (doc->active > 0) {
781 if(!g_ascii_strcasecmp("xi:include", element_name)) {
784 end_element(context, element_name, doc->user_data, error);
787 while (xistate->attribute_names[i]) {
788 g_free((char *)(xistate->attribute_names[i]));
789 g_free((char *)(xistate->attribute_values[i]));
792 g_free(xistate->attribute_names);
793 g_free(xistate->attribute_values);
797 /* Called for character data */
798 /* text is not nul-terminated */
800 xi_text (GMarkupParseContext *context,
811 static const GMarkupParser parser = {
819 * * Parse the contents of the configuration file
821 * * @param document struct holding info about the config file
822 * * @param error info on any errors detected
823 * * @returns boolean TRUE or FALSE
827 parse_file(struct xmldocument *document, xmlerror **error)
829 GMarkupParseContext *context;
830 gchar *contents, *message;
835 dbg(1,"enter filename='%s'\n", document->href);
836 context = g_markup_parse_context_new (&parser, 0, document, NULL);
838 if (!g_file_get_contents (document->href, &contents, &len, error)) {
839 g_markup_parse_context_free (context);
842 document->active=document->xpointer ? 0:1;
843 document->first=NULL;
845 result = g_markup_parse_context_parse (context, contents, len, error);
846 if (!result && error && *error) {
847 g_markup_parse_context_get_position(context, &line, &chr);
848 message=g_strdup_printf("%s at line %d, char %d\n", (*error)->message, line, chr);
849 g_free((*error)->message);
850 (*error)->message=message;
852 g_markup_parse_context_free (context);
854 dbg(1,"return %d\n", result);
860 parse_node(struct xmldocument *document, ezxml_t node)
863 xi_start_element(NULL,node->name, node->attr, node->attr+1, document, NULL);
865 xi_text(NULL,node->txt,strlen(node->txt),document,NULL);
867 parse_node(document, node->child);
868 xi_end_element (NULL,node->name,document,NULL);
874 parse_file(struct xmldocument *document, xmlerror **error)
878 /* BUG workaround: ezxml parse file leaves negative fds unclosed */
879 fd = open(document->href, O_RDONLY, 0);
882 root = ezxml_parse_fd(fd);
886 document->active=document->xpointer ? 0:1;
887 document->first=NULL;
890 parse_node(document, root);
897 * * Load and parse the master config file
899 * * @param filename FQFN of the file
900 * * @param error ptr to error details, if any
901 * * @returns boolean TRUE or FALSE (if error detected)
904 gboolean config_load(const char *filename, xmlerror **error)
906 struct xmldocument document;
907 struct xmlstate *curr=NULL;
910 dbg(1,"enter filename='%s'\n", filename);
911 memset(&document, 0, sizeof(document));
912 document.href=filename;
913 document.user_data=&curr;
914 result=parse_file(&document, error);
915 if (result && curr) {
916 g_set_error(error,G_MARKUP_ERROR,G_MARKUP_ERROR_PARSE, "element '%s' not closed", curr->element);
919 dbg(1,"return %d\n", result);