1 #define __SP_REPR_UTIL_C__
3 /** \file
4 * Miscellaneous helpers for reprs.
5 */
7 /*
8 * Authors:
9 * Lauris Kaplinski <lauris@ximian.com>
10 *
11 * Copyright (C) 1999-2000 Lauris Kaplinski
12 * Copyright (C) 2000-2001 Ximian, Inc.
13 * g++ port Copyright (C) 2003 Nathan Hurst
14 *
15 * Licensed under GNU GPL
16 */
18 #include "config.h"
20 #include <math.h>
22 #if HAVE_STRING_H
23 # include <string.h>
24 #endif
26 #if HAVE_STDLIB_H
27 # include <stdlib.h>
28 #endif
31 #include <glib.h>
33 #include "svg/stringstream.h"
34 #include "svg/css-ostringstream.h"
36 #include "xml/repr.h"
37 #include "xml/repr-sorting.h"
39 struct SPXMLNs {
40 SPXMLNs *next;
41 unsigned int uri, prefix;
42 };
44 /*#####################
45 # DEFINITIONS
46 #####################*/
48 #ifndef FALSE
49 # define FALSE 0
50 #endif
52 #ifndef TRUE
53 # define TRUE (!FALSE)
54 #endif
56 #ifndef MAX
57 # define MAX(a,b) (((a) < (b)) ? (b) : (a))
58 #endif
60 /*#####################
61 # FORWARD DECLARATIONS
62 #####################*/
64 static void sp_xml_ns_register_defaults();
65 static char *sp_xml_ns_auto_prefix(char const *uri);
67 /*#####################
68 # UTILITY
69 #####################*/
71 /**
72 * Locale-independent double to string conversion
73 */
74 unsigned int
75 sp_xml_dtoa(gchar *buf, double val, unsigned int tprec, unsigned int fprec, unsigned int padf)
76 {
77 double dival, fval, epsilon;
78 int idigits, ival, i;
79 i = 0;
80 if (val < 0.0) {
81 buf[i++] = '-';
82 val = -val;
83 }
84 /* Determine number of integral digits */
85 if (val >= 1.0) {
86 idigits = (int) floor(log10(val));
87 } else {
88 idigits = 0;
89 }
90 /* Determine the actual number of fractional digits */
91 fprec = MAX(fprec, tprec - idigits);
92 /* Find epsilon */
93 epsilon = 0.5 * pow(10.0, - (double) fprec);
94 /* Round value */
95 val += epsilon;
96 /* Extract integral and fractional parts */
97 dival = floor(val);
98 ival = (int) dival;
99 fval = val - dival;
100 /* Write integra */
101 if (ival > 0) {
102 char c[32];
103 int j;
104 j = 0;
105 while (ival > 0) {
106 c[32 - (++j)] = '0' + (ival % 10);
107 ival /= 10;
108 }
109 memcpy(buf + i, &c[32 - j], j);
110 i += j;
111 tprec -= j;
112 } else {
113 buf[i++] = '0';
114 tprec -= 1;
115 }
116 if ((fprec > 0) && (padf || (fval > epsilon))) {
117 buf[i++] = '.';
118 while ((fprec > 0) && (padf || (fval > epsilon))) {
119 fval *= 10.0;
120 dival = floor(fval);
121 fval -= dival;
122 buf[i++] = '0' + (int) dival;
123 fprec -= 1;
124 }
126 }
127 buf[i] = 0;
128 return i;
129 }
135 /*#####################
136 # MAIN
137 #####################*/
139 /**
140 * SPXMLNs
141 */
143 static SPXMLNs *namespaces=NULL;
145 /*
146 * There are the prefixes to use for the XML namespaces defined
147 * in repr.h
148 */
149 static void
150 sp_xml_ns_register_defaults()
151 {
152 static SPXMLNs defaults[8];
154 defaults[0].uri = g_quark_from_static_string(SP_SODIPODI_NS_URI);
155 defaults[0].prefix = g_quark_from_static_string("sodipodi");
156 defaults[0].next = &defaults[1];
158 defaults[1].uri = g_quark_from_static_string(SP_XLINK_NS_URI);
159 defaults[1].prefix = g_quark_from_static_string("xlink");
160 defaults[1].next = &defaults[2];
162 defaults[2].uri = g_quark_from_static_string(SP_SVG_NS_URI);
163 defaults[2].prefix = g_quark_from_static_string("svg");
164 defaults[2].next = &defaults[3];
166 defaults[3].uri = g_quark_from_static_string(SP_INKSCAPE_NS_URI);
167 defaults[3].prefix = g_quark_from_static_string("inkscape");
168 defaults[3].next = &defaults[4];
170 defaults[4].uri = g_quark_from_static_string(SP_RDF_NS_URI);
171 defaults[4].prefix = g_quark_from_static_string("rdf");
172 defaults[4].next = &defaults[5];
174 defaults[5].uri = g_quark_from_static_string(SP_CC_NS_URI);
175 defaults[5].prefix = g_quark_from_static_string("cc");
176 defaults[5].next = &defaults[6];
178 defaults[6].uri = g_quark_from_static_string(SP_DC_NS_URI);
179 defaults[6].prefix = g_quark_from_static_string("dc");
180 defaults[6].next = &defaults[7];
182 // Inkscape versions prior to 0.44 would write this namespace
183 // URI instead of the correct sodipodi namespace; by adding this
184 // entry to the table last (where it gets used for URI -> prefix
185 // lookups, but not prefix -> URI lookups), we effectively transfer
186 // elements in this namespace to the correct sodipodi namespace:
188 defaults[7].uri = g_quark_from_static_string(SP_BROKEN_SODIPODI_NS_URI);
189 defaults[7].prefix = g_quark_from_static_string("sodipodi");
190 defaults[7].next = NULL;
192 namespaces = &defaults[0];
193 }
195 char *
196 sp_xml_ns_auto_prefix(char const *uri)
197 {
198 char const *start, *end;
199 char *new_prefix;
200 start = uri;
201 while ((end = strpbrk(start, ":/"))) {
202 start = end + 1;
203 }
204 end = start + strspn(start, "abcdefghijklmnopqrstuvwxyz");
205 if (end == start) {
206 start = "ns";
207 end = start + 2;
208 }
209 new_prefix = g_strndup(start, end - start);
210 if (sp_xml_ns_prefix_uri(new_prefix)) {
211 char *temp;
212 int counter=0;
213 do {
214 temp = g_strdup_printf("%s%d", new_prefix, counter++);
215 } while (sp_xml_ns_prefix_uri(temp));
216 g_free(new_prefix);
217 new_prefix = temp;
218 }
219 return new_prefix;
220 }
222 gchar const *
223 sp_xml_ns_uri_prefix(gchar const *uri, gchar const *suggested)
224 {
225 char const *prefix;
227 if (!uri) return NULL;
229 if (!namespaces) {
230 sp_xml_ns_register_defaults();
231 }
233 GQuark const key = g_quark_from_string(uri);
234 prefix = NULL;
235 for ( SPXMLNs *iter=namespaces ; iter ; iter = iter->next ) {
236 if ( iter->uri == key ) {
237 prefix = g_quark_to_string(iter->prefix);
238 break;
239 }
240 }
242 if (!prefix) {
243 char *new_prefix;
244 SPXMLNs *ns;
245 if (suggested) {
246 GQuark const prefix_key=g_quark_from_string(suggested);
248 SPXMLNs *found=namespaces;
249 while ( found && found->prefix != prefix_key ) {
250 found = found->next;
251 }
253 if (found) { // prefix already used?
254 new_prefix = sp_xml_ns_auto_prefix(uri);
255 } else { // safe to use suggested
256 new_prefix = g_strdup(suggested);
257 }
258 } else {
259 new_prefix = sp_xml_ns_auto_prefix(uri);
260 }
262 ns = g_new(SPXMLNs, 1);
263 g_assert( ns != NULL );
264 ns->uri = g_quark_from_string(uri);
265 ns->prefix = g_quark_from_string(new_prefix);
267 g_free(new_prefix);
269 ns->next = namespaces;
270 namespaces = ns;
272 prefix = g_quark_to_string(ns->prefix);
273 }
275 return prefix;
276 }
278 gchar const *
279 sp_xml_ns_prefix_uri(gchar const *prefix)
280 {
281 SPXMLNs *iter;
282 char const *uri;
284 if (!prefix) return NULL;
286 if (!namespaces) {
287 sp_xml_ns_register_defaults();
288 }
290 GQuark const key = g_quark_from_string(prefix);
291 uri = NULL;
292 for ( iter = namespaces ; iter ; iter = iter->next ) {
293 if ( iter->prefix == key ) {
294 uri = g_quark_to_string(iter->uri);
295 break;
296 }
297 }
298 return uri;
299 }
301 double sp_repr_get_double_attribute(Inkscape::XML::Node *repr, char const *key, double def)
302 {
303 char *result;
305 g_return_val_if_fail(repr != NULL, def);
306 g_return_val_if_fail(key != NULL, def);
308 result = (char *) repr->attribute(key);
310 if (result == NULL) return def;
312 return g_ascii_strtod(result, NULL);
313 }
315 int sp_repr_get_int_attribute(Inkscape::XML::Node *repr, char const *key, int def)
316 {
317 char *result;
319 g_return_val_if_fail(repr != NULL, def);
320 g_return_val_if_fail(key != NULL, def);
322 result = (char *) repr->attribute(key);
324 if (result == NULL) return def;
326 return atoi(result);
327 }
329 /**
330 * Works for different-parent objects, so long as they have a common ancestor. Return value:
331 * 0 positions are equivalent
332 * 1 first object's position is greater than the second
333 * -1 first object's position is less than the second
334 */
335 int
336 sp_repr_compare_position(Inkscape::XML::Node *first, Inkscape::XML::Node *second)
337 {
338 int p1, p2;
339 if (sp_repr_parent(first) == sp_repr_parent(second)) {
340 /* Basic case - first and second have same parent */
341 p1 = first->position();
342 p2 = second->position();
343 } else {
344 /* Special case - the two objects have different parents. They
345 could be in different groups or on different layers for
346 instance. */
348 // Find the lowest common ancestor(LCA)
349 Inkscape::XML::Node *ancestor = LCA(first, second);
350 g_assert(ancestor != NULL);
352 if (ancestor == first) {
353 return 1;
354 } else if (ancestor == second) {
355 return -1;
356 } else {
357 Inkscape::XML::Node const *to_first = AncetreFils(first, ancestor);
358 Inkscape::XML::Node const *to_second = AncetreFils(second, ancestor);
359 g_assert(sp_repr_parent(to_second) == sp_repr_parent(to_first));
360 p1 = to_first->position();
361 p2 = to_second->position();
362 }
363 }
365 if (p1 > p2) return 1;
366 if (p1 < p2) return -1;
367 return 0;
369 /* effic: Assuming that the parent--child relationship is consistent
370 (i.e. that the parent really does contain first and second among
371 its list of children), it should be equivalent to walk along the
372 children and see which we encounter first (returning 0 iff first
373 == second).
375 Given that this function is used solely for sorting, we can use a
376 similar approach to do the sort: gather the things to be sorted,
377 into an STL vector (to allow random access and faster
378 traversals). Do a single pass of the parent's children; for each
379 child, do a pass on whatever items in the vector we haven't yet
380 encountered. If the child is found, then swap it to the
381 beginning of the yet-unencountered elements of the vector.
382 Continue until no more than one remains unencountered. --
383 pjrm */
384 }
386 /**
387 * lookup child by \a key, \a value.
388 */
389 Inkscape::XML::Node *
390 sp_repr_lookup_child(Inkscape::XML::Node *repr,
391 gchar const *key,
392 gchar const *value)
393 {
394 g_return_val_if_fail(repr != NULL, NULL);
395 for ( Inkscape::XML::Node *child = repr->firstChild() ; child ; child = child->next() ) {
396 gchar const *child_value = child->attribute(key);
397 if ( child_value == value ||
398 value && child_value && !strcmp(child_value, value) )
399 {
400 return child;
401 }
402 }
403 return NULL;
404 }
406 /**
407 * \brief Recursively find the Inkscape::XML::Node matching the given XML name.
408 * \return A pointer to the matching Inkscape::XML::Node
409 * \param repr The Inkscape::XML::Node to start from
410 * \param name The desired XML name
411 *
412 */
413 Inkscape::XML::Node *
414 sp_repr_lookup_name( Inkscape::XML::Node *repr, gchar const *name, gint maxdepth )
415 {
416 g_return_val_if_fail(repr != NULL, NULL);
417 g_return_val_if_fail(name != NULL, NULL);
419 GQuark const quark = g_quark_from_string(name);
421 if ( (GQuark)repr->code() == quark ) return repr;
422 if ( maxdepth == 0 ) return NULL;
424 // maxdepth == -1 means unlimited
425 if ( maxdepth == -1 ) maxdepth = 0;
427 Inkscape::XML::Node *found = NULL;
428 for (Inkscape::XML::Node *child = repr->firstChild() ; child && !found; child = child->next() ) {
429 found = sp_repr_lookup_name( child, name, maxdepth-1 );
430 }
432 return found;
433 }
435 /**
436 * Parses the boolean value of an attribute "key" in repr and sets val accordingly, or to FALSE if
437 * the attr is not set.
438 *
439 * \return TRUE if the attr was set, FALSE otherwise.
440 */
441 unsigned int
442 sp_repr_get_boolean(Inkscape::XML::Node *repr, gchar const *key, unsigned int *val)
443 {
444 gchar const *v;
446 g_return_val_if_fail(repr != NULL, FALSE);
447 g_return_val_if_fail(key != NULL, FALSE);
448 g_return_val_if_fail(val != NULL, FALSE);
450 v = repr->attribute(key);
452 if (v != NULL) {
453 if (!g_strcasecmp(v, "true") ||
454 !g_strcasecmp(v, "yes" ) ||
455 !g_strcasecmp(v, "y" ) ||
456 (atoi(v) != 0)) {
457 *val = TRUE;
458 } else {
459 *val = FALSE;
460 }
461 return TRUE;
462 } else {
463 *val = FALSE;
464 return FALSE;
465 }
466 }
468 unsigned int
469 sp_repr_get_int(Inkscape::XML::Node *repr, gchar const *key, int *val)
470 {
471 gchar const *v;
473 g_return_val_if_fail(repr != NULL, FALSE);
474 g_return_val_if_fail(key != NULL, FALSE);
475 g_return_val_if_fail(val != NULL, FALSE);
477 v = repr->attribute(key);
479 if (v != NULL) {
480 *val = atoi(v);
481 return TRUE;
482 }
484 return FALSE;
485 }
487 unsigned int
488 sp_repr_get_double(Inkscape::XML::Node *repr, gchar const *key, double *val)
489 {
490 gchar const *v;
492 g_return_val_if_fail(repr != NULL, FALSE);
493 g_return_val_if_fail(key != NULL, FALSE);
494 g_return_val_if_fail(val != NULL, FALSE);
496 v = repr->attribute(key);
498 if (v != NULL) {
499 *val = g_ascii_strtod(v, NULL);
500 return TRUE;
501 }
503 return FALSE;
504 }
506 unsigned int
507 sp_repr_set_boolean(Inkscape::XML::Node *repr, gchar const *key, unsigned int val)
508 {
509 g_return_val_if_fail(repr != NULL, FALSE);
510 g_return_val_if_fail(key != NULL, FALSE);
512 repr->setAttribute(key, (val) ? "true" : "false");
513 return true;
514 }
516 unsigned int
517 sp_repr_set_int(Inkscape::XML::Node *repr, gchar const *key, int val)
518 {
519 gchar c[32];
521 g_return_val_if_fail(repr != NULL, FALSE);
522 g_return_val_if_fail(key != NULL, FALSE);
524 g_snprintf(c, 32, "%d", val);
526 repr->setAttribute(key, c);
527 return true;
528 }
530 /**
531 * Set a property attribute to \a val [slightly rounded], in the format
532 * required for CSS properties: in particular, it never uses exponent
533 * notation.
534 */
535 unsigned int
536 sp_repr_set_css_double(Inkscape::XML::Node *repr, gchar const *key, double val)
537 {
538 g_return_val_if_fail(repr != NULL, FALSE);
539 g_return_val_if_fail(key != NULL, FALSE);
541 Inkscape::CSSOStringStream os;
542 os << val;
544 repr->setAttribute(key, os.str().c_str());
545 return true;
546 }
548 /**
549 * For attributes where an exponent is allowed.
550 *
551 * Not suitable for property attributes (fill-opacity, font-size etc.).
552 */
553 unsigned int
554 sp_repr_set_svg_double(Inkscape::XML::Node *repr, gchar const *key, double val)
555 {
556 g_return_val_if_fail(repr != NULL, FALSE);
557 g_return_val_if_fail(key != NULL, FALSE);
559 Inkscape::SVGOStringStream os;
560 os << val;
562 repr->setAttribute(key, os.str().c_str());
563 return true;
564 }
567 /*
568 Local Variables:
569 mode:c++
570 c-file-style:"stroustrup"
571 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
572 indent-tabs-mode:nil
573 fill-column:99
574 End:
575 */
576 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :