1 /* Dia -- an diagram creation/manipulation program
2  * Copyright (C) 1998,1999 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 /** \file dia_xml.c  Helper function to convert Dia's basic to and from XML */
19 #include <config.h>
20 
21 #include <stdio.h>
22 #include <stdlib.h>
23 #include <string.h>
24 #include <math.h>
25 #include <fcntl.h>
26 
27 #include <glib.h>
28 #include <glib/gstdio.h>
29 
30 #include <libxml/parser.h>
31 #include <libxml/parserInternals.h>
32 #include <libxml/xmlmemory.h>
33 
34 #ifdef HAVE_UNISTD_H
35 #include <unistd.h>
36 #endif
37 
38 #include <zlib.h>
39 
40 #include "intl.h"
41 #include "utils.h"
42 #include "dia_xml_libxml.h"
43 #include "dia_xml.h"
44 #include "geometry.h"		/* For isinf() on Solaris */
45 #include "message.h"
46 
47 #ifdef G_OS_WIN32
48 #include <io.h> /* write, close */
49 #endif
50 
51 #ifdef G_OS_WIN32 /* apparently _MSC_VER and mingw */
52 #include <float.h>
53 # ifndef isinf
54 # define isinf(a) (!_finite(a))
55 # endif
56 #endif
57 
58 #define BUFLEN 1024
59 
60 /*
61  * redefinition of isnan and isinf, for portability, as explained in :
62  * http://www.gnu.org/software/autoconf/manual/html_node/Function-Portability.html
63  */
64 
65 #ifndef isnan
66 # define isnan(x) \
67      (sizeof (x) == sizeof (long double) ? isnan_ld (x) \
68      : sizeof (x) == sizeof (double) ? isnan_d (x) \
69      : isnan_f (x))
isnan_f(float x)70 static inline int isnan_f  (float       x) { return x != x; }
isnan_d(double x)71 static inline int isnan_d  (double      x) { return x != x; }
isnan_ld(long double x)72 static inline int isnan_ld (long double x) { return x != x; }
73 #endif
74 
75 #ifndef isinf
76 # define isinf(x) \
77     (sizeof (x) == sizeof (long double) ? isinf_ld (x) \
78      : sizeof (x) == sizeof (double) ? isinf_d (x) \
79      : isinf_f (x))
isinf_f(float x)80 static inline int isinf_f  (float       x) { return isnan (x - x); }
isinf_d(double x)81 static inline int isinf_d  (double      x) { return isnan (x - x); }
isinf_ld(long double x)82 static inline int isinf_ld (long double x) { return isnan (x - x); }
83 #endif
84 
85 /** If all files produced by dia were good XML files, we wouldn't have to do
86  *  this little gymnastic. Alas, during the libxml1 days, we were outputting
87  *  files with no encoding specification (which means UTF-8 if we're in an
88  *  asciish encoding) and strings encoded in local charset (so, we wrote
89  *  broken files).
90  *
91  *  The following logic finds if we have a broken file, and attempts to fix
92  *  it if it's possible. If the file is correct or is unrecognisable, we pass
93  *  it untouched to libxml2.
94  * @param filename The name of the file to check.
95  * @param default_enc The default encoding to use if none is given.
96  * @return The filename given if it seems ok, or the name of a new file
97  *          with fixed contents, or NULL if we couldn't read the file.  The
98  *          caller should free this string and unlink the file if it is not
99  *          the same as `filename'.
100  * @bug The many gzclose-g_free-return sequences should be refactored into
101  *       an "exception handle" (goto+label). At least for people who think goto is
102  *       better than this. I dont. --hb
103  */
104 static const gchar *
xml_file_check_encoding(const gchar * filename,const gchar * default_enc)105 xml_file_check_encoding(const gchar *filename, const gchar *default_enc)
106 {
107   int fd = g_open (filename, O_RDONLY, 0);
108   gzFile zf = gzdopen(fd,"rb");
109   gchar *buf;
110   gchar *p,*pmax;
111   int len;
112   gchar *tmp,*res;
113   int uf;
114   gboolean well_formed_utf8;
115 
116   static char magic_xml[] =
117   {0x3c,0x3f,0x78,0x6d,0x6c,0x00}; /* "<?xml" in ASCII */
118 
119   if (!zf) {
120     dia_log_message("%s can not be opened for encoding check (%s)", filename, fd > 0 ? "gzdopen" : "g_open");
121     /* XXX perhaps we can just chicken out to libxml ? -- CC */
122     return filename;
123   }
124   p = buf = g_malloc0(BUFLEN);
125   len = gzread(zf,buf,BUFLEN);
126   pmax = p + len;
127 
128   /* first, we expect the magic <?xml string */
129   if ((0 != strncmp(p,magic_xml,5)) || (len < 5)) {
130     gzclose(zf);
131     g_free(buf);
132     return filename; /* let libxml figure out what this is. */
133   }
134   /* now, we're sure we have some asciish XML file. */
135   p += 5;
136   while (((*p == 0x20)||(*p == 0x09)||(*p == 0x0d)||(*p == 0x0a))
137          && (p<pmax)) p++;
138   if (p>=pmax) { /* whoops ? */
139     gzclose(zf);
140     g_free(buf);
141     return filename;
142   }
143   if (0 != strncmp(p,"version=\"",9)) {
144     gzclose(zf); /* chicken out. */
145     g_free(buf);
146     return filename;
147   }
148   p += 9;
149   /* The header is rather well formed. */
150   if (p>=pmax) { /* whoops ? */
151     gzclose(zf);
152     g_free(buf);
153     return filename;
154   }
155   while ((*p != '"') && (p < pmax)) p++;
156   p++;
157   while (((*p == 0x20)||(*p == 0x09)||(*p == 0x0d)||(*p == 0x0a))
158          && (p<pmax)) p++;
159   if (p>=pmax) { /* whoops ? */
160     gzclose(zf);
161     g_free(buf);
162     return filename;
163   }
164   if (0 == strncmp(p,"encoding=\"",10)) {
165     gzclose(zf); /* this file has an encoding string. Good. */
166     g_free(buf);
167     return filename;
168   }
169   /* now let's read the whole file, to see if there are offending bits.
170    * We can call it well formed UTF-8 if the highest isn't used
171    */
172   well_formed_utf8 = TRUE;
173   do {
174     int i;
175     for (i = 0; i < len; i++)
176       if (buf[i] & 0x80 || buf[i] == '&')
177         well_formed_utf8 = FALSE;
178     len = gzread(zf,buf,BUFLEN);
179   } while (len > 0 && well_formed_utf8);
180   if (well_formed_utf8) {
181     gzclose(zf); /* this file is utf-8 compatible  */
182     g_free(buf);
183     return filename;
184   } else {
185     gzclose(zf); /* poor man's fseek */
186     fd = g_open (filename, O_RDONLY, 0);
187     zf = gzdopen(fd,"rb");
188     len = gzread(zf,buf,BUFLEN);
189   }
190 
191   if (0 != strcmp(default_enc,"UTF-8")) {
192     message_warning(_("The file %s has no encoding specification;\n"
193                       "assuming it is encoded in %s"),
194 		    dia_message_filename(filename), default_enc);
195   } else {
196     gzclose(zf); /* we apply the standard here. */
197     g_free(buf);
198     return filename;
199   }
200 
201   tmp = getenv("TMP");
202   if (!tmp) tmp = getenv("TEMP");
203   if (!tmp) tmp = "/tmp";
204 
205   res = g_strconcat(tmp,G_DIR_SEPARATOR_S,"dia-xml-fix-encodingXXXXXX",NULL);
206   uf = g_mkstemp(res);
207   write(uf,buf,p-buf);
208   write(uf," encoding=\"",11);
209   write(uf,default_enc,strlen(default_enc));
210   write(uf,"\" ",2);
211   write(uf,p,pmax - p);
212 
213   while (1) {
214     len = gzread(zf,buf,BUFLEN);
215     if (len <= 0) break;
216     write(uf,buf,len);
217   }
218   gzclose(zf);
219   close(uf);
220   g_free(buf);
221   return res; /* caller frees the name and unlinks the file. */
222 }
223 
224 /** Parse a given file into XML, handling old broken files correctly.
225  * @param filename The name of the file to read.
226  * @returns An XML document parsed from the file.
227  * @see xmlParseFile() in the XML2 library for details on the return value.
228  */
229 xmlDocPtr
xmlDiaParseFile(const char * filename)230 xmlDiaParseFile(const char *filename)
231 {
232   G_CONST_RETURN char *local_charset = NULL;
233 
234   if (   !g_get_charset(&local_charset)
235       && local_charset) {
236     /* we're not in an UTF-8 environment. */
237     const gchar *fname = xml_file_check_encoding(filename,local_charset);
238     if (fname != filename) {
239       /* We've got a corrected file to parse. */
240       xmlDocPtr ret = xmlDoParseFile(fname);
241       unlink(fname);
242       /* printf("has read %s instead of %s\n",fname,filename); */
243       g_free((void *)fname);
244       return ret;
245     } else {
246       /* the XML file is good. libxml is "old enough" to handle it correctly.
247        */
248       return xmlDoParseFile(filename);
249     }
250   } else {
251     return xmlDoParseFile(filename);
252   }
253 }
254 
255 /** Parse an xml file from a filename given in Dia's/GLib's filename encoding
256  * @param filename A file to parse. On win32 the filename encoding is utf-8 since GLib 2.6
257  * @return An XML document.
258  */
259 xmlDocPtr
xmlDoParseFile(const char * filename)260 xmlDoParseFile(const char *filename)
261 {
262   return xmlParseFile(filename);
263 }
264 
265 /** Find a named attribute node in an XML object node.
266  *  Note that Dia has a concept of attribute node that is not the same
267  *  as an XML attribute.
268  * @param obj_node The node to look in.
269  * @param attrname The name of the attribute node to find.
270  * @return The node matching the given name, or NULL if none found.
271  */
272 AttributeNode
object_find_attribute(ObjectNode obj_node,const char * attrname)273 object_find_attribute(ObjectNode obj_node,
274 		      const char *attrname)
275 {
276   AttributeNode attr;
277   xmlChar *name;
278 
279   while (obj_node && xmlIsBlankNode(obj_node))
280     obj_node = obj_node->next;
281   if (!obj_node) return NULL;
282 
283   attr =  obj_node->xmlChildrenNode;
284   while (attr != NULL) {
285     if (xmlIsBlankNode(attr)) {
286       attr = attr->next;
287       continue;
288     }
289 
290     name = xmlGetProp(attr, (const xmlChar *)"name");
291     if ( (name!=NULL) && (strcmp((char *) name, attrname)==0) ) {
292       xmlFree(name);
293       return attr;
294     }
295     if (name) xmlFree(name);
296 
297     attr = attr->next;
298   }
299   return NULL;
300 }
301 
302 /** Find an attribute in a composite XML node.
303  * @param composite_node The composite node to search.
304  * @param attrname The name of the attribute node to find.
305  * @return The desired node, or NULL if none exists in `composite_node'.
306  * @bug Describe in more detail how a composite node differs from an
307  *   object node.
308  */
309 AttributeNode
composite_find_attribute(DataNode composite_node,const char * attrname)310 composite_find_attribute(DataNode composite_node,
311 			 const char *attrname)
312 {
313   AttributeNode attr;
314   xmlChar *name;
315 
316   while (composite_node && xmlIsBlankNode(composite_node))
317     composite_node = composite_node->next;
318   if (!composite_node) return NULL;
319 
320   attr =  composite_node->xmlChildrenNode;
321   while (attr != NULL) {
322     if (xmlIsBlankNode(attr)) {
323       attr = attr->next;
324       continue;
325     }
326 
327     name = xmlGetProp(attr, (const xmlChar *)"name");
328     if ( (name!=NULL) && (strcmp((char *) name, attrname)==0) ) {
329       xmlFree(name);
330       return attr;
331     }
332     if (name) xmlFree(name);
333 
334     attr = attr->next;
335   }
336   return NULL;
337 }
338 
339 /** The number of non-blank data nodes in an attribute node.
340  * @param attribute The attribute node to read from.
341  * @returns The number of non-blank data nodes in the node.
342  */
343 int
attribute_num_data(AttributeNode attribute)344 attribute_num_data(AttributeNode attribute)
345 {
346   xmlNode *data;
347   int nr=0;
348 
349   data =  attribute ? attribute->xmlChildrenNode : NULL;
350   while (data != NULL) {
351     if (xmlIsBlankNode(data)) {
352       data = data->next;
353       continue;
354     }
355     nr++;
356     data = data->next;
357   }
358   return nr;
359 }
360 
361 /** Get the first data node in an attribute node.
362  * @param attribute The attribute node to look through.
363  * @return The first non-black data node in the attribute node.
364  */
365 DataNode
attribute_first_data(AttributeNode attribute)366 attribute_first_data(AttributeNode attribute)
367 {
368   xmlNode *data = attribute ? attribute->xmlChildrenNode : NULL;
369   while (data && xmlIsBlankNode(data)) data = data->next;
370   return (DataNode) data;
371 }
372 
373 /** Get the next data node (sibling).
374  * @param data A data node to start from (e.g. just processed)
375  * @returns The next sibling data node.
376  */
377 DataNode
data_next(DataNode data)378 data_next(DataNode data)
379 {
380 
381   if (data) {
382     data = data->next;
383     while (data && xmlIsBlankNode(data)) data = data->next;
384   }
385   return (DataNode) data;
386 }
387 
388 /** Get the type of a data node.
389  * @param data The data node.
390  * @return The type that the data node defines, or 0 on error.  In case of
391  *  error, an error message is displayed.
392  * @note This function does a number of strcmp calls, which may not be the
393  *  fastest way to check if a node is of the expected type.
394  * @bug Make functions that check quickly if a node is of a specific type
395  *  (but profile first).
396  */
397 DataType
data_type(DataNode data)398 data_type(DataNode data)
399 {
400   const char *name;
401 
402   name = data ? (const char *)data->name : (const char *)"";
403   if (strcmp(name, "composite")==0) {
404     return DATATYPE_COMPOSITE;
405   } else if (strcmp(name, "int")==0) {
406     return DATATYPE_INT;
407   } else if (strcmp(name, "enum")==0) {
408     return DATATYPE_ENUM;
409   } else if (strcmp(name, "real")==0) {
410     return DATATYPE_REAL;
411   } else if (strcmp(name, "boolean")==0) {
412     return DATATYPE_BOOLEAN;
413   } else if (strcmp(name, "color")==0) {
414     return DATATYPE_COLOR;
415   } else if (strcmp(name, "point")==0) {
416     return DATATYPE_POINT;
417   } else if (strcmp(name, "rectangle")==0) {
418     return DATATYPE_RECTANGLE;
419   } else if (strcmp(name, "string")==0) {
420     return DATATYPE_STRING;
421   } else if (strcmp(name, "font")==0) {
422     return DATATYPE_FONT;
423   } else if (strcmp(name, "bezpoint")==0) {
424     return DATATYPE_BEZPOINT;
425   } else if (strcmp(name, "dict")==0) {
426     return DATATYPE_DICT;
427   }
428 
429   message_error("Unknown type of DataNode");
430   return 0;
431 }
432 
433 /** Return the value of an integer-type data node.
434  * @param data The data node to read from.
435  * @returns The integer value found in the node.  If the node is not an
436  *  integer node, an error message is displayed and 0 is returned.
437  */
438 int
data_int(DataNode data)439 data_int(DataNode data)
440 {
441   xmlChar *val;
442   int res;
443 
444   if (data_type(data)!=DATATYPE_INT) {
445     message_error("Taking int value of non-int node.");
446     return 0;
447   }
448 
449   val = xmlGetProp(data, (const xmlChar *)"val");
450   res = atoi((char *) val);
451   if (val) xmlFree(val);
452 
453   return res;
454 }
455 
456 /** Return the value of an enum-type data node.
457  * @param data The data node to read from.
458  * @returns The enum value found in the node.  If the node is not an
459  *  enum node, an error message is displayed and 0 is returned.
460  */
data_enum(DataNode data)461 int data_enum(DataNode data)
462 {
463   xmlChar *val;
464   int res;
465 
466   if (data_type(data)!=DATATYPE_ENUM) {
467     message_error("Taking enum value of non-enum node.");
468     return 0;
469   }
470 
471   val = xmlGetProp(data, (const xmlChar *)"val");
472   res = atoi((char *) val);
473   if (val) xmlFree(val);
474 
475   return res;
476 }
477 
478 /** Return the value of a real-type data node.
479  * @param data The data node to read from.
480  * @returns The real value found in the node.  If the node is not a
481  *  real-type node, an error message is displayed and 0.0 is returned.
482  */
483 real
data_real(DataNode data)484 data_real(DataNode data)
485 {
486   xmlChar *val;
487   real res;
488 
489   if (data_type(data)!=DATATYPE_REAL) {
490     message_error("Taking real value of non-real node.");
491     return 0;
492   }
493 
494   val = xmlGetProp(data, (const xmlChar *)"val");
495   res = g_ascii_strtod((char *) val, NULL);
496   if (val) xmlFree(val);
497 
498   return res;
499 }
500 
501 /** Return the value of a boolean-type data node.
502  * @param data The data node to read from.
503  * @returns The boolean value found in the node.  If the node is not a
504  *  boolean node, an error message is displayed and FALSE is returned.
505  */
506 int
data_boolean(DataNode data)507 data_boolean(DataNode data)
508 {
509   xmlChar *val;
510   int res;
511 
512   if (data_type(data)!=DATATYPE_BOOLEAN) {
513     message_error("Taking boolean value of non-boolean node.");
514     return 0;
515   }
516 
517   val = xmlGetProp(data, (const xmlChar *)"val");
518 
519   if ((val) && (strcmp((char *) val, "true")==0))
520     res =  TRUE;
521   else
522     res = FALSE;
523 
524   if (val) xmlFree(val);
525 
526   return res;
527 }
528 
529 /** Return the integer value of a hex digit.
530  * @param c A hex digit, one of 0-9, a-f or A-F.
531  * @returns The value of the digit, i.e. 0-15.  If a non-gex digit is given
532  *  an error message is displayed to the user, and 0 is returned.
533  */
534 static int
hex_digit(char c)535 hex_digit(char c)
536 {
537   if ((c>='0') && (c<='9'))
538     return c-'0';
539   if ((c>='a') && (c<='f'))
540     return (c-'a') + 10;
541   if ((c>='A') && (c<='F'))
542     return (c-'A') + 10;
543   message_error("wrong hex digit %c", c);
544   return 0;
545 }
546 
547 /** Return the value of a color-type data node.
548  * @param data The XML node to read from
549  * @param col A place to store the resulting RGB values.  If the node does
550  *  not contain a valid color value, an error message is displayed to the
551  *  user, and `col' is unchanged.
552  * @note Could be cool to use RGBA data here, even if we can't display it yet.
553  */
554 void
data_color(DataNode data,Color * col)555 data_color(DataNode data, Color *col)
556 {
557   xmlChar *val;
558   int r=0, g=0, b=0;
559 
560   if (data_type(data)!=DATATYPE_COLOR) {
561     message_error("Taking color value of non-color node.");
562     return;
563   }
564 
565   val = xmlGetProp(data, (const xmlChar *)"val");
566 
567   /* Format #RRGGBB */
568   /*        0123456 */
569 
570   if ((val) && (xmlStrlen(val)>=7)) {
571     r = hex_digit(val[1])*16 + hex_digit(val[2]);
572     g = hex_digit(val[3])*16 + hex_digit(val[4]);
573     b = hex_digit(val[5])*16 + hex_digit(val[6]);
574   }
575 
576   if (val) xmlFree(val);
577 
578   col->red = (float)(r/255.0);
579   col->green = (float)(g/255.0);
580   col->blue = (float)(b/255.0);
581 }
582 
583 /** Return the value of a point-type data node.
584  * @param data The XML node to read from
585  * @param point A place to store the resulting x, y values.  If the node does
586  *  not contain a valid point value, an error message is displayed to the
587  *  user, and `point' is unchanged.
588  */
589 void
data_point(DataNode data,Point * point)590 data_point(DataNode data, Point *point)
591 {
592   xmlChar *val;
593   gchar *str;
594   real ax,ay;
595 
596   if (data_type(data)!=DATATYPE_POINT) {
597     message_error(_("Taking point value of non-point node."));
598     return;
599   }
600 
601   val = xmlGetProp(data, (const xmlChar *)"val");
602   point->x = g_ascii_strtod((char *)val, &str);
603   ax = fabs(point->x);
604   if ((ax > 1e9) || ((ax < 1e-9) && (ax != 0.0)) || isnan(ax) || isinf(ax)) {
605     /* there is no provision to keep values larger when saving,
606      * so do this 'reduction' silent */
607     if (!(ax < 1e-9))
608       g_warning(_("Incorrect x Point value \"%s\" %f; discarding it."),val,point->x);
609     point->x = 0.0;
610   }
611   while ((*str != ',') && (*str!=0))
612     str++;
613   if (*str==0){
614     point->y = 0.0;
615     g_warning(_("Error parsing point."));
616     xmlFree(val);
617     return;
618   }
619   point->y = g_ascii_strtod(str+1, NULL);
620   ay = fabs(point->y);
621   if ((ay > 1e9) || ((ay < 1e-9) && (ay != 0.0)) || isnan(ay) || isinf(ay)) {
622     if (!(ay < 1e-9)) /* don't bother with useless warnings (see above) */
623       g_warning(_("Incorrect y Point value \"%s\" %f; discarding it."),str+1,point->y);
624     point->y = 0.0;
625   }
626   xmlFree(val);
627 }
628 
629 /** Return the value of a bezpoint-type data node.
630  * @param data The XML node to read from
631  * @param point A place to store the resulting values.  If the node does
632  *  not contain a valid bezpoint zero initialization is performed.
633  */
634 void
data_bezpoint(DataNode data,BezPoint * point)635 data_bezpoint(DataNode data, BezPoint *point)
636 {
637   xmlChar *val;
638   gchar *str;
639   if (data_type(data)!=DATATYPE_BEZPOINT) {
640     message_error(_("Taking bezpoint value of non-point node."));
641     return;
642   }
643   val = xmlGetProp(data, (const xmlChar *)"type");
644   if (val) {
645      if (strcmp((char *)val, "moveto") == 0)
646        point->type = BEZ_MOVE_TO;
647      else if (strcmp((char *)val, "lineto") == 0)
648        point->type = BEZ_LINE_TO;
649      else
650        point->type = BEZ_CURVE_TO;
651     xmlFree(val);
652   }
653   val = xmlGetProp(data, (const xmlChar *)"p1");
654   if (val) {
655     point->p1.x = g_ascii_strtod((char *)val, &str);
656     if (*str==0) {
657       point->p1.y = 0;
658       g_warning(_("Error parsing bezpoint p1."));
659     } else {
660       point->p1.y = g_ascii_strtod(str+1, NULL);
661     }
662     xmlFree(val);
663   } else {
664     point->p1.x = 0;
665     point->p1.y = 0;
666   }
667   val = xmlGetProp(data, (const xmlChar *)"p2");
668   if (val) {
669     point->p2.x = g_ascii_strtod((char *)val, &str);
670     if (*str==0) {
671       point->p2.y = 0;
672       g_warning(_("Error parsing bezpoint p2."));
673     } else {
674       point->p2.y = g_ascii_strtod(str+1, NULL);
675     }
676     xmlFree(val);
677   } else {
678     point->p2.x = 0;
679     point->p2.y = 0;
680   }
681   val = xmlGetProp(data, (const xmlChar *)"p3");
682   if (val) {
683     point->p3.x = g_ascii_strtod((char *)val, &str);
684     if (*str==0) {
685       point->p3.y = 0;
686       g_warning(_("Error parsing bezpoint p3."));
687     } else {
688       point->p3.y = g_ascii_strtod(str+1, NULL);
689     }
690     xmlFree(val);
691   } else {
692     point->p3.x = 0;
693     point->p3.y = 0;
694   }
695 }
696 
697 /** Return the value of a rectangle-type data node.
698  * @param data The data node to read from.
699  * @param rect A place to store the resulting values.  If the node does
700  *  not contain a valid rectangle value, an error message is displayed to the
701  *  user, and `rect' is unchanged.
702  */
703 void
data_rectangle(DataNode data,Rectangle * rect)704 data_rectangle(DataNode data, Rectangle *rect)
705 {
706   xmlChar *val;
707   gchar *str;
708 
709   if (data_type(data)!=DATATYPE_RECTANGLE) {
710     message_error("Taking rectangle value of non-rectangle node.");
711     return;
712   }
713 
714   val = xmlGetProp(data, (const xmlChar *)"val");
715 
716   rect->left = g_ascii_strtod((char *)val, &str);
717 
718   while ((*str != ',') && (*str!=0))
719     str++;
720 
721   if (*str==0){
722     message_error("Error parsing rectangle.");
723     xmlFree(val);
724     return;
725   }
726 
727   rect->top = g_ascii_strtod(str+1, &str);
728 
729   while ((*str != ';') && (*str!=0))
730     str++;
731 
732   if (*str==0){
733     message_error("Error parsing rectangle.");
734     xmlFree(val);
735     return;
736   }
737 
738   rect->right = g_ascii_strtod(str+1, &str);
739 
740   while ((*str != ',') && (*str!=0))
741     str++;
742 
743   if (*str==0){
744     message_error("Error parsing rectangle.");
745     xmlFree(val);
746     return;
747   }
748 
749   rect->bottom = g_ascii_strtod(str+1, NULL);
750 
751   xmlFree(val);
752 }
753 
754 /** Return the value of a string-type data node.
755  * @param data The data node to read from.
756  * @returns The string value found in the node.  If the node is not a
757  *  string node, an error message is displayed and NULL is returned.  The
758  *  returned valuee should be freed after use.
759  * @note For historical reasons, strings in Dia XML are surrounded by ##.
760  */
761 gchar *
data_string(DataNode data)762 data_string(DataNode data)
763 {
764   xmlChar *val;
765   gchar *str, *p,*str2;
766   int len;
767 
768   if (data_type(data)!=DATATYPE_STRING) {
769     message_error("Taking string value of non-string node.");
770     return NULL;
771   }
772 
773   val = xmlGetProp(data, (const xmlChar *)"val");
774   if (val != NULL) { /* Old kind of string. Left for backwards compatibility */
775     str  = g_malloc(4 * (sizeof(char)*(xmlStrlen(val)+1))); /* extra room
776                                                             for UTF8 */
777     p = str;
778     while (*val) {
779       if (*val == '\\') {
780 	val++;
781 	switch (*val) {
782 	case '0':
783 	  /* Just skip this. \0 means nothing */
784 	  break;
785 	case 'n':
786 	  *p++ = '\n';
787 	  break;
788 	case 't':
789 	  *p++ = '\t';
790 	  break;
791 	case '\\':
792 	  *p++ = '\\';
793 	  break;
794 	default:
795 	  message_error("Error in string tag.");
796 	}
797       } else {
798 	*p++ = *val;
799       }
800       val++;
801     }
802     *p = 0;
803     xmlFree(val);
804     str2 = g_strdup(str);  /* to remove the extra space */
805     g_free(str);
806     return str2;
807   }
808 
809   if (data->xmlChildrenNode!=NULL) {
810     p = (char *)xmlNodeListGetString(data->doc, data->xmlChildrenNode, TRUE);
811 
812     if (*p!='#')
813       message_error("Error in file, string not starting with #\n");
814 
815     len = strlen(p)-1; /* Ignore first '#' */
816 
817     str = g_malloc(len+1);
818 
819     strncpy(str, p+1, len);
820     str[len]=0; /* For safety */
821 
822     str[strlen(str)-1] = 0; /* Remove last '#' */
823     xmlFree(p);
824     return str;
825   }
826 
827   return NULL;
828 }
829 
830 /** Return the value of a filename-type data node.
831  * @param data The data node to read from.
832  * @return The filename value found in the node.  If the node is not a
833  *  filename node, an error message is displayed and NULL is returned.
834  *  The resulting string is in the local filesystem's encoding rather than
835  *  UTF-8, and should be freed after use.
836  * @bug data_string() can return NULL, what does g_filename_from_utf8 do then?
837  */
838 char *
data_filename(DataNode data)839 data_filename(DataNode data)
840 {
841   char *utf8 = data_string(data);
842   char *filename = g_filename_from_utf8(utf8, -1, NULL, NULL, NULL);
843   g_free(utf8);
844   return filename;
845 }
846 
847 /** Return the value of a font-type data node.  This handles both the current
848  * format (family and style) and the old format (name).
849  * @param data The data node to read from.
850  * @return The font value found in the node.  If the node is not a
851  *  font node, an error message is displayed and NULL is returned.  The
852  *  resulting value should be freed after use.
853  */
854 DiaFont *
data_font(DataNode data)855 data_font(DataNode data)
856 {
857   xmlChar *family;
858   DiaFont *font;
859 
860   if (data_type(data)!=DATATYPE_FONT) {
861     message_error("Taking font value of non-font node.");
862     return NULL;
863   }
864 
865   family = xmlGetProp(data, (const xmlChar *)"family");
866   /* always prefer the new format */
867   if (family) {
868     DiaFontStyle style;
869     char* style_name = (char *) xmlGetProp(data, (const xmlChar *)"style");
870     style = style_name ? atoi(style_name) : 0;
871 
872     font = dia_font_new ((char *)family, style, 1.0);
873     if (family) free(family);
874     if (style_name) xmlFree(style_name);
875   } else {
876     /* Legacy format support */
877     char *name = (char *)xmlGetProp(data, (const xmlChar *)"name");
878     font = dia_font_new_from_legacy_name(name);
879     free(name);
880   }
881   return font;
882 }
883 
884 /* ***** Saving XML **** */
885 
886 /** Create a new attribute node.
887  * @param obj_node The object node to create the attribute node under.
888  * @param attrname The name of the attribute node.
889  * @return A new attribute node.
890  * @bug Should have utility functions that creates the node and sets
891  *  the value based on type.
892  */
893 AttributeNode
new_attribute(ObjectNode obj_node,const char * attrname)894 new_attribute(ObjectNode obj_node,
895 	      const char *attrname)
896 {
897   AttributeNode attr;
898   attr = xmlNewChild(obj_node, NULL, (const xmlChar *)"attribute", NULL);
899   xmlSetProp(attr, (const xmlChar *)"name", (xmlChar *)attrname);
900 
901   return attr;
902 }
903 
904 /** Add an attribute node to a composite node.
905  * @param composite_node The composite node.
906  * @param attrname The name of the new attribute node.
907  * @return The attribute node added.
908  * @bug This does exactly the same as new_attribute.
909  */
910 AttributeNode
composite_add_attribute(DataNode composite_node,const char * attrname)911 composite_add_attribute(DataNode composite_node,
912 			const char *attrname)
913 {
914   AttributeNode attr;
915   attr = xmlNewChild(composite_node, NULL, (const xmlChar *)"attribute", NULL);
916   xmlSetProp(attr, (const xmlChar *)"name", (xmlChar *)attrname);
917 
918   return attr;
919 }
920 
921 /** Add integer data to an attribute node.
922  * @param attr The attribute node.
923  * @param data The value to set.
924  */
925 void
data_add_int(AttributeNode attr,int data)926 data_add_int(AttributeNode attr, int data)
927 {
928   DataNode data_node;
929   char buffer[20+1]; /* Enought for 64bit int + zero */
930 
931   g_snprintf(buffer, 20, "%d", data);
932 
933   data_node = xmlNewChild(attr, NULL, (const xmlChar *)"int", NULL);
934   xmlSetProp(data_node, (const xmlChar *)"val", (xmlChar *)buffer);
935 }
936 
937 /** Add enum data to an attribute node.
938  * @param attr The attribute node.
939  * @param data The value to set.
940  */
941 void
data_add_enum(AttributeNode attr,int data)942 data_add_enum(AttributeNode attr, int data)
943 {
944   DataNode data_node;
945   char buffer[20+1]; /* Enought for 64bit int + zero */
946 
947   g_snprintf(buffer, 20, "%d", data);
948 
949   data_node = xmlNewChild(attr, NULL, (const xmlChar *)"enum", NULL);
950   xmlSetProp(data_node, (const xmlChar *)"val", (xmlChar *)buffer);
951 }
952 
953 /** Add real-typed data to an attribute node.
954  * @param attr The attribute node.
955  * @param data The value to set.
956  */
957 void
data_add_real(AttributeNode attr,real data)958 data_add_real(AttributeNode attr, real data)
959 {
960   DataNode data_node;
961   char buffer[G_ASCII_DTOSTR_BUF_SIZE]; /* Large enought */
962 
963   g_ascii_dtostr(buffer, G_ASCII_DTOSTR_BUF_SIZE, data);
964 
965   data_node = xmlNewChild(attr, NULL, (const xmlChar *)"real", NULL);
966   xmlSetProp(data_node, (const xmlChar *)"val", (xmlChar *)buffer);
967 }
968 
969 /** Add boolean data to an attribute node.
970  * @param attr The attribute node.
971  * @param data The value to set.
972  */
973 void
data_add_boolean(AttributeNode attr,int data)974 data_add_boolean(AttributeNode attr, int data)
975 {
976   DataNode data_node;
977 
978   data_node = xmlNewChild(attr, NULL, (const xmlChar *)"boolean", NULL);
979   if (data)
980     xmlSetProp(data_node, (const xmlChar *)"val", (const xmlChar *)"true");
981   else
982     xmlSetProp(data_node, (const xmlChar *)"val", (const xmlChar *)"false");
983 }
984 
985 /** Convert a floating-point value to hexadecimal.
986  * @param x The floating point value.
987  * @param str A string to place the result in.
988  * @note Currently only works for 0 <= x <= 255 and will silently cap the value
989  *  to those limits.  Also expects str to have at least two bytes allocated,
990  *  and doesn't null-terminate it.  This works well for converting a color
991  *  value, but is pretty much useless for other values.
992  */
993 static void
convert_to_hex(float x,char * str)994 convert_to_hex(float x, char *str)
995 {
996   static const char hex_digit[] = "0123456789abcdef";
997   int val;
998 
999   val = x * 255.0;
1000   if (val>255)
1001     val = 255;
1002   if (val<0)
1003     val = 0;
1004 
1005   str[0] = hex_digit[val/16];
1006   str[1] = hex_digit[val%16];
1007 }
1008 
1009 /** Add color data to an attribute node.
1010  * @param attr The attribute node.
1011  * @param col The value to set.
1012  */
1013 void
data_add_color(AttributeNode attr,const Color * col)1014 data_add_color(AttributeNode attr, const Color *col)
1015 {
1016   char buffer[1+6+1];
1017   DataNode data_node;
1018 
1019   buffer[0] = '#';
1020   convert_to_hex(col->red, &buffer[1]);
1021   convert_to_hex(col->green, &buffer[3]);
1022   convert_to_hex(col->blue, &buffer[5]);
1023   buffer[7] = 0;
1024 
1025   data_node = xmlNewChild(attr, NULL, (const xmlChar *)"color", NULL);
1026   xmlSetProp(data_node, (const xmlChar *)"val", (xmlChar *)buffer);
1027 }
1028 
1029 static gchar *
_str_point(const Point * point)1030 _str_point (const Point *point)
1031 {
1032   gchar *buffer;
1033   gchar px_buf[G_ASCII_DTOSTR_BUF_SIZE];
1034   gchar py_buf[G_ASCII_DTOSTR_BUF_SIZE];
1035 
1036   g_ascii_formatd(px_buf, sizeof(px_buf), "%g", point->x);
1037   g_ascii_formatd(py_buf, sizeof(py_buf), "%g", point->y);
1038   buffer = g_strconcat(px_buf, ",", py_buf, NULL);
1039 
1040   return buffer;
1041 }
1042 
1043 /** Add point data to an attribute node.
1044  * @param attr The attribute node.
1045  * @param point The value to set.
1046  */
1047 void
data_add_point(AttributeNode attr,const Point * point)1048 data_add_point(AttributeNode attr, const Point *point)
1049 {
1050   DataNode data_node;
1051   gchar *buffer = _str_point (point);
1052 
1053   data_node = xmlNewChild(attr, NULL, (const xmlChar *)"point", NULL);
1054   xmlSetProp(data_node, (const xmlChar *)"val", (xmlChar *)buffer);
1055   g_free(buffer);
1056 }
1057 
1058 void
data_add_bezpoint(AttributeNode attr,const BezPoint * point)1059 data_add_bezpoint(AttributeNode attr, const BezPoint *point)
1060 {
1061   DataNode data_node;
1062   gchar *buffer;
1063 
1064   data_node = xmlNewChild(attr, NULL, (const xmlChar *)"bezpoint", NULL);
1065   switch (point->type) {
1066   case BEZ_MOVE_TO :
1067     xmlSetProp(data_node, (const xmlChar *)"type", (const xmlChar *)"moveto");
1068     break;
1069   case BEZ_LINE_TO :
1070     xmlSetProp(data_node, (const xmlChar *)"type", (const xmlChar *)"lineto");
1071     break;
1072   case BEZ_CURVE_TO :
1073     xmlSetProp(data_node, (const xmlChar *)"type", (const xmlChar *)"curveto");
1074     break;
1075   default :
1076     g_assert_not_reached();
1077   }
1078 
1079   buffer = _str_point (&point->p1);
1080   xmlSetProp(data_node, (const xmlChar *)"p1", (xmlChar *)buffer);
1081   g_free (buffer);
1082   if (point->type == BEZ_CURVE_TO) {
1083     buffer = _str_point (&point->p2);
1084     xmlSetProp(data_node, (const xmlChar *)"p2", (xmlChar *)buffer);
1085     g_free (buffer);
1086     buffer = _str_point (&point->p3);
1087     xmlSetProp(data_node, (const xmlChar *)"p3", (xmlChar *)buffer);
1088     g_free (buffer);
1089   }
1090 }
1091 
1092 /** Add rectangle data to an attribute node.
1093  * @param attr The attribute node.
1094  * @param rect The value to set.
1095  */
1096 void
data_add_rectangle(AttributeNode attr,const Rectangle * rect)1097 data_add_rectangle(AttributeNode attr, const Rectangle *rect)
1098 {
1099   DataNode data_node;
1100   gchar *buffer;
1101   gchar rl_buf[G_ASCII_DTOSTR_BUF_SIZE];
1102   gchar rr_buf[G_ASCII_DTOSTR_BUF_SIZE];
1103   gchar rt_buf[G_ASCII_DTOSTR_BUF_SIZE];
1104   gchar rb_buf[G_ASCII_DTOSTR_BUF_SIZE];
1105 
1106   g_ascii_formatd(rl_buf, sizeof(rl_buf), "%g", rect->left);
1107   g_ascii_formatd(rr_buf, sizeof(rr_buf), "%g", rect->right);
1108   g_ascii_formatd(rt_buf, sizeof(rt_buf), "%g", rect->top);
1109   g_ascii_formatd(rb_buf, sizeof(rb_buf), "%g", rect->bottom);
1110 
1111   buffer = g_strconcat(rl_buf, ",", rt_buf, ";", rr_buf, ",", rb_buf, NULL);
1112 
1113   data_node = xmlNewChild(attr, NULL, (const xmlChar *)"rectangle", NULL);
1114   xmlSetProp(data_node, (const xmlChar *)"val", (xmlChar *)buffer);
1115 
1116   g_free(buffer);
1117 }
1118 
1119 /** Add string data to an attribute node.
1120  * @param attr The attribute node.
1121  * @param str The value to set.
1122  */
1123 void
data_add_string(AttributeNode attr,const char * str)1124 data_add_string(AttributeNode attr, const char *str)
1125 {
1126     DataNode data_node;
1127     xmlChar *escaped_str;
1128     xmlChar *sharped_str;
1129 
1130     if (str==NULL) {
1131         data_node = xmlNewChild(attr, NULL, (const xmlChar *)"string", (const xmlChar *)"##");
1132         return;
1133     }
1134 
1135     escaped_str = xmlEncodeEntitiesReentrant(attr->doc, (xmlChar *) str);
1136 
1137     sharped_str = (xmlChar *) g_strconcat("#", (char *) escaped_str, "#", NULL);
1138 
1139     xmlFree(escaped_str);
1140 
1141     data_node = xmlNewChild(attr, NULL, (const xmlChar *)"string", (xmlChar *) sharped_str);
1142 
1143     g_free(sharped_str);
1144 }
1145 
1146 /** Add filename data to an attribute node.
1147  * @param attr The attribute node.
1148  * @param filename The value to set.  This should be n the local filesystem
1149  *  encoding, not utf-8.
1150  */
1151 void
data_add_filename(DataNode data,const char * str)1152 data_add_filename(DataNode data, const char *str)
1153 {
1154   char *utf8 = g_filename_to_utf8(str, -1, NULL, NULL, NULL);
1155 
1156   data_add_string(data, utf8);
1157 
1158   g_free(utf8);
1159 }
1160 
1161 /** Add font data to an attribute node.
1162  * @param attr The attribute node.
1163  * @param font The value to set.
1164  */
1165 void
data_add_font(AttributeNode attr,const DiaFont * font)1166 data_add_font(AttributeNode attr, const DiaFont *font)
1167 {
1168   DataNode data_node;
1169   DiaFontStyle style;
1170   char buffer[20+1]; /* Enought for 64bit int + zero */
1171 
1172   data_node = xmlNewChild(attr, NULL, (const xmlChar *)"font", NULL);
1173   style = dia_font_get_style (font);
1174   xmlSetProp(data_node, (const xmlChar *)"family", (xmlChar *) dia_font_get_family(font));
1175   g_snprintf(buffer, 20, "%d", dia_font_get_style(font));
1176 
1177   xmlSetProp(data_node, (const xmlChar *)"style", (xmlChar *) buffer);
1178   /* Legacy support: don't crash older Dia on missing 'name' attribute */
1179   xmlSetProp(data_node, (const xmlChar *)"name", (xmlChar *) dia_font_get_legacy_name(font));
1180 }
1181 
1182 /** Add a new composite node to an attribute node.
1183  * @param attr The attribute node to add to.
1184  * @param type The type of the new node.
1185  * @returns The new child of `attr'.
1186  */
1187 DataNode
data_add_composite(AttributeNode attr,const char * type)1188 data_add_composite(AttributeNode attr, const char *type)
1189 {
1190   /* type can be NULL */
1191   DataNode data_node;
1192 
1193   data_node = xmlNewChild(attr, NULL, (const xmlChar *)"composite", NULL);
1194   if (type != NULL)
1195     xmlSetProp(data_node, (const xmlChar *)"type", (xmlChar *)type);
1196 
1197   return data_node;
1198 }
1199 
1200 #define BUFSIZE 2048
1201 #define OVERRUN_SAFETY 16
1202 
1203 /* diarc option */
1204 int pretty_formated_xml = TRUE;
1205 
1206 /** Save an XML document to a file.
1207  * @param filename The file to save to.
1208  * @param cur The XML document structure.
1209  * @return The return value of xmlSaveFormatFileEnc.
1210  * @bug Get the proper defn of the return value from libxml2.
1211  */
1212 int
xmlDiaSaveFile(const char * filename,xmlDocPtr cur)1213 xmlDiaSaveFile(const char *filename,
1214                    xmlDocPtr cur)
1215 {
1216     int old = 0, ret;
1217 
1218     if (pretty_formated_xml)
1219         old = xmlKeepBlanksDefault (0);
1220     ret = xmlSaveFormatFileEnc (filename,cur, "UTF-8", pretty_formated_xml ? 1 : 0);
1221     if (pretty_formated_xml)
1222         xmlKeepBlanksDefault (old);
1223     return ret;
1224 }
1225