1 /*
2  * go-libxml-extras.c: stuff that should have been in libxml2.
3  *
4  * Authors:
5  *   Daniel Veillard <Daniel.Veillard@w3.org>
6  *   Miguel de Icaza <miguel@gnu.org>
7  *   Jody Goldberg <jody@gnome.org>
8  *   Jukka-Pekka Iivonen <jiivonen@hutcs.cs.hut.fi>
9  *
10  * This program is free software; you can redistribute it and/or
11  * modify it under the terms of the GNU General Public License as
12  * published by the Free Software Foundation; either version 2 of the
13  * License, or (at your option) version 3.
14  *
15  * This program is distributed in the hope that it will be useful,
16  * but WITHOUT ANY WARRANTY; without even the implied warranty of
17  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
18  * GNU General Public License for more details.
19  *
20  * You should have received a copy of the GNU General Public License
21  * along with this program; if not, write to the Free Software
22  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301
23  * USA
24  */
25 #include <goffice/goffice-config.h>
26 #include "go-libxml-extras.h"
27 #include "go-color.h"
28 
29 #include <string.h>
30 #include <errno.h>
31 #include <stdlib.h>
32 #include <math.h>
33 
34 #define CC2XML(s) ((xmlChar const *)(s))
35 #define CXML2C(s) ((char const *)(s))
36 
37 
38 /**
39  * go_xml_parse_file: (skip)
40  * @filename: the locale path to a file to parse.
41  *
42  * Like xmlParseFile, but faster.  Does not accept compressed files.
43  * See http://bugzilla.gnome.org/show_bug.cgi?id=168414
44  *
45  * Note: this reads the entire file into memory and should therefore
46  * not be used for user-supplied files.
47  *
48  * Returns: (transfer full): A libxml2 xmlDocPtr or %NULL.
49  **/
50 xmlDocPtr
go_xml_parse_file(char const * filename)51 go_xml_parse_file (char const *filename)
52 {
53 	xmlDocPtr result = NULL;
54 	gchar *contents;
55 	gsize length;
56 
57 	if (g_file_get_contents (filename, &contents, &length, NULL)) {
58 		result = xmlParseMemory (contents, length);
59 		g_free (contents);
60 	}
61 
62 	return result;
63 }
64 
65 /**
66  * go_xml_node_get_cstr: (skip)
67  * @node: #xmlNodePtr
68  * @name: attribute name
69  * Get an xmlChar * value for a node carried as an attibute
70  * result must be xmlFree
71  *
72  * Returns: (transfer full): the attribute value
73  */
74 xmlChar *
go_xml_node_get_cstr(xmlNodePtr node,char const * name)75 go_xml_node_get_cstr (xmlNodePtr node, char const *name)
76 {
77 	if (name != NULL)
78 		return xmlGetProp (node, CC2XML (name));
79 	/* in libxml1 <foo/> would return NULL
80 	 * in libxml2 <foo/> would return ""
81 	 */
82 	if (node->xmlChildrenNode != NULL)
83 		return xmlNodeGetContent (node);
84 	return NULL;
85 }
86 void
go_xml_node_set_cstr(xmlNodePtr node,char const * name,char const * val)87 go_xml_node_set_cstr (xmlNodePtr node, char const *name, char const *val)
88 {
89 	if (name)
90 		xmlSetProp (node, CC2XML (name), CC2XML (val));
91 	else
92 		xmlNodeSetContent (node, CC2XML (val));
93 }
94 
95 gboolean
go_xml_node_get_bool(xmlNodePtr node,char const * name,gboolean * val)96 go_xml_node_get_bool (xmlNodePtr node, char const *name, gboolean *val)
97 {
98 	xmlChar *buf = go_xml_node_get_cstr (node, name);
99 	if (buf == NULL)
100 		return FALSE;
101 
102 	*val = (!strcmp (CXML2C (buf), "1")
103 		|| 0 == g_ascii_strcasecmp (CXML2C (buf), "true"));
104 	xmlFree (buf);
105 	return TRUE;
106 }
107 
108 void
go_xml_node_set_bool(xmlNodePtr node,char const * name,gboolean val)109 go_xml_node_set_bool (xmlNodePtr node, char const *name, gboolean val)
110 {
111 	go_xml_node_set_cstr (node, name, val ? "true" : "false");
112 }
113 
114 gboolean
go_xml_node_get_int(xmlNodePtr node,char const * name,int * val)115 go_xml_node_get_int (xmlNodePtr node, char const *name, int *val)
116 {
117 	xmlChar *buf;
118 	char *end;
119 	gboolean ok;
120 	long l;
121 
122 	buf = go_xml_node_get_cstr (node, name);
123 	if (buf == NULL)
124 		return FALSE;
125 
126 	errno = 0; /* strto(ld) sets errno, but does not clear it.  */
127 	*val = l = strtol (CXML2C (buf), &end, 10);
128 	ok = (CXML2C (buf) != end) && *end == 0 && errno != ERANGE && (*val == l);
129 	xmlFree (buf);
130 
131 	return ok;
132 }
133 
134 void
go_xml_node_set_int(xmlNodePtr node,char const * name,int val)135 go_xml_node_set_int (xmlNodePtr node, char const *name, int val)
136 {
137 	char str[4 * sizeof (int)];
138 	sprintf (str, "%d", val);
139 	go_xml_node_set_cstr (node, name, str);
140 }
141 
142 gboolean
go_xml_node_get_double(xmlNodePtr node,char const * name,double * val)143 go_xml_node_get_double (xmlNodePtr node, char const *name, double *val)
144 {
145 	xmlChar *buf;
146 	char *end;
147 	gboolean ok;
148 
149 	buf = go_xml_node_get_cstr (node, name);
150 	if (buf == NULL)
151 		return FALSE;
152 
153 	errno = 0; /* strto(ld) sets errno, but does not clear it.  */
154 	*val = strtod (CXML2C (buf), &end);
155 	ok = (CXML2C (buf) != end) && *end == 0 && errno != ERANGE;
156 	xmlFree (buf);
157 
158 	return ok;
159 }
160 
161 void
go_xml_node_set_double(xmlNodePtr node,char const * name,double val,int precision)162 go_xml_node_set_double (xmlNodePtr node, char const *name, double val,
163 		     int precision)
164 {
165 	char str[101 + DBL_DIG];
166 
167 	if (precision < 0 || precision > DBL_DIG)
168 		precision = DBL_DIG;
169 
170 	if (fabs (val) < 1e9 && fabs (val) > 1e-5)
171 		g_snprintf (str, 100 + DBL_DIG, "%.*g", precision, val);
172 	else
173 		g_snprintf (str, 100 + DBL_DIG, "%f", val);
174 
175 	go_xml_node_set_cstr (node, name, str);
176 }
177 
178 
179 gboolean
go_xml_node_get_gocolor(xmlNodePtr node,char const * name,GOColor * res)180 go_xml_node_get_gocolor (xmlNodePtr node, char const *name, GOColor *res)
181 {
182 	xmlChar *color;
183 	int r, g, b;
184 
185 	color = xmlGetProp (node, CC2XML (name));
186 	if (color == NULL)
187 		return FALSE;
188 	if (sscanf (CXML2C (color), "%X:%X:%X", &r, &g, &b) == 3) {
189 		r >>= 8;
190 		g >>= 8;
191 		b >>= 8;
192 		*res = GO_COLOR_FROM_RGBA (r,g,b,0xff);
193 		xmlFree (color);
194 		return TRUE;
195 	}
196 	xmlFree (color);
197 	return FALSE;
198 }
199 
200 void
go_xml_node_set_gocolor(xmlNodePtr node,char const * name,GOColor val)201 go_xml_node_set_gocolor (xmlNodePtr node, char const *name, GOColor val)
202 {
203 	unsigned r, g, b;
204 	char str[4 * sizeof (val)];
205 
206 	GO_COLOR_TO_RGB (val, &r, &g, &b);
207 	sprintf (str, "%X:%X:%X", r, g, b);
208 	go_xml_node_set_cstr (node, name, str);
209 }
210 
211 gboolean
go_xml_node_get_enum(xmlNodePtr node,char const * name,GType etype,gint * val)212 go_xml_node_get_enum (xmlNodePtr node, char const *name, GType etype, gint *val)
213 {
214 	GEnumClass *eclass = G_ENUM_CLASS (g_type_class_peek (etype));
215 	GEnumValue *ev;
216 	xmlChar *s;
217 	int i;
218 
219 	s = xmlGetProp (node, CC2XML (name));
220 	if (s == NULL)
221 		return FALSE;
222 
223 	ev = g_enum_get_value_by_name (eclass, CXML2C (s));
224 	if (!ev) ev = g_enum_get_value_by_nick (eclass, CXML2C (s));
225 	if (!ev && go_xml_node_get_int (node, name, &i))
226 		/* Check that the value is valid.  */
227 		ev = g_enum_get_value (eclass, i);
228 	xmlFree (s);
229 	if (!ev) return FALSE;
230 
231 	*val = ev->value;
232 	return TRUE;
233 }
234 
235 void
go_xml_node_set_enum(xmlNodePtr node,char const * name,GType etype,gint val)236 go_xml_node_set_enum (xmlNodePtr node, char const *name, GType etype, gint val)
237 {
238 	GEnumClass *eclass = G_ENUM_CLASS (g_type_class_peek (etype));
239 	GEnumValue *ev = g_enum_get_value (eclass, val);
240 
241 	if (ev)
242 		go_xml_node_set_cstr (node, name, ev->value_name);
243 	else
244 		g_warning ("Invalid value %d for type %s",
245 			   val, g_type_name (etype));
246 }
247 
248 /*************************************************************************/
249 
250 /**
251  * go_xml_get_child_by_name: (skip)
252  * @tree: #xmlNode
253  * @name: child name
254  *
255  * Returns: (transfer none): the child with @name as name if any.
256  **/
257 xmlNode *
go_xml_get_child_by_name(xmlNode const * parent,char const * child_name)258 go_xml_get_child_by_name (xmlNode const *parent, char const *child_name)
259 {
260 	xmlNode *child;
261 
262 	g_return_val_if_fail (parent != NULL, NULL);
263 	g_return_val_if_fail (child_name != NULL, NULL);
264 
265 	for (child = parent->xmlChildrenNode; child != NULL; child = child->next) {
266 		if (xmlStrcmp (child->name, CC2XML (child_name)) == 0) {
267 			return child;
268 		}
269 	}
270 	return NULL;
271 }
272 
273 /**
274  * go_xml_get_child_by_name_no_lang: (skip)
275  * @tree: #xmlNode
276  * @name: child name
277  *
278  * Returns: (transfer none): the child with @name as name and withou "xml:lang"
279  * attribute if any.
280  **/
281 xmlNode *
go_xml_get_child_by_name_no_lang(xmlNode const * parent,char const * name)282 go_xml_get_child_by_name_no_lang (xmlNode const *parent, char const *name)
283 {
284 	xmlNodePtr node;
285 
286 	g_return_val_if_fail (parent != NULL, NULL);
287 	g_return_val_if_fail (name != NULL, NULL);
288 
289 	for (node = parent->xmlChildrenNode; node != NULL; node = node->next) {
290 		xmlChar *lang;
291 
292 		if (node->name == NULL || strcmp (CXML2C (node->name), name) != 0) {
293 			continue;
294 		}
295 		lang = xmlGetProp (node, CC2XML ("xml:lang"));
296 		if (lang == NULL) {
297 			return node;
298 		}
299 		xmlFree (lang);
300 	}
301 
302 	return NULL;
303 }
304 
305 /**
306  * go_xml_get_child_by_name_by_lang: (skip)
307  * @tree: #xmlNode
308  * @name: child name
309  *
310  * Returns: (transfer none): the child with @name as name and with "xml:lang"
311  * attribute corresponding to the preferred language.
312  **/
313 xmlNode *
go_xml_get_child_by_name_by_lang(xmlNode const * parent,gchar const * name)314 go_xml_get_child_by_name_by_lang (xmlNode const *parent, gchar const *name)
315 {
316 	xmlNodePtr   best_node = NULL, node;
317 	gint         best_lang_score = INT_MAX;
318 	char const * const *langs = g_get_language_names ();
319 
320 	g_return_val_if_fail (parent != NULL, NULL);
321 	g_return_val_if_fail (name != NULL, NULL);
322 
323 	for (node = parent->xmlChildrenNode; node != NULL; node = node->next) {
324 		xmlChar *lang;
325 
326 		if (node->name == NULL || strcmp (CXML2C (node->name), name) != 0)
327 			continue;
328 
329 		lang = xmlGetProp (node, CC2XML ("lang"));
330 		if (lang != NULL) {
331 			gint i;
332 
333 			for (i = 0; langs[i] != NULL && i < best_lang_score; i++) {
334 				if (strcmp (langs[i], CXML2C (lang)) == 0) {
335 					best_node = node;
336 					best_lang_score = i;
337 				}
338 			}
339 			xmlFree (lang);
340 		} else if (best_node == NULL)
341 			best_node = node;
342 
343 		if (best_lang_score == 0)
344 			return best_node;
345 	}
346 
347 	return best_node;
348 }
349 
350 /**
351  * go_xml_out_add_double:
352  * @output: destination
353  * @id: (allow-none): attribute name
354  * @d: value
355  *
356  * Output a representation of @d that will be read back without loss of
357  * precision.
358  */
359 void
go_xml_out_add_double(GsfXMLOut * output,char const * id,double d)360 go_xml_out_add_double (GsfXMLOut *output, char const *id, double d)
361 {
362 	GString *str = g_string_new (NULL);
363 	go_dtoa (str, "!g", d);
364 	gsf_xml_out_add_cstr (output, id, str->str);
365 	g_string_free (str, TRUE);
366 }
367 
368 #ifdef GOFFICE_WITH_LONG_DOUBLE
369 /**
370  * go_xml_out_add_long_double:
371  * @output: destination
372  * @id: (allow-none): attribute name
373  * @ld: value
374  *
375  * Output a representation of @ld that will be read back without loss of
376  * precision.
377  */
378 void
go_xml_out_add_long_double(GsfXMLOut * output,char const * id,long double ld)379 go_xml_out_add_long_double (GsfXMLOut *output, char const *id, long double ld)
380 {
381 	GString *str = g_string_new (NULL);
382 	go_dtoa (str, "!Lg", ld);
383 	gsf_xml_out_add_cstr (output, id, str->str);
384 	g_string_free (str, TRUE);
385 }
386 #endif
387 
388 
389 void
go_xml_out_add_color(GsfXMLOut * output,char const * id,GOColor c)390 go_xml_out_add_color (GsfXMLOut *output, char const *id, GOColor c)
391 {
392 	char *str = go_color_as_str (c);
393 	gsf_xml_out_add_cstr_unchecked (output, id, str);
394 	g_free (str);
395 }
396 
397 static GSList *xml_in_docs;
398 
399 /**
400  * _go_libxml_extras_shutdown: (skip)
401  */
402 void
_go_libxml_extras_shutdown(void)403 _go_libxml_extras_shutdown (void)
404 {
405 	GSList *l;
406 
407 	for (l = xml_in_docs; l; l = l->next) {
408 		GsfXMLInDoc **pdoc = l->data;
409 		gsf_xml_in_doc_free (*pdoc);
410 		*pdoc = NULL;
411 	}
412 	g_slist_free (xml_in_docs);
413 	xml_in_docs = NULL;
414 }
415 
416 void
go_xml_in_doc_dispose_on_exit(GsfXMLInDoc ** pdoc)417 go_xml_in_doc_dispose_on_exit (GsfXMLInDoc **pdoc)
418 {
419 	xml_in_docs = g_slist_prepend (xml_in_docs, pdoc);
420 }
421