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