1 /* Dia -- an diagram creation/manipulation program
2 * Copyright (C) 1998 Alexander Larsson
3 *
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation; either version 2 of the License, or
7 * (at your option) any later version.
8 *
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.
13 *
14 * You should have received a copy of the GNU General Public License
15 * along with this program; if not, write to the Free Software
16 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
17 */
18 #include <config.h>
19
20 /* so we get fdopen declared even when compiling with -ansi */
21 #define _POSIX_C_SOURCE 200809L
22 #define _BSD_SOURCE 1 /* to get the prototype for fchmod() */
23
24 #include <sys/stat.h>
25 #ifdef HAVE_UNISTD_H
26 #include <unistd.h>
27 #endif
28 #include <stdlib.h>
29 #include <sys/types.h>
30 #include <stdio.h>
31 #include <fcntl.h>
32 #include <string.h>
33
34 #include <glib.h>
35 #include <glib/gstdio.h> /* g_access() and friends */
36 #include <errno.h>
37
38 #ifndef W_OK
39 #define W_OK 2
40 #endif
41
42 #include "intl.h"
43
44 #include <libxml/tree.h>
45 #include <libxml/parser.h>
46 #include <libxml/xmlmemory.h>
47 #include "dia_xml_libxml.h"
48 #include "dia_xml.h"
49
50 #include "load_save.h"
51 #include "group.h"
52 #include "diagramdata.h"
53 #include "object.h"
54 #include "message.h"
55 #include "preferences.h"
56 #include "diapagelayout.h"
57 #include "autosave.h"
58 #include "newgroup.h"
59
60 #ifdef G_OS_WIN32
61 #include <io.h>
62 #endif
63
64 static void read_connections(GList *objects, xmlNodePtr layer_node,
65 GHashTable *objects_hash);
66 static void GHFuncUnknownObjects(gpointer key,
67 gpointer value,
68 gpointer user_data);
69 static GList *read_objects(xmlNodePtr objects,
70 GHashTable *objects_hash,
71 const char *filename, DiaObject *parent,
72 GHashTable *unknown_objects_hash);
73 static void hash_free_string(gpointer key,
74 gpointer value,
75 gpointer user_data);
76 static xmlNodePtr find_node_named (xmlNodePtr p, const char *name);
77 static gboolean diagram_data_load(const char *filename, DiagramData *data,
78 void* user_data);
79 static gboolean write_objects(GList *objects, xmlNodePtr objects_node,
80 GHashTable *objects_hash, int *obj_nr,
81 const char *filename);
82 static gboolean write_connections(GList *objects, xmlNodePtr layer_node,
83 GHashTable *objects_hash);
84 static xmlDocPtr diagram_data_write_doc(DiagramData *data, const char *filename);
85 static int diagram_data_raw_save(DiagramData *data, const char *filename);
86 static int diagram_data_save(DiagramData *data, const char *filename);
87
88
89 static void
GHFuncUnknownObjects(gpointer key,gpointer value,gpointer user_data)90 GHFuncUnknownObjects(gpointer key,
91 gpointer value,
92 gpointer user_data)
93 {
94 GString* s = (GString*)user_data;
95 g_string_append(s, "\n");
96 g_string_append(s, (gchar*)key);
97 g_free(key);
98 }
99
100 /**
101 * Recursive function to read objects from a specific level in the xml.
102 *
103 * Nowadays there are quite a few of them :
104 * - Layers : a diagram may have different layers, but this function does *not*
105 * add the created objects to them as it does not know on which nesting level it
106 * is called. So the topmost caller must add the returned objects to the layer.
107 * - Groups : groups in itself can have an arbitrary nesting level including other
108 * groups or objects or both of them. A group not containing any objects is by
109 * definition useless. So it is not created. This is to avoid trouble with some older
110 * diagrams which happen to be saved with empty groups.
111 * - Parents : if the parent relations would have been there from the beginning of
112 * Dias file format they probably would have been added as nesting level
113 * themselves. But to maintain forward compatibility (allow to let older versions
114 * of Dia to see as much as possible) they were added all on the same level and
115 * the parent child relation is reconstructed from additional attributes.
116 */
117 static GList *
read_objects(xmlNodePtr objects,GHashTable * objects_hash,const char * filename,DiaObject * parent,GHashTable * unknown_objects_hash)118 read_objects(xmlNodePtr objects,
119 GHashTable *objects_hash,const char *filename, DiaObject *parent,
120 GHashTable *unknown_objects_hash)
121 {
122 GList *list;
123 DiaObjectType *type;
124 DiaObject *obj;
125 ObjectNode obj_node;
126 char *typestr;
127 char *versionstr;
128 char *id;
129 int version;
130 xmlNodePtr child_node;
131
132 list = NULL;
133
134 obj_node = objects->xmlChildrenNode;
135
136 while ( obj_node != NULL) {
137 if (xmlIsBlankNode(obj_node)) {
138 obj_node = obj_node->next;
139 continue;
140 }
141
142 if (!obj_node) break;
143
144 if (xmlStrcmp(obj_node->name, (const xmlChar *)"object")==0) {
145 typestr = (char *) xmlGetProp(obj_node, (const xmlChar *)"type");
146 versionstr = (char *) xmlGetProp(obj_node, (const xmlChar *)"version");
147 id = (char *) xmlGetProp(obj_node, (const xmlChar *)"id");
148
149 version = 0;
150 if (versionstr != NULL) {
151 version = atoi(versionstr);
152 xmlFree(versionstr);
153 }
154
155 type = object_get_type((char *)typestr);
156
157 if (!type) {
158 if (g_utf8_validate (typestr, -1, NULL) &&
159 NULL == g_hash_table_lookup(unknown_objects_hash, typestr))
160 g_hash_table_insert(unknown_objects_hash, g_strdup(typestr), 0);
161 }
162 else
163 {
164 obj = type->ops->load(obj_node, version, filename);
165 list = g_list_append(list, obj);
166
167 if (parent)
168 {
169 obj->parent = parent;
170 parent->children = g_list_append(parent->children, obj);
171 }
172
173 g_hash_table_insert(objects_hash, g_strdup((char *)id), obj);
174
175 child_node = obj_node->children;
176
177 while(child_node)
178 {
179 if (xmlStrcmp(child_node->name, (const xmlChar *)"children") == 0)
180 {
181 GList *children_read = read_objects(child_node, objects_hash, filename, obj, unknown_objects_hash);
182 list = g_list_concat(list, children_read);
183 break;
184 }
185 child_node = child_node->next;
186 }
187 }
188 if (typestr) xmlFree(typestr);
189 if (id) xmlFree (id);
190 } else if (xmlStrcmp(obj_node->name, (const xmlChar *)"group")==0
191 && obj_node->children) {
192 /* don't create empty groups */
193 GList *inner_objects = read_objects(obj_node, objects_hash, filename, NULL, unknown_objects_hash);
194
195 if (inner_objects) {
196 obj = group_create(inner_objects);
197
198 #ifdef USE_NEWGROUP
199 /* Old group objects had objects recursively inside them. Since
200 * objects are now done with parenting, we need to extract those objects,
201 * make a newgroup object, set parent-child relationships, and add
202 * all of them to the diagram.
203 */
204 {
205 DiaObject *newgroup;
206 GList *objects;
207 Point lower_right;
208 type = object_get_type("Misc - NewGroup");
209 lower_right = obj->position;
210 newgroup = type->ops->create(&lower_right,
211 NULL,
212 NULL,
213 NULL);
214 list = g_list_append(list, newgroup);
215
216 for (objects = group_objects(obj); objects != NULL; objects = g_list_next(objects)) {
217 DiaObject *subobj = (DiaObject *) objects->data;
218 list = g_list_append(list, subobj);
219 subobj->parent = newgroup;
220 newgroup->children = g_list_append(newgroup->children, subobj);
221 if (subobj->bounding_box.right > lower_right.x) {
222 lower_right.x = subobj->bounding_box.right;
223 }
224 if (subobj->bounding_box.bottom > lower_right.y) {
225 lower_right.y = subobj->bounding_box.bottom;
226 }
227 }
228 newgroup->ops->move_handle(newgroup, newgroup->handles[7],
229 &lower_right, NULL,
230 HANDLE_MOVE_CREATE_FINAL, 0);
231 /* We've used the info from the old group, destroy it */
232 group_destroy_shallow(obj);
233 }
234 #else
235 list = g_list_append(list, obj);
236 #endif
237 }
238 } else {
239 /* silently ignore other nodes */
240 }
241
242 obj_node = obj_node->next;
243 }
244 return list;
245 }
246
247 static void
read_connections(GList * objects,xmlNodePtr layer_node,GHashTable * objects_hash)248 read_connections(GList *objects, xmlNodePtr layer_node,
249 GHashTable *objects_hash)
250 {
251 ObjectNode obj_node;
252 GList *list;
253 xmlNodePtr connections;
254 xmlNodePtr connection;
255 char *handlestr;
256 char *tostr;
257 char *connstr;
258 int handle, conn;
259 DiaObject *to;
260
261 list = objects;
262 obj_node = layer_node->xmlChildrenNode;
263 while ((list != NULL) && (obj_node != NULL)) {
264 DiaObject *obj = (DiaObject *) list->data;
265
266 /* the obj and there node must stay in sync to properly setup connections */
267 while (obj_node && (xmlIsBlankNode(obj_node) || XML_COMMENT_NODE == obj_node->type))
268 obj_node = obj_node->next;
269 if (!obj_node) break;
270
271 if IS_GROUP(obj) {
272 read_connections(group_objects(obj), obj_node, objects_hash);
273 } else {
274 gboolean broken = FALSE;
275 /* an invalid bounding box is a good sign for some need of corrections */
276 gboolean wants_update = obj->bounding_box.right >= obj->bounding_box.left
277 || obj->bounding_box.top >= obj->bounding_box.bottom;
278 connections = obj_node->xmlChildrenNode;
279 while ((connections!=NULL) &&
280 (xmlStrcmp(connections->name, (const xmlChar *)"connections")!=0))
281 connections = connections->next;
282 if (connections != NULL) {
283 connection = connections->xmlChildrenNode;
284 while (connection != NULL) {
285 char *donestr;
286 if (xmlIsBlankNode(connection)) {
287 connection = connection->next;
288 continue;
289 }
290 handlestr = (char * )xmlGetProp(connection, (const xmlChar *)"handle");
291 tostr = (char *) xmlGetProp(connection, (const xmlChar *)"to");
292 connstr = (char *) xmlGetProp(connection, (const xmlChar *)"connection");
293
294 handle = atoi(handlestr);
295 conn = strtol(connstr, &donestr, 10);
296 if (*donestr != '\0') { /* Didn't convert it all -- use string */
297 conn = -1;
298 }
299
300 to = g_hash_table_lookup(objects_hash, tostr);
301
302 if (to == NULL) {
303 message_error(_("Error loading diagram.\n"
304 "Linked object not found in document."));
305 broken = TRUE;
306 } else if (handle < 0 || handle >= obj->num_handles) {
307 message_error(_("Error loading diagram.\n"
308 "connection handle %d does not exist on '%s'."),
309 handle, to->type->name);
310 broken = TRUE;
311 } else {
312 if (conn == -1) { /* Find named connpoint */
313 int i;
314 for (i = 0; i < to->num_connections; i++) {
315 if (to->connections[i]->name != NULL &&
316 strcmp(to->connections[i]->name, connstr) == 0) {
317 conn = i;
318 break;
319 }
320 }
321 }
322 if (conn >= 0 && conn < to->num_connections) {
323 object_connect(obj, obj->handles[handle],
324 to->connections[conn]);
325 /* force an update on the connection, helpful with (incomplete) generated files */
326 if (wants_update) {
327 obj->handles[handle]->pos =
328 to->connections[conn]->last_pos = to->connections[conn]->pos;
329 #if 0
330 obj->ops->move_handle(obj, obj->handles[handle], &to->connections[conn]->pos,
331 to->connections[conn], HANDLE_MOVE_CONNECTED,0);
332 #endif
333 }
334 } else {
335 message_error(_("Error loading diagram.\n"
336 "connection point %d does not exist on '%s'."),
337 conn, to->type->name);
338 broken = TRUE;
339 }
340 }
341
342 if (handlestr) xmlFree(handlestr);
343 if (tostr) xmlFree(tostr);
344 if (connstr) xmlFree(connstr);
345
346 connection = connection->next;
347 }
348 /* Fix positions of the connection object for (de)generated files.
349 * Only done for the last point connected otherwise the intermediate posisitions
350 * may screw the auto-routing algorithm.
351 */
352 if (!broken && obj && obj->ops->set_props && wants_update) {
353 /* called for it's side-effect of update_data */
354 obj->ops->move(obj,&obj->position);
355
356 for (handle = 0; handle < obj->num_handles; ++handle) {
357 if (obj->handles[handle]->connected_to)
358 obj->ops->move_handle(obj, obj->handles[handle], &obj->handles[handle]->pos,
359 obj->handles[handle]->connected_to, HANDLE_MOVE_CONNECTED,0);
360 }
361 }
362 }
363 }
364
365 /* Now set up parent relationships. */
366 connections = obj_node->xmlChildrenNode;
367 while ((connections!=NULL) &&
368 (xmlStrcmp(connections->name, (const xmlChar *)"childnode")!=0))
369 connections = connections->next;
370 if (connections != NULL) {
371 tostr = (char *)xmlGetProp(connections, (const xmlChar *)"parent");
372 if (tostr) {
373 obj->parent = g_hash_table_lookup(objects_hash, tostr);
374 if (obj->parent == NULL) {
375 message_error(_("Can't find parent %s of %s object\n"),
376 tostr, obj->type->name);
377 } else {
378 obj->parent->children = g_list_prepend(obj->parent->children, obj);
379 }
380 }
381 }
382
383 list = g_list_next(list);
384 obj_node = obj_node->next;
385 }
386 }
387
388 static void
hash_free_string(gpointer key,gpointer value,gpointer user_data)389 hash_free_string(gpointer key,
390 gpointer value,
391 gpointer user_data)
392 {
393 g_free(key);
394 }
395
396 static xmlNodePtr
find_node_named(xmlNodePtr p,const char * name)397 find_node_named (xmlNodePtr p, const char *name)
398 {
399 while (p && 0 != xmlStrcmp(p->name, (xmlChar *)name))
400 p = p->next;
401 return p;
402 }
403
404 static gboolean
diagram_data_load(const char * filename,DiagramData * data,void * user_data)405 diagram_data_load(const char *filename, DiagramData *data, void* user_data)
406 {
407 GHashTable *objects_hash;
408 int fd;
409 GList *list;
410 xmlDocPtr doc;
411 xmlNodePtr root;
412 xmlNodePtr diagramdata;
413 xmlNodePtr paperinfo, gridinfo, guideinfo;
414 xmlNodePtr layer_node;
415 AttributeNode attr;
416 Layer *layer;
417 xmlNsPtr namespace;
418 gchar firstchar;
419 Diagram *diagram = DIA_IS_DIAGRAM (data) ? DIA_DIAGRAM (data) : NULL;
420 Layer *active_layer = NULL;
421 GHashTable* unknown_objects_hash = g_hash_table_new(g_str_hash, g_str_equal);
422
423
424 if (g_file_test (filename, G_FILE_TEST_IS_DIR)) {
425 message_error(_("You must specify a file, not a directory.\n"));
426 return FALSE;
427 }
428
429 fd = g_open(filename, O_RDONLY, 0);
430
431 if (fd==-1) {
432 message_error(_("Couldn't open: '%s' for reading.\n"),
433 dia_message_filename(filename));
434 return FALSE;
435 }
436
437 if (read(fd, &firstchar, 1)) {
438 data->is_compressed = (firstchar != '<');
439 } else {
440 /* Couldn't read a single char? Set to default. */
441 data->is_compressed = prefs.new_diagram.compress_save;
442 }
443
444 /* Note that this closing and opening means we can't read from a pipe */
445 close(fd);
446
447 doc = xmlDiaParseFile(filename);
448
449 if (doc == NULL){
450 message_error(_("Error loading diagram %s.\nUnknown file type."),
451 dia_message_filename(filename));
452 return FALSE;
453 }
454
455 root = doc->xmlRootNode;
456 /* skip comments */
457 while (root && (root->type != XML_ELEMENT_NODE))
458 root = root->next;
459 if (root == NULL) {
460 message_error(_("Error loading diagram %s.\nUnknown file type."),
461 dia_message_filename(filename));
462 xmlFreeDoc (doc);
463 return FALSE;
464 }
465
466 namespace = xmlSearchNs(doc, root, (const xmlChar *)"dia");
467 if (xmlStrcmp (root->name, (const xmlChar *)"diagram") || (namespace == NULL)){
468 message_error(_("Error loading diagram %s.\nNot a Dia file."),
469 dia_message_filename(filename));
470 xmlFreeDoc (doc);
471 return FALSE;
472 }
473
474 /* Destroy the default layer: */
475 g_ptr_array_remove(data->layers, data->active_layer);
476 layer_destroy(data->active_layer);
477
478 diagramdata =
479 find_node_named (root->xmlChildrenNode, "diagramdata");
480
481 /* Read in diagram data: */
482 data->bg_color = prefs.new_diagram.bg_color;
483 attr = composite_find_attribute(diagramdata, "background");
484 if (attr != NULL)
485 data_color(attribute_first_data(attr), &data->bg_color);
486
487 if (diagram) {
488 diagram->pagebreak_color = prefs.new_diagram.pagebreak_color;
489 attr = composite_find_attribute(diagramdata, "pagebreak");
490 if (attr != NULL)
491 data_color(attribute_first_data(attr), &diagram->pagebreak_color);
492 }
493 /* load paper information from diagramdata section */
494 attr = composite_find_attribute(diagramdata, "paper");
495 if (attr != NULL) {
496 paperinfo = attribute_first_data(attr);
497
498 attr = composite_find_attribute(paperinfo, "name");
499 if (attr != NULL) {
500 g_free(data->paper.name);
501 data->paper.name = data_string(attribute_first_data(attr));
502 }
503 if (data->paper.name == NULL || data->paper.name[0] == '\0') {
504 data->paper.name = g_strdup(prefs.new_diagram.papertype);
505 }
506 /* set default margins for paper size ... */
507 dia_page_layout_get_default_margins(data->paper.name,
508 &data->paper.tmargin,
509 &data->paper.bmargin,
510 &data->paper.lmargin,
511 &data->paper.rmargin);
512
513 attr = composite_find_attribute(paperinfo, "tmargin");
514 if (attr != NULL)
515 data->paper.tmargin = data_real(attribute_first_data(attr));
516 attr = composite_find_attribute(paperinfo, "bmargin");
517 if (attr != NULL)
518 data->paper.bmargin = data_real(attribute_first_data(attr));
519 attr = composite_find_attribute(paperinfo, "lmargin");
520 if (attr != NULL)
521 data->paper.lmargin = data_real(attribute_first_data(attr));
522 attr = composite_find_attribute(paperinfo, "rmargin");
523 if (attr != NULL)
524 data->paper.rmargin = data_real(attribute_first_data(attr));
525
526 attr = composite_find_attribute(paperinfo, "is_portrait");
527 data->paper.is_portrait = TRUE;
528 if (attr != NULL)
529 data->paper.is_portrait = data_boolean(attribute_first_data(attr));
530
531 attr = composite_find_attribute(paperinfo, "scaling");
532 data->paper.scaling = 1.0;
533 if (attr != NULL)
534 data->paper.scaling = data_real(attribute_first_data(attr));
535
536 attr = composite_find_attribute(paperinfo, "fitto");
537 data->paper.fitto = FALSE;
538 if (attr != NULL)
539 data->paper.fitto = data_boolean(attribute_first_data(attr));
540
541 attr = composite_find_attribute(paperinfo, "fitwidth");
542 data->paper.fitwidth = 1;
543 if (attr != NULL)
544 data->paper.fitwidth = data_int(attribute_first_data(attr));
545
546 attr = composite_find_attribute(paperinfo, "fitheight");
547 data->paper.fitheight = 1;
548 if (attr != NULL)
549 data->paper.fitheight = data_int(attribute_first_data(attr));
550
551 /* calculate effective width/height */
552 dia_page_layout_get_paper_size(data->paper.name,
553 &data->paper.width,
554 &data->paper.height);
555 if (!data->paper.is_portrait) {
556 gfloat tmp = data->paper.width;
557
558 data->paper.width = data->paper.height;
559 data->paper.height = tmp;
560 }
561 data->paper.width -= data->paper.lmargin;
562 data->paper.width -= data->paper.rmargin;
563 data->paper.height -= data->paper.tmargin;
564 data->paper.height -= data->paper.bmargin;
565 data->paper.width /= data->paper.scaling;
566 data->paper.height /= data->paper.scaling;
567 }
568
569 if (diagram) {
570 attr = composite_find_attribute(diagramdata, "grid");
571 if (attr != NULL) {
572 gridinfo = attribute_first_data(attr);
573
574 attr = composite_find_attribute(gridinfo, "width_x");
575 if (attr != NULL)
576 diagram->grid.width_x = data_real(attribute_first_data(attr));
577 attr = composite_find_attribute(gridinfo, "width_y");
578 if (attr != NULL)
579 diagram->grid.width_y = data_real(attribute_first_data(attr));
580 attr = composite_find_attribute(gridinfo, "visible_x");
581 if (attr != NULL)
582 diagram->grid.visible_x = data_int(attribute_first_data(attr));
583 attr = composite_find_attribute(gridinfo, "visible_y");
584 if (attr != NULL)
585 diagram->grid.visible_y = data_int(attribute_first_data(attr));
586
587 diagram->grid.colour = prefs.new_diagram.grid_color;
588 attr = composite_find_attribute(diagramdata, "color");
589 if (attr != NULL)
590 data_color(attribute_first_data(attr), &diagram->grid.colour);
591 }
592 }
593 if (diagram) {
594 attr = composite_find_attribute(diagramdata, "guides");
595 if (attr != NULL) {
596 guint i;
597 DataNode guide;
598
599 guideinfo = attribute_first_data(attr);
600
601 attr = composite_find_attribute(guideinfo, "hguides");
602 if (attr != NULL) {
603 diagram->guides.nhguides = attribute_num_data(attr);
604 g_free(diagram->guides.hguides);
605 diagram->guides.hguides = g_new(real, diagram->guides.nhguides);
606
607 guide = attribute_first_data(attr);
608 for (i = 0; i < diagram->guides.nhguides; i++, guide = data_next(guide))
609 diagram->guides.hguides[i] = data_real(guide);
610 }
611 attr = composite_find_attribute(guideinfo, "vguides");
612 if (attr != NULL) {
613 diagram->guides.nvguides = attribute_num_data(attr);
614 g_free(diagram->guides.vguides);
615 diagram->guides.vguides = g_new(real, diagram->guides.nvguides);
616
617 guide = attribute_first_data(attr);
618 for (i = 0; i < diagram->guides.nvguides; i++, guide = data_next(guide))
619 diagram->guides.vguides[i] = data_real(guide);
620 }
621 }
622 }
623
624 /* Read in all layers: */
625 layer_node =
626 find_node_named (root->xmlChildrenNode, "layer");
627
628 objects_hash = g_hash_table_new(g_str_hash, g_str_equal);
629
630 while (layer_node != NULL) {
631 gchar *name;
632 char *visible;
633 char *active;
634
635 if (xmlIsBlankNode(layer_node)) {
636 layer_node = layer_node->next;
637 continue;
638 }
639
640 if (!layer_node) break;
641
642 name = (char *)xmlGetProp(layer_node, (const xmlChar *)"name");
643 if (!name) break; /* name is mandatory */
644
645 layer = new_layer(g_strdup(name), data);
646 if (name) xmlFree(name);
647
648 layer->visible = FALSE;
649 visible = (char *)xmlGetProp(layer_node, (const xmlChar *)"visible");
650 if (visible) {
651 if (strcmp(visible, "true")==0)
652 layer->visible = TRUE;
653 xmlFree(visible);
654 }
655 /* Read in all objects: */
656
657 list = read_objects(layer_node, objects_hash, filename, NULL, unknown_objects_hash);
658 layer_add_objects (layer, list);
659 read_connections( list, layer_node, objects_hash);
660
661 data_add_layer(data, layer);
662
663 active = (char *)xmlGetProp(layer_node, (const xmlChar *)"active");
664 if (active) {
665 if (strcmp(active, "true")==0)
666 active_layer = layer;
667 xmlFree(active);
668 }
669
670 layer_node = layer_node->next;
671 }
672
673 if (!active_layer)
674 data->active_layer = (Layer *) g_ptr_array_index(data->layers, 0);
675 else
676 data_set_active_layer(data, active_layer);
677
678 xmlFreeDoc(doc);
679
680 g_hash_table_foreach(objects_hash, hash_free_string, NULL);
681
682 g_hash_table_destroy(objects_hash);
683
684 if (data->layers->len < 1) {
685 message_error (_("Error loading diagram:\n%s.\n"
686 "A valid Dia file defines at least one layer."),
687 dia_message_filename(filename));
688 return FALSE;
689 } else if (0 < g_hash_table_size(unknown_objects_hash)) {
690 GString* unknown_str = g_string_new("Unknown types while reading diagram file");
691
692 /* show all the unknown types in one message */
693 g_hash_table_foreach(unknown_objects_hash,
694 GHFuncUnknownObjects,
695 unknown_str);
696 message_warning("%s", unknown_str->str);
697 g_string_free(unknown_str, TRUE);
698 }
699 g_hash_table_destroy(unknown_objects_hash);
700
701 return TRUE;
702 }
703
704 static gboolean
write_objects(GList * objects,xmlNodePtr objects_node,GHashTable * objects_hash,int * obj_nr,const char * filename)705 write_objects(GList *objects, xmlNodePtr objects_node,
706 GHashTable *objects_hash, int *obj_nr, const char *filename)
707 {
708 char buffer[31];
709 ObjectNode obj_node;
710 xmlNodePtr group_node;
711 GList *list;
712
713 list = objects;
714 while (list != NULL) {
715 DiaObject *obj = (DiaObject *) list->data;
716
717 if (g_hash_table_lookup(objects_hash, obj))
718 {
719 list = g_list_next(list);
720 continue;
721 }
722
723 if (IS_GROUP(obj) && group_objects(obj) != NULL) {
724 group_node = xmlNewChild(objects_node, NULL, (const xmlChar *)"group", NULL);
725 write_objects(group_objects(obj), group_node,
726 objects_hash, obj_nr, filename);
727 } else {
728 obj_node = xmlNewChild(objects_node, NULL, (const xmlChar *)"object", NULL);
729
730 xmlSetProp(obj_node, (const xmlChar *)"type", (xmlChar *)obj->type->name);
731 g_snprintf(buffer, 30, "%d", obj->type->version);
732 xmlSetProp(obj_node, (const xmlChar *)"version", (xmlChar *)buffer);
733
734 g_snprintf(buffer, 30, "O%d", *obj_nr);
735 xmlSetProp(obj_node, (const xmlChar *)"id", (xmlChar *)buffer);
736
737 (*obj->type->ops->save)(obj, obj_node, filename);
738
739 /* Add object -> obj_nr to hash table */
740 g_hash_table_insert(objects_hash, obj, GINT_TO_POINTER(*obj_nr));
741 (*obj_nr)++;
742
743 /*
744 if (object_flags_set(obj, DIA_OBJECT_CAN_PARENT) && obj->children)
745 {
746 int res;
747 xmlNodePtr parent_node;
748 parent_node = xmlNewChild(obj_node, NULL, "children", NULL);
749 res = write_objects(obj->children, parent_node, objects_hash, obj_nr, filename);
750 if (!res) return FALSE;
751 }
752 */
753 }
754
755 list = g_list_next(list);
756 }
757 return TRUE;
758 }
759
760 /* Parents broke assumption that both objects and xml nodes are laid out
761 * linearly.
762 */
763
764 static gboolean
write_connections(GList * objects,xmlNodePtr layer_node,GHashTable * objects_hash)765 write_connections(GList *objects, xmlNodePtr layer_node,
766 GHashTable *objects_hash)
767 {
768 ObjectNode obj_node;
769 GList *list;
770 xmlNodePtr connections;
771 xmlNodePtr connection;
772 char buffer[31];
773 int i;
774
775 list = objects;
776 obj_node = layer_node->xmlChildrenNode;
777 while ((list != NULL) && (obj_node != NULL)) {
778 DiaObject *obj = (DiaObject *) list->data;
779
780 while (obj_node && xmlIsBlankNode(obj_node)) obj_node = obj_node->next;
781 if (!obj_node) break;
782
783 if IS_GROUP(obj) {
784 write_connections(group_objects(obj), obj_node, objects_hash);
785 } else {
786 connections = NULL;
787
788 for (i=0;i<obj->num_handles;i++) {
789 ConnectionPoint *con_point;
790 Handle *handle;
791
792 handle = obj->handles[i];
793 con_point = handle->connected_to;
794
795 if ( con_point != NULL ) {
796 DiaObject *other_obj;
797 int con_point_nr;
798
799 other_obj = con_point->object;
800
801 con_point_nr=0;
802 while (other_obj->connections[con_point_nr] != con_point) {
803 con_point_nr++;
804 if (con_point_nr>=other_obj->num_connections) {
805 message_error("Internal error saving diagram\n con_point_nr >= other_obj->num_connections\n");
806 return FALSE;
807 }
808 }
809
810 if (connections == NULL)
811 connections = xmlNewChild(obj_node, NULL, (const xmlChar *)"connections", NULL);
812
813 connection = xmlNewChild(connections, NULL, (const xmlChar *)"connection", NULL);
814 /* from what handle on this object*/
815 g_snprintf(buffer, 30, "%d", i);
816 xmlSetProp(connection, (const xmlChar *)"handle", (xmlChar *)buffer);
817 /* to what object */
818 g_snprintf(buffer, 30, "O%d",
819 GPOINTER_TO_INT(g_hash_table_lookup(objects_hash,
820 other_obj)));
821
822 xmlSetProp(connection, (const xmlChar *)"to", (xmlChar *) buffer);
823 /* to what connection_point on that object */
824 if (other_obj->connections[con_point_nr]->name != NULL) {
825 g_snprintf(buffer, 30, "%s", other_obj->connections[con_point_nr]->name);
826 } else {
827 g_snprintf(buffer, 30, "%d", con_point_nr);
828 }
829 xmlSetProp(connection, (const xmlChar *)"connection", (xmlChar *) buffer);
830 }
831 }
832 }
833
834 if (obj->parent) {
835 DiaObject *other_obj = obj->parent;
836 xmlNodePtr parent;
837 g_snprintf(buffer, 30, "O%d",
838 GPOINTER_TO_INT(g_hash_table_lookup(objects_hash, other_obj)));
839 parent = xmlNewChild(obj_node, NULL, (const xmlChar *)"childnode", NULL);
840 xmlSetProp(parent, (const xmlChar *)"parent", (xmlChar *)buffer);
841 }
842
843 list = g_list_next(list);
844 obj_node = obj_node->next;
845 }
846 return TRUE;
847 }
848
849 /* Filename seems to be junk, but is passed on to objects */
850 static xmlDocPtr
diagram_data_write_doc(DiagramData * data,const char * filename)851 diagram_data_write_doc(DiagramData *data, const char *filename)
852 {
853 xmlDocPtr doc;
854 xmlNodePtr tree;
855 xmlNodePtr pageinfo, gridinfo, guideinfo;
856 xmlNodePtr layer_node;
857 GHashTable *objects_hash;
858 gboolean res;
859 int obj_nr;
860 guint i;
861 Layer *layer;
862 AttributeNode attr;
863 xmlNs *name_space;
864 Diagram *diagram = DIA_IS_DIAGRAM (data) ? DIA_DIAGRAM (data) : NULL;
865
866 doc = xmlNewDoc((const xmlChar *)"1.0");
867 doc->encoding = xmlStrdup((const xmlChar *)"UTF-8");
868 doc->xmlRootNode = xmlNewDocNode(doc, NULL, (const xmlChar *)"diagram", NULL);
869
870 name_space = xmlNewNs(doc->xmlRootNode,
871 (const xmlChar *)DIA_XML_NAME_SPACE_BASE,
872 (const xmlChar *)"dia");
873 xmlSetNs(doc->xmlRootNode, name_space);
874
875 tree = xmlNewChild(doc->xmlRootNode, name_space, (const xmlChar *)"diagramdata", NULL);
876
877 attr = new_attribute((ObjectNode)tree, "background");
878 data_add_color(attr, &data->bg_color);
879
880 if (diagram) {
881 attr = new_attribute((ObjectNode)tree, "pagebreak");
882 data_add_color(attr, &diagram->pagebreak_color);
883 }
884 attr = new_attribute((ObjectNode)tree, "paper");
885 pageinfo = data_add_composite(attr, "paper");
886 data_add_string(composite_add_attribute(pageinfo, "name"),
887 data->paper.name);
888 data_add_real(composite_add_attribute(pageinfo, "tmargin"),
889 data->paper.tmargin);
890 data_add_real(composite_add_attribute(pageinfo, "bmargin"),
891 data->paper.bmargin);
892 data_add_real(composite_add_attribute(pageinfo, "lmargin"),
893 data->paper.lmargin);
894 data_add_real(composite_add_attribute(pageinfo, "rmargin"),
895 data->paper.rmargin);
896 data_add_boolean(composite_add_attribute(pageinfo, "is_portrait"),
897 data->paper.is_portrait);
898 data_add_real(composite_add_attribute(pageinfo, "scaling"),
899 data->paper.scaling);
900 data_add_boolean(composite_add_attribute(pageinfo, "fitto"),
901 data->paper.fitto);
902 if (data->paper.fitto) {
903 data_add_int(composite_add_attribute(pageinfo, "fitwidth"),
904 data->paper.fitwidth);
905 data_add_int(composite_add_attribute(pageinfo, "fitheight"),
906 data->paper.fitheight);
907 }
908
909 if (diagram) {
910 attr = new_attribute((ObjectNode)tree, "grid");
911 gridinfo = data_add_composite(attr, "grid");
912 data_add_real(composite_add_attribute(gridinfo, "width_x"),
913 diagram->grid.width_x);
914 data_add_real(composite_add_attribute(gridinfo, "width_y"),
915 diagram->grid.width_y);
916 data_add_int(composite_add_attribute(gridinfo, "visible_x"),
917 diagram->grid.visible_x);
918 data_add_int(composite_add_attribute(gridinfo, "visible_y"),
919 diagram->grid.visible_y);
920 attr = new_attribute((ObjectNode)tree, "color");
921 data_add_composite(gridinfo, "color");
922 data_add_color(attr, &diagram->grid.colour);
923
924 attr = new_attribute((ObjectNode)tree, "guides");
925 guideinfo = data_add_composite(attr, "guides");
926 attr = composite_add_attribute(guideinfo, "hguides");
927 for (i = 0; i < diagram->guides.nhguides; i++)
928 data_add_real(attr, diagram->guides.hguides[i]);
929 attr = composite_add_attribute(guideinfo, "vguides");
930 for (i = 0; i < diagram->guides.nvguides; i++)
931 data_add_real(attr, diagram->guides.vguides[i]);
932 }
933
934 objects_hash = g_hash_table_new(g_direct_hash, g_direct_equal);
935
936 obj_nr = 0;
937
938 for (i = 0; i < data->layers->len; i++) {
939 layer_node = xmlNewChild(doc->xmlRootNode, name_space, (const xmlChar *)"layer", NULL);
940 layer = (Layer *) g_ptr_array_index(data->layers, i);
941 xmlSetProp(layer_node, (const xmlChar *)"name", (xmlChar *)layer->name);
942
943 if (layer->visible)
944 xmlSetProp(layer_node, (const xmlChar *)"visible", (const xmlChar *)"true");
945 else
946 xmlSetProp(layer_node, (const xmlChar *)"visible", (const xmlChar *)"false");
947
948 if (layer == data->active_layer)
949 xmlSetProp(layer_node, (const xmlChar *)"active", (const xmlChar *)"true");
950
951 write_objects(layer->objects, layer_node,
952 objects_hash, &obj_nr, filename);
953
954 res = write_connections(layer->objects, layer_node, objects_hash);
955 /* Why do we bail out like this? It leaks! */
956 if (!res)
957 return NULL;
958 }
959 g_hash_table_destroy(objects_hash);
960
961 if (data->is_compressed)
962 xmlSetDocCompressMode(doc, 9);
963 else
964 xmlSetDocCompressMode(doc, 0);
965
966 return doc;
967 }
968
969 /** This tries to save the diagram into a file, without any backup
970 * Returns >= 0 on success.
971 * Only for internal use. */
972 static int
diagram_data_raw_save(DiagramData * data,const char * filename)973 diagram_data_raw_save(DiagramData *data, const char *filename)
974 {
975 xmlDocPtr doc;
976 int ret;
977
978 doc = diagram_data_write_doc(data, filename);
979
980 ret = xmlDiaSaveFile (filename, doc);
981 xmlFreeDoc(doc);
982
983 return ret;
984 }
985
986 /** This saves the diagram, using a backup in case of failure.
987 * @param data
988 * @param filename
989 * @returns TRUE on successfull save, FALSE otherwise. If a failure is
990 * indicated, an error message will already have been given to the user.
991 */
992 static int
diagram_data_save(DiagramData * data,const char * user_filename)993 diagram_data_save(DiagramData *data, const char *user_filename)
994 {
995 FILE *file;
996 char *bakname=NULL,*tmpname=NULL,*dirname=NULL,*p;
997 char *filename = (char *)user_filename;
998 int mode,_umask;
999 int fildes;
1000 int ret = 0;
1001
1002 /* Once we depend on GTK 2.8+, we can use these tests. */
1003 #if GLIB_CHECK_VERSION(2,8,0) && !defined G_OS_WIN32
1004 /* Check that we're allowed to write to the target file at all. */
1005 /* not going to work with 'My Docments' - read-only but still useable, see bug #504469 */
1006 if ( g_file_test(filename, G_FILE_TEST_EXISTS)
1007 && g_access(filename, W_OK) != 0) {
1008 message_error(_("Not allowed to write to output file %s\n"),
1009 dia_message_filename(filename));
1010 goto CLEANUP;
1011 }
1012 #endif
1013
1014 if (g_file_test(user_filename, G_FILE_TEST_IS_SYMLINK)) {
1015 GError *error = NULL;
1016 filename = g_file_read_link(user_filename, &error);
1017 if (!filename) {
1018 message_error("%s", error->message);
1019 g_error_free(error);
1020 goto CLEANUP;
1021 }
1022 }
1023
1024 /* build the temporary and backup file names */
1025 dirname = g_strdup(filename);
1026 p = strrchr((char *)dirname,G_DIR_SEPARATOR);
1027 if (p) {
1028 *(p+1) = 0;
1029 } else {
1030 g_free(dirname);
1031 dirname = g_strdup("." G_DIR_SEPARATOR_S);
1032 }
1033 tmpname = g_strconcat(dirname,"__diaXXXXXX",NULL);
1034 bakname = g_strconcat(filename,"~",NULL);
1035
1036 #if GLIB_CHECK_VERSION(2,8,0) && !defined G_OS_WIN32
1037 /* Check that we can create the other files */
1038 if ( g_file_test(dirname, G_FILE_TEST_EXISTS)
1039 && g_access(dirname, W_OK) != 0) {
1040 message_error(_("Not allowed to write temporary files in %s\n"),
1041 dia_message_filename(dirname));
1042 goto CLEANUP;
1043 }
1044 #endif
1045
1046 /* open a temporary name, and fix the modes to match what fopen() would have
1047 done (mkstemp() is (rightly so) a bit paranoid for what we do). */
1048 fildes = g_mkstemp(tmpname);
1049 /* should not be necessary anymore on *NIXas well, because we are using g_mkstemp ? */
1050 _umask = umask(0); umask(_umask);
1051 mode = 0666 & ~_umask;
1052 #ifndef G_OS_WIN32
1053 ret = fchmod(fildes,mode);
1054 #else
1055 ret = 0; /* less paranoia on windoze */
1056 #endif
1057 file = fdopen(fildes,"wb");
1058
1059 /* Now write the data in the temporary file name. */
1060
1061 if (file==NULL) {
1062 message_error(_("Can't open output file %s: %s\n"),
1063 dia_message_filename(tmpname), strerror(errno));
1064 goto CLEANUP;
1065 }
1066 fclose(file);
1067
1068 ret = diagram_data_raw_save(data, tmpname);
1069
1070 if (ret < 0) {
1071 /* Save failed; we clean our stuff up, without touching the file named
1072 "filename" if it existed. */
1073 message_error(_("Internal error %d writing file %s\n"),
1074 ret, dia_message_filename(tmpname));
1075 g_unlink(tmpname);
1076 goto CLEANUP;
1077 }
1078 /* save succeeded. We kill the old backup file, move the old file into
1079 backup, and the temp file into the new saved file. */
1080 g_unlink(bakname);
1081 g_rename(filename,bakname);
1082 ret = g_rename(tmpname,filename);
1083 if (ret < 0) {
1084 message_error(_("Can't rename %s to final output file %s: %s\n"),
1085 dia_message_filename(filename),
1086 dia_message_filename(filename), strerror(errno));
1087 }
1088 CLEANUP:
1089 if (filename != user_filename)
1090 g_free(filename);
1091 g_free(tmpname);
1092 g_free(dirname);
1093 g_free(bakname);
1094 return (ret?FALSE:TRUE);
1095 }
1096
1097 int
diagram_save(Diagram * dia,const char * filename)1098 diagram_save(Diagram *dia, const char *filename)
1099 {
1100 gboolean res = diagram_data_save(dia->data, filename);
1101
1102 if (!res) {
1103 return res;
1104 }
1105
1106 dia->unsaved = FALSE;
1107 undo_mark_save(dia->undo);
1108 diagram_set_modified (dia, FALSE);
1109
1110 diagram_cleanup_autosave(dia);
1111
1112 return TRUE;
1113 }
1114
1115 /* Autosave stuff. Needs to use low-level save to avoid setting and resetting flags */
1116 void
diagram_cleanup_autosave(Diagram * dia)1117 diagram_cleanup_autosave(Diagram *dia)
1118 {
1119 gchar *savefile;
1120 struct stat statbuf;
1121
1122 savefile = dia->autosavefilename;
1123 if (savefile == NULL) return;
1124 #ifdef TRACES
1125 g_print("Cleaning up autosave %s for %s\n",
1126 savefile, dia->filename ? dia->filename : "<no name>");
1127 #endif
1128 if (g_stat(savefile, &statbuf) == 0) { /* Success */
1129 g_unlink(savefile);
1130 }
1131 g_free(savefile);
1132 dia->autosavefilename = NULL;
1133 dia->autosaved = FALSE;
1134 }
1135
1136 /** Absolutely autosave a diagram.
1137 * Called after a periodic check at the first idleness.
1138 */
1139 void
diagram_autosave(Diagram * dia)1140 diagram_autosave(Diagram *dia)
1141 {
1142 gchar *save_filename;
1143
1144 /* Must check if the diagram is still valid, or Death Ensues! */
1145 GList *diagrams = dia_open_diagrams();
1146 Diagram *diagram;
1147 while (diagrams != NULL) {
1148 diagram = (Diagram *)diagrams->data;
1149 if (diagram == dia &&
1150 diagram_is_modified(diagram) &&
1151 !diagram->autosaved) {
1152 save_filename = g_strdup_printf("%s.autosave", dia->filename);
1153
1154 if (dia->autosavefilename != NULL)
1155 g_free(dia->autosavefilename);
1156 dia->autosavefilename = save_filename;
1157 diagram_data_raw_save(dia->data, save_filename);
1158 dia->autosaved = TRUE;
1159 return;
1160 }
1161 diagrams = g_list_next(diagrams);
1162 }
1163 }
1164
1165 /* --- filter interfaces --- */
1166 static void
export_native(DiagramData * data,const gchar * filename,const gchar * diafilename,void * user_data)1167 export_native(DiagramData *data, const gchar *filename,
1168 const gchar *diafilename, void* user_data)
1169 {
1170 diagram_data_save(data, filename);
1171 }
1172
1173 static const gchar *extensions[] = { "dia", NULL };
1174 DiaExportFilter dia_export_filter = {
1175 N_("Dia Diagram File"),
1176 extensions,
1177 export_native
1178 };
1179 DiaImportFilter dia_import_filter = {
1180 N_("Dia Diagram File"),
1181 extensions,
1182 diagram_data_load
1183 };
1184