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