1 /*
2  * load-library.c
3  *
4  *
5  * Authors:
6  *  Richard Hult <rhult@hem.passagen.se>
7  *  Ricardo Markiewicz <rmarkie@fi.uba.ar>
8  *  Andres de Barbara <adebarbara@fi.uba.ar>
9  *  Marc Lorber <lorber.marc@wanadoo.fr>
10  *
11  * Web page: https://ahoi.io/project/oregano
12  *
13  * Copyright (C) 1999-2001  Richard Hult
14  * Copyright (C) 2003,2006  Ricardo Markiewicz
15  * Copyright (C) 2009-2012  Marc Lorber
16  *
17  * This program is free software; you can redistribute it and/or
18  * modify it under the terms of the GNU General Public License as
19  * published by the Free Software Foundation; either version 2 of the
20  * License, or (at your option) any later version.
21  *
22  * This program is distributed in the hope that it will be useful,
23  * but WITHOUT ANY WARRANTY; without even the implied warranty of
24  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
25  * General Public License for more details.
26  *
27  * You should have received a copy of the GNU General Public
28  * License along with this program; if not, write to the
29  * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
30  * Boston, MA 02110-1301, USA.
31  */
32 
33 #include <goocanvas.h>
34 #include <string.h>
35 #include <glib/gi18n.h>
36 
37 #include "xml-compat.h"
38 #include "oregano.h"
39 #include "xml-helper.h"
40 #include "load-common.h"
41 #include "load-library.h"
42 #include "part-label.h"
43 
44 typedef enum {
45 	PARSE_START,
46 	PARSE_LIBRARY,
47 	PARSE_NAME,
48 	PARSE_AUTHOR,
49 	PARSE_VERSION,
50 	PARSE_SYMBOLS,
51 	PARSE_SYMBOL,
52 	PARSE_SYMBOL_NAME,
53 	PARSE_SYMBOL_OBJECTS,
54 	PARSE_SYMBOL_LINE,
55 	PARSE_SYMBOL_ARC,
56 	PARSE_SYMBOL_TEXT,
57 	PARSE_SYMBOL_CONNECTIONS,
58 	PARSE_SYMBOL_CONNECTION,
59 	PARSE_PARTS,
60 	PARSE_PART,
61 	PARSE_PART_NAME,
62 	PARSE_PART_DESCRIPTION,
63 	PARSE_PART_USESYMBOL,
64 	PARSE_PART_LABELS,
65 	PARSE_PART_LABEL,
66 	PARSE_PART_LABEL_NAME,
67 	PARSE_PART_LABEL_TEXT,
68 	PARSE_PART_LABEL_POS,
69 	PARSE_PART_PROPERTIES,
70 	PARSE_PART_PROPERTY,
71 	PARSE_PART_PROPERTY_NAME,
72 	PARSE_PART_PROPERTY_VALUE,
73 	PARSE_FINISH,
74 	PARSE_UNKNOWN,
75 	PARSE_ERROR
76 } State;
77 
78 typedef struct
79 {
80 	Library *library;
81 
82 	State state;
83 	State prev_state;
84 	gint unknown_depth;
85 	GString *content;
86 
87 	// Temporary placeholder for part
88 	LibraryPart *part;
89 	PartLabel *label;
90 	Property *property;
91 
92 	// Temporary placeholder for symbol
93 	LibrarySymbol *symbol;
94 	Connection *connection;
95 	SymbolObject *object;
96 } ParseState;
97 
98 static xmlEntityPtr get_entity (void *user_data, const xmlChar *name);
99 static void start_document (ParseState *state);
100 static void end_document (ParseState *state);
101 static void start_element (ParseState *state, const xmlChar *name, const xmlChar **attrs);
102 static void end_element (ParseState *state, const xmlChar *name);
103 
104 static void my_characters (ParseState *state, const xmlChar *chars, int len);
105 static void my_warning (void *user_data, const char *msg, ...);
106 static void my_error (void *user_data, const char *msg, ...);
107 static void my_fatal_error (void *user_data, const char *msg, ...);
108 
109 static xmlSAXHandler oreganoSAXParser = {
110     0,                                    // internalSubset
111     0,                                    // isStandalone
112     0,                                    // hasInternalSubset
113     0,                                    // hasExternalSubset
114     0,                                    // resolveEntity
115     (getEntitySAXFunc)get_entity,         // getEntity
116     0,                                    // entityDecl
117     0,                                    // notationDecl
118     0,                                    // attributeDecl
119     0,                                    // elementDecl
120     0,                                    // unparsedEntityDecl
121     0,                                    // setDocumentLocator
122     (startDocumentSAXFunc)start_document, // startDocument
123     (endDocumentSAXFunc)end_document,     // endDocument
124     (startElementSAXFunc)start_element,   // startElement
125     (endElementSAXFunc)end_element,       // endElement
126     0,                                    // reference
127     (charactersSAXFunc)my_characters,     // characters
128     0,                                    // ignorableWhitespace
129     0,                                    // processingInstruction
130     0,                                    // (commentSAXFunc)0,	 comment
131     (warningSAXFunc)my_warning,           // warning
132     (errorSAXFunc)my_error,               // error
133     (fatalErrorSAXFunc)my_fatal_error,    // fatalError
134 };
135 
library_get_symbol(const gchar * symbol_name)136 LibrarySymbol *library_get_symbol (const gchar *symbol_name)
137 {
138 	LibrarySymbol *symbol;
139 	Library *library;
140 	GList *iter;
141 
142 	g_return_val_if_fail (symbol_name != NULL, NULL);
143 
144 	symbol = NULL;
145 	for (iter = oregano.libraries; iter; iter = iter->next) {
146 		library = iter->data;
147 		symbol = g_hash_table_lookup (library->symbol_hash, symbol_name);
148 		if (symbol)
149 			break;
150 	}
151 
152 	if (symbol == NULL) {
153 		g_message (_ ("Could not find the requested symbol: %s\n"), symbol_name);
154 	}
155 
156 	return symbol;
157 }
158 
library_get_part(Library * library,const gchar * part_name)159 LibraryPart *library_get_part (Library *library, const gchar *part_name)
160 {
161 	LibraryPart *part;
162 
163 	g_return_val_if_fail (library != NULL, NULL);
164 	g_return_val_if_fail (part_name != NULL, NULL);
165 
166 	part = g_hash_table_lookup (library->part_hash, part_name);
167 	if (part == NULL) {
168 		g_message (_ ("Could not find the requested part: %s\n"), part_name);
169 	}
170 	return part;
171 }
172 
library_parse_xml_file(const gchar * filename)173 Library *library_parse_xml_file (const gchar *filename)
174 {
175 	Library *library;
176 	ParseState state;
177 
178 	if (!oreganoXmlSAXParseFile (&oreganoSAXParser, &state, filename)) {
179 		g_warning ("Library '%s' not well formed!", filename);
180 	}
181 
182 	if (state.state == PARSE_ERROR) {
183 		library = NULL;
184 	} else {
185 		library = state.library;
186 	}
187 
188 	return library;
189 }
190 
start_document(ParseState * state)191 static void start_document (ParseState *state)
192 {
193 	state->state = PARSE_START;
194 	state->unknown_depth = 0;
195 	state->prev_state = PARSE_UNKNOWN;
196 
197 	state->content = g_string_sized_new (128);
198 	state->part = NULL;
199 	state->symbol = NULL;
200 	state->connection = NULL;
201 	state->label = NULL;
202 	state->property = NULL;
203 
204 	state->library = g_new0 (Library, 1);
205 	state->library->name = NULL;
206 	state->library->author = NULL;
207 	state->library->version = NULL;
208 
209 	state->library->part_hash = g_hash_table_new (g_str_hash, g_str_equal);
210 	state->library->symbol_hash = g_hash_table_new (g_str_hash, g_str_equal);
211 }
212 
end_document(ParseState * state)213 static void end_document (ParseState *state)
214 {
215 	if (state->unknown_depth != 0)
216 		g_warning ("unknown_depth != 0 (%d)", state->unknown_depth);
217 }
218 
start_element(ParseState * state,const xmlChar * xml_name,const xmlChar ** attrs)219 static void start_element (ParseState *state, const xmlChar *xml_name, const xmlChar **attrs)
220 {
221 	const char *name = (const char *)xml_name;
222 
223 	switch (state->state) {
224 	case PARSE_START:
225 		if (strcmp (name, "ogo:library")) {
226 			g_warning ("Expecting 'ogo:library'.  Got '%s'", name);
227 			state->state = PARSE_ERROR;
228 		} else
229 			state->state = PARSE_LIBRARY;
230 		break;
231 
232 	case PARSE_LIBRARY:
233 		if (!strcmp (name, "ogo:author")) {
234 			state->state = PARSE_AUTHOR;
235 			g_string_truncate (state->content, 0);
236 		} else if (!strcmp (name, "ogo:name")) {
237 			state->state = PARSE_NAME;
238 			g_string_truncate (state->content, 0);
239 		} else if (!strcmp (name, "ogo:version")) {
240 			state->state = PARSE_VERSION;
241 			g_string_truncate (state->content, 0);
242 		} else if (!strcmp (name, "ogo:symbols")) {
243 			state->state = PARSE_SYMBOLS;
244 		} else if (!strcmp (name, "ogo:parts")) {
245 			state->state = PARSE_PARTS;
246 		} else {
247 			state->prev_state = state->state;
248 			state->state = PARSE_UNKNOWN;
249 			state->unknown_depth++;
250 		}
251 		break;
252 
253 	case PARSE_SYMBOLS:
254 		if (!strcmp (name, "ogo:symbol")) {
255 			state->state = PARSE_SYMBOL;
256 			state->symbol = g_new0 (LibrarySymbol, 1);
257 		} else {
258 			state->prev_state = state->state;
259 			state->state = PARSE_UNKNOWN;
260 			state->unknown_depth++;
261 		}
262 		break;
263 
264 	case PARSE_SYMBOL:
265 		if (!strcmp (name, "ogo:name")) {
266 			state->state = PARSE_SYMBOL_NAME;
267 			g_string_truncate (state->content, 0);
268 		} else if (!strcmp (name, "ogo:objects")) {
269 			state->state = PARSE_SYMBOL_OBJECTS;
270 		} else if (!strcmp (name, "ogo:connections")) {
271 			state->state = PARSE_SYMBOL_CONNECTIONS;
272 		} else {
273 			state->prev_state = state->state;
274 			state->state = PARSE_UNKNOWN;
275 			state->unknown_depth++;
276 		}
277 		break;
278 	case PARSE_SYMBOL_OBJECTS:
279 		if (!strcmp (name, "ogo:line")) {
280 			state->object = g_new0 (SymbolObject, 1);
281 			state->object->type = SYMBOL_OBJECT_LINE;
282 			state->state = PARSE_SYMBOL_LINE;
283 			g_string_truncate (state->content, 0);
284 		} else if (!strcmp (name, "ogo:arc")) {
285 			state->object = g_new0 (SymbolObject, 1);
286 			state->object->type = SYMBOL_OBJECT_ARC;
287 			state->state = PARSE_SYMBOL_ARC;
288 			g_string_truncate (state->content, 0);
289 		} else if (!strcmp (name, "ogo:text")) {
290 			state->object = g_new0 (SymbolObject, 1);
291 			state->object->type = SYMBOL_OBJECT_TEXT;
292 			state->state = PARSE_SYMBOL_TEXT;
293 			g_string_truncate (state->content, 0);
294 		} else {
295 			state->prev_state = state->state;
296 			state->state = PARSE_UNKNOWN;
297 			state->unknown_depth++;
298 		}
299 		break;
300 
301 	case PARSE_SYMBOL_CONNECTIONS:
302 		if (!strcmp (name, "ogo:connection")) {
303 			state->state = PARSE_SYMBOL_CONNECTION;
304 			state->connection = g_new0 (Connection, 1);
305 			g_string_truncate (state->content, 0);
306 		} else {
307 			state->prev_state = state->state;
308 			state->state = PARSE_UNKNOWN;
309 			state->unknown_depth++;
310 		}
311 		break;
312 
313 	case PARSE_PARTS:
314 		if (!strcmp (name, "ogo:part")) {
315 			state->state = PARSE_PART;
316 			state->part = g_new0 (LibraryPart, 1);
317 			state->part->library = state->library;
318 		} else {
319 			state->prev_state = state->state;
320 			state->state = PARSE_UNKNOWN;
321 			state->unknown_depth++;
322 		}
323 		break;
324 	case PARSE_PART:
325 		if (!strcmp (name, "ogo:name")) {
326 			state->state = PARSE_PART_NAME;
327 			g_string_truncate (state->content, 0);
328 		} else if (!strcmp (name, "ogo:description")) {
329 			state->state = PARSE_PART_DESCRIPTION;
330 			g_string_truncate (state->content, 0);
331 		} else if (!strcmp (name, "ogo:symbol")) {
332 			state->state = PARSE_PART_USESYMBOL;
333 			g_string_truncate (state->content, 0);
334 		} else if (!strcmp (name, "ogo:labels")) {
335 			state->state = PARSE_PART_LABELS;
336 		} else if (!strcmp (name, "ogo:properties")) {
337 			state->state = PARSE_PART_PROPERTIES;
338 		} else {
339 			state->prev_state = state->state;
340 			state->state = PARSE_UNKNOWN;
341 			state->unknown_depth++;
342 		}
343 		break;
344 	case PARSE_PART_LABELS:
345 		if (!strcmp (name, "ogo:label")) {
346 			state->state = PARSE_PART_LABEL;
347 			state->label = g_new0 (PartLabel, 1);
348 		} else {
349 			state->prev_state = state->state;
350 			state->state = PARSE_UNKNOWN;
351 			state->unknown_depth++;
352 		}
353 		break;
354 	case PARSE_PART_LABEL:
355 		if (!strcmp (name, "ogo:name")) {
356 			state->state = PARSE_PART_LABEL_NAME;
357 			g_string_truncate (state->content, 0);
358 		} else if (!strcmp (name, "ogo:text")) {
359 			state->state = PARSE_PART_LABEL_TEXT;
360 			g_string_truncate (state->content, 0);
361 		} else if (!strcmp (name, "ogo:position")) {
362 			state->state = PARSE_PART_LABEL_POS;
363 			g_string_truncate (state->content, 0);
364 		} else {
365 			state->prev_state = state->state;
366 			state->state = PARSE_UNKNOWN;
367 			state->unknown_depth++;
368 		}
369 		break;
370 
371 	case PARSE_PART_PROPERTIES:
372 		if (!strcmp (name, "ogo:property")) {
373 			state->state = PARSE_PART_PROPERTY;
374 			state->property = g_new0 (Property, 1);
375 		} else {
376 			state->prev_state = state->state;
377 			state->state = PARSE_UNKNOWN;
378 			state->unknown_depth++;
379 		}
380 		break;
381 	case PARSE_PART_PROPERTY:
382 		if (!strcmp (name, "ogo:name")) {
383 			state->state = PARSE_PART_PROPERTY_NAME;
384 			g_string_truncate (state->content, 0);
385 		} else if (!strcmp (name, "ogo:value")) {
386 			state->state = PARSE_PART_PROPERTY_VALUE;
387 			g_string_truncate (state->content, 0);
388 		} else {
389 			state->prev_state = state->state;
390 			state->state = PARSE_UNKNOWN;
391 			state->unknown_depth++;
392 		}
393 		break;
394 
395 	case PARSE_SYMBOL_NAME:
396 	case PARSE_SYMBOL_LINE:
397 	case PARSE_SYMBOL_ARC:
398 	case PARSE_SYMBOL_TEXT:
399 	case PARSE_SYMBOL_CONNECTION:
400 	case PARSE_PART_NAME:
401 	case PARSE_PART_DESCRIPTION:
402 	case PARSE_PART_USESYMBOL:
403 	case PARSE_PART_LABEL_NAME:
404 	case PARSE_PART_LABEL_TEXT:
405 	case PARSE_PART_LABEL_POS:
406 	case PARSE_PART_PROPERTY_NAME:
407 	case PARSE_PART_PROPERTY_VALUE:
408 	case PARSE_NAME:
409 	case PARSE_AUTHOR:
410 	case PARSE_VERSION:
411 		// there should be no tags inside these types of tags
412 		g_message ("*** '%s' tag found", name);
413 		state->prev_state = state->state;
414 		state->state = PARSE_UNKNOWN;
415 		state->unknown_depth++;
416 		break;
417 
418 	case PARSE_ERROR:
419 		break;
420 	case PARSE_UNKNOWN:
421 		state->unknown_depth++;
422 		break;
423 	case PARSE_FINISH:
424 		// should not start new elements in this state
425 		g_assert_not_reached ();
426 		break;
427 	}
428 	// g_message("Start element %s (state %s)", name, states[state->state]);
429 }
430 
end_element(ParseState * state,const xmlChar * name)431 static void end_element (ParseState *state, const xmlChar *name)
432 {
433 	switch (state->state) {
434 	case PARSE_UNKNOWN:
435 		state->unknown_depth--;
436 		if (state->unknown_depth == 0)
437 			state->state = state->prev_state;
438 		break;
439 	case PARSE_AUTHOR:
440 		state->library->author = g_strdup (state->content->str);
441 		state->state = PARSE_LIBRARY;
442 		break;
443 	case PARSE_NAME:
444 		state->library->name = g_strdup (state->content->str);
445 		state->state = PARSE_LIBRARY;
446 		break;
447 	case PARSE_VERSION:
448 		state->library->version = g_strdup (state->content->str);
449 		state->state = PARSE_LIBRARY;
450 		break;
451 	case PARSE_SYMBOLS:
452 		state->state = PARSE_LIBRARY;
453 		break;
454 	case PARSE_SYMBOL:
455 		g_hash_table_insert (state->library->symbol_hash, state->symbol->name, state->symbol);
456 		state->state = PARSE_SYMBOLS;
457 		break;
458 	case PARSE_SYMBOL_NAME:
459 		state->symbol->name = g_strdup (state->content->str);
460 		state->state = PARSE_SYMBOL;
461 		break;
462 	case PARSE_SYMBOL_OBJECTS:
463 		state->state = PARSE_SYMBOL;
464 		break;
465 	case PARSE_SYMBOL_LINE: {
466 		int i, j;
467 		gchar *ptr;
468 		gchar **points;
469 
470 		i = sscanf (state->content->str, "%d %n", &state->object->u.uline.spline, &j);
471 		if (i)
472 			ptr = state->content->str + j;
473 		else {
474 			state->object->u.uline.spline = FALSE;
475 			ptr = state->content->str;
476 		}
477 
478 		points = g_strsplit (ptr, "(", 0);
479 
480 		i = 0;
481 		// Count the points.
482 		while (points[i] != NULL) {
483 			i++;
484 		}
485 
486 		// Do not count the first string, which simply is a (.
487 		i--;
488 
489 		// Construct goo canvas points.
490 		state->object->u.uline.line = goo_canvas_points_new (i);
491 		for (j = 0; j < i; j++) {
492 			double x, y;
493 
494 			sscanf (points[j + 1], "%lf %lf)", &x, &y);
495 
496 			state->object->u.uline.line->coords[2 * j] = x;
497 			state->object->u.uline.line->coords[2 * j + 1] = y;
498 		}
499 
500 		g_strfreev (points);
501 		state->symbol->symbol_objects =
502 		    g_slist_prepend (state->symbol->symbol_objects, state->object);
503 		state->state = PARSE_SYMBOL_OBJECTS;
504 	} break;
505 	case PARSE_SYMBOL_ARC:
506 		sscanf (state->content->str, "(%lf %lf)(%lf %lf)", &state->object->u.arc.x1,
507 		        &state->object->u.arc.y1, &state->object->u.arc.x2, &state->object->u.arc.y2);
508 		state->symbol->symbol_objects =
509 		    g_slist_prepend (state->symbol->symbol_objects, state->object);
510 		state->state = PARSE_SYMBOL_OBJECTS;
511 		break;
512 
513 	case PARSE_SYMBOL_TEXT:
514 		sscanf (state->content->str, "(%d %d)%s", &state->object->u.text.x,
515 		        &state->object->u.text.y, state->object->u.text.str);
516 		state->symbol->symbol_objects =
517 		    g_slist_prepend (state->symbol->symbol_objects, state->object);
518 		state->state = PARSE_SYMBOL_OBJECTS;
519 		break;
520 
521 	case PARSE_SYMBOL_CONNECTIONS:
522 		state->state = PARSE_SYMBOL;
523 		state->symbol->connections = g_slist_reverse (state->symbol->connections);
524 		break;
525 	case PARSE_SYMBOL_CONNECTION:
526 		sscanf (state->content->str, "(%lf %lf)", &state->connection->pos.x,
527 		        &state->connection->pos.y);
528 		state->symbol->connections =
529 		    g_slist_prepend (state->symbol->connections, state->connection);
530 		state->state = PARSE_SYMBOL_CONNECTIONS;
531 		break;
532 
533 	case PARSE_PARTS:
534 		state->state = PARSE_LIBRARY;
535 		break;
536 	case PARSE_PART:
537 		g_hash_table_insert (state->library->part_hash, state->part->name, state->part);
538 		state->state = PARSE_PARTS;
539 		break;
540 	case PARSE_PART_NAME:
541 		state->part->name = g_strdup (state->content->str);
542 		state->state = PARSE_PART;
543 		break;
544 	case PARSE_PART_DESCRIPTION:
545 		state->part->description = g_strdup (state->content->str);
546 		state->state = PARSE_PART;
547 		break;
548 	case PARSE_PART_USESYMBOL:
549 		state->part->symbol_name = g_strdup (state->content->str);
550 		state->state = PARSE_PART;
551 		break;
552 	case PARSE_PART_LABELS:
553 		state->state = PARSE_PART;
554 		break;
555 	case PARSE_PART_LABEL:
556 		state->state = PARSE_PART_LABELS;
557 		state->part->labels = g_slist_prepend (state->part->labels, state->label);
558 		break;
559 	case PARSE_PART_LABEL_NAME:
560 		state->label->name = g_strdup (state->content->str);
561 		state->state = PARSE_PART_LABEL;
562 		break;
563 	case PARSE_PART_LABEL_TEXT:
564 		state->label->text = g_strdup (state->content->str);
565 		state->state = PARSE_PART_LABEL;
566 		break;
567 	case PARSE_PART_LABEL_POS:
568 		sscanf (state->content->str, "(%lf %lf)", &state->label->pos.x, &state->label->pos.y);
569 		state->state = PARSE_PART_LABEL;
570 		break;
571 	case PARSE_PART_PROPERTIES:
572 		state->state = PARSE_PART;
573 		break;
574 	case PARSE_PART_PROPERTY:
575 		state->state = PARSE_PART_PROPERTIES;
576 		state->part->properties = g_slist_prepend (state->part->properties, state->property);
577 		break;
578 	case PARSE_PART_PROPERTY_NAME:
579 		state->property->name = g_strdup (state->content->str);
580 		state->state = PARSE_PART_PROPERTY;
581 		break;
582 	case PARSE_PART_PROPERTY_VALUE:
583 		state->property->value = g_strdup (state->content->str);
584 		state->state = PARSE_PART_PROPERTY;
585 		break;
586 
587 	case PARSE_LIBRARY:
588 		// The end of the file.
589 		state->state = PARSE_FINISH;
590 		break;
591 	case PARSE_ERROR:
592 		break;
593 	case PARSE_START:
594 	case PARSE_FINISH:
595 		// There should not be a closing tag in this state.
596 		g_assert_not_reached ();
597 		break;
598 	}
599 	// g_message("End element %s (state %s)", name, states[state->state]);
600 }
601 
my_characters(ParseState * state,const xmlChar * chars,int len)602 static void my_characters (ParseState *state, const xmlChar *chars, int len)
603 {
604 	int i;
605 
606 	if (state->state == PARSE_FINISH || state->state == PARSE_START ||
607 	    state->state == PARSE_PARTS || state->state == PARSE_PART)
608 		return;
609 
610 	for (i = 0; i < len; i++)
611 		g_string_append_c (state->content, chars[i]);
612 }
613 
get_entity(void * user_data,const xmlChar * name)614 static xmlEntityPtr get_entity (void *user_data, const xmlChar *name)
615 {
616 	return xmlGetPredefinedEntity (name);
617 }
618 
my_warning(void * user_data,const char * msg,...)619 static void my_warning (void *user_data, const char *msg, ...)
620 {
621 	va_list args;
622 
623 	va_start (args, msg);
624 	g_logv ("XML", G_LOG_LEVEL_WARNING, msg, args);
625 	va_end (args);
626 }
627 
my_error(void * user_data,const char * msg,...)628 static void my_error (void *user_data, const char *msg, ...)
629 {
630 	va_list args;
631 
632 	va_start (args, msg);
633 	g_logv ("XML", G_LOG_LEVEL_CRITICAL, msg, args);
634 	va_end (args);
635 }
636 
my_fatal_error(void * user_data,const char * msg,...)637 static void my_fatal_error (void *user_data, const char *msg, ...)
638 {
639 	va_list args;
640 
641 	va_start (args, msg);
642 	g_logv ("XML", G_LOG_LEVEL_ERROR, msg, args);
643 	va_end (args);
644 }
645