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