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 <cstring>
24 #endif
26 #if HAVE_STDLIB_H
27 # include <cstdlib>
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[10];
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 = &defaults[8];
192 // "Duck prion"
193 // This URL became widespread due to a bug in versions <= 0.43
195 defaults[8].uri = g_quark_from_static_string("http://inkscape.sourceforge.net/DTD/s odipodi-0.dtd");
196 defaults[8].prefix = g_quark_from_static_string("sodipodi");
197 defaults[8].next = &defaults[9];
199 // This namespace URI is being phased out by Creative Commons
201 defaults[9].uri = g_quark_from_static_string(SP_OLD_CC_NS_URI);
202 defaults[9].prefix = g_quark_from_static_string("cc");
203 defaults[9].next = NULL;
205 namespaces = &defaults[0];
206 }
208 char *
209 sp_xml_ns_auto_prefix(char const *uri)
210 {
211 char const *start, *end;
212 char *new_prefix;
213 start = uri;
214 while ((end = strpbrk(start, ":/"))) {
215 start = end + 1;
216 }
217 end = start + strspn(start, "abcdefghijklmnopqrstuvwxyz");
218 if (end == start) {
219 start = "ns";
220 end = start + 2;
221 }
222 new_prefix = g_strndup(start, end - start);
223 if (sp_xml_ns_prefix_uri(new_prefix)) {
224 char *temp;
225 int counter=0;
226 do {
227 temp = g_strdup_printf("%s%d", new_prefix, counter++);
228 } while (sp_xml_ns_prefix_uri(temp));
229 g_free(new_prefix);
230 new_prefix = temp;
231 }
232 return new_prefix;
233 }
235 gchar const *
236 sp_xml_ns_uri_prefix(gchar const *uri, gchar const *suggested)
237 {
238 char const *prefix;
240 if (!uri) return NULL;
242 if (!namespaces) {
243 sp_xml_ns_register_defaults();
244 }
246 GQuark const key = g_quark_from_string(uri);
247 prefix = NULL;
248 for ( SPXMLNs *iter=namespaces ; iter ; iter = iter->next ) {
249 if ( iter->uri == key ) {
250 prefix = g_quark_to_string(iter->prefix);
251 break;
252 }
253 }
255 if (!prefix) {
256 char *new_prefix;
257 SPXMLNs *ns;
258 if (suggested) {
259 GQuark const prefix_key=g_quark_from_string(suggested);
261 SPXMLNs *found=namespaces;
262 while ( found && found->prefix != prefix_key ) {
263 found = found->next;
264 }
266 if (found) { // prefix already used?
267 new_prefix = sp_xml_ns_auto_prefix(uri);
268 } else { // safe to use suggested
269 new_prefix = g_strdup(suggested);
270 }
271 } else {
272 new_prefix = sp_xml_ns_auto_prefix(uri);
273 }
275 ns = g_new(SPXMLNs, 1);
276 g_assert( ns != NULL );
277 ns->uri = g_quark_from_string(uri);
278 ns->prefix = g_quark_from_string(new_prefix);
280 g_free(new_prefix);
282 ns->next = namespaces;
283 namespaces = ns;
285 prefix = g_quark_to_string(ns->prefix);
286 }
288 return prefix;
289 }
291 gchar const *
292 sp_xml_ns_prefix_uri(gchar const *prefix)
293 {
294 SPXMLNs *iter;
295 char const *uri;
297 if (!prefix) return NULL;
299 if (!namespaces) {
300 sp_xml_ns_register_defaults();
301 }
303 GQuark const key = g_quark_from_string(prefix);
304 uri = NULL;
305 for ( iter = namespaces ; iter ; iter = iter->next ) {
306 if ( iter->prefix == key ) {
307 uri = g_quark_to_string(iter->uri);
308 break;
309 }
310 }
311 return uri;
312 }
314 double sp_repr_get_double_attribute(Inkscape::XML::Node *repr, char const *key, double def)
315 {
316 char *result;
318 g_return_val_if_fail(repr != NULL, def);
319 g_return_val_if_fail(key != NULL, def);
321 result = (char *) repr->attribute(key);
323 if (result == NULL) return def;
325 return g_ascii_strtod(result, NULL);
326 }
328 long long int sp_repr_get_int_attribute(Inkscape::XML::Node *repr, char const *key, long long int def)
329 {
330 char *result;
332 g_return_val_if_fail(repr != NULL, def);
333 g_return_val_if_fail(key != NULL, def);
335 result = (char *) repr->attribute(key);
337 if (result == NULL) return def;
339 return atoll(result);
340 }
342 /**
343 * Works for different-parent objects, so long as they have a common ancestor. Return value:
344 * 0 positions are equivalent
345 * 1 first object's position is greater than the second
346 * -1 first object's position is less than the second
347 */
348 int
349 sp_repr_compare_position(Inkscape::XML::Node *first, Inkscape::XML::Node *second)
350 {
351 int p1, p2;
352 if (sp_repr_parent(first) == sp_repr_parent(second)) {
353 /* Basic case - first and second have same parent */
354 p1 = first->position();
355 p2 = second->position();
356 } else {
357 /* Special case - the two objects have different parents. They
358 could be in different groups or on different layers for
359 instance. */
361 // Find the lowest common ancestor(LCA)
362 Inkscape::XML::Node *ancestor = LCA(first, second);
363 g_assert(ancestor != NULL);
365 if (ancestor == first) {
366 return 1;
367 } else if (ancestor == second) {
368 return -1;
369 } else {
370 Inkscape::XML::Node const *to_first = AncetreFils(first, ancestor);
371 Inkscape::XML::Node const *to_second = AncetreFils(second, ancestor);
372 g_assert(sp_repr_parent(to_second) == sp_repr_parent(to_first));
373 p1 = to_first->position();
374 p2 = to_second->position();
375 }
376 }
378 if (p1 > p2) return 1;
379 if (p1 < p2) return -1;
380 return 0;
382 /* effic: Assuming that the parent--child relationship is consistent
383 (i.e. that the parent really does contain first and second among
384 its list of children), it should be equivalent to walk along the
385 children and see which we encounter first (returning 0 iff first
386 == second).
388 Given that this function is used solely for sorting, we can use a
389 similar approach to do the sort: gather the things to be sorted,
390 into an STL vector (to allow random access and faster
391 traversals). Do a single pass of the parent's children; for each
392 child, do a pass on whatever items in the vector we haven't yet
393 encountered. If the child is found, then swap it to the
394 beginning of the yet-unencountered elements of the vector.
395 Continue until no more than one remains unencountered. --
396 pjrm */
397 }
399 /**
400 * lookup child by \a key, \a value.
401 */
402 Inkscape::XML::Node *
403 sp_repr_lookup_child(Inkscape::XML::Node *repr,
404 gchar const *key,
405 gchar const *value)
406 {
407 g_return_val_if_fail(repr != NULL, NULL);
408 for ( Inkscape::XML::Node *child = repr->firstChild() ; child ; child = child->next() ) {
409 gchar const *child_value = child->attribute(key);
410 if ( child_value == value ||
411 value && child_value && !strcmp(child_value, value) )
412 {
413 return child;
414 }
415 }
416 return NULL;
417 }
419 /**
420 * \brief Recursively find the Inkscape::XML::Node matching the given XML name.
421 * \return A pointer to the matching Inkscape::XML::Node
422 * \param repr The Inkscape::XML::Node to start from
423 * \param name The desired XML name
424 *
425 */
426 Inkscape::XML::Node *
427 sp_repr_lookup_name( Inkscape::XML::Node *repr, gchar const *name, gint maxdepth )
428 {
429 g_return_val_if_fail(repr != NULL, NULL);
430 g_return_val_if_fail(name != NULL, NULL);
432 GQuark const quark = g_quark_from_string(name);
434 if ( (GQuark)repr->code() == quark ) return repr;
435 if ( maxdepth == 0 ) return NULL;
437 // maxdepth == -1 means unlimited
438 if ( maxdepth == -1 ) maxdepth = 0;
440 Inkscape::XML::Node *found = NULL;
441 for (Inkscape::XML::Node *child = repr->firstChild() ; child && !found; child = child->next() ) {
442 found = sp_repr_lookup_name( child, name, maxdepth-1 );
443 }
445 return found;
446 }
448 /**
449 * Parses the boolean value of an attribute "key" in repr and sets val accordingly, or to FALSE if
450 * the attr is not set.
451 *
452 * \return TRUE if the attr was set, FALSE otherwise.
453 */
454 unsigned int
455 sp_repr_get_boolean(Inkscape::XML::Node *repr, gchar const *key, unsigned int *val)
456 {
457 gchar const *v;
459 g_return_val_if_fail(repr != NULL, FALSE);
460 g_return_val_if_fail(key != NULL, FALSE);
461 g_return_val_if_fail(val != NULL, FALSE);
463 v = repr->attribute(key);
465 if (v != NULL) {
466 if (!g_strcasecmp(v, "true") ||
467 !g_strcasecmp(v, "yes" ) ||
468 !g_strcasecmp(v, "y" ) ||
469 (atoi(v) != 0)) {
470 *val = TRUE;
471 } else {
472 *val = FALSE;
473 }
474 return TRUE;
475 } else {
476 *val = FALSE;
477 return FALSE;
478 }
479 }
481 unsigned int
482 sp_repr_get_int(Inkscape::XML::Node *repr, gchar const *key, int *val)
483 {
484 gchar const *v;
486 g_return_val_if_fail(repr != NULL, FALSE);
487 g_return_val_if_fail(key != NULL, FALSE);
488 g_return_val_if_fail(val != NULL, FALSE);
490 v = repr->attribute(key);
492 if (v != NULL) {
493 *val = atoi(v);
494 return TRUE;
495 }
497 return FALSE;
498 }
500 unsigned int
501 sp_repr_get_double(Inkscape::XML::Node *repr, gchar const *key, double *val)
502 {
503 gchar const *v;
505 g_return_val_if_fail(repr != NULL, FALSE);
506 g_return_val_if_fail(key != NULL, FALSE);
507 g_return_val_if_fail(val != NULL, FALSE);
509 v = repr->attribute(key);
511 if (v != NULL) {
512 *val = g_ascii_strtod(v, NULL);
513 return TRUE;
514 }
516 return FALSE;
517 }
519 unsigned int
520 sp_repr_set_boolean(Inkscape::XML::Node *repr, gchar const *key, unsigned int val)
521 {
522 g_return_val_if_fail(repr != NULL, FALSE);
523 g_return_val_if_fail(key != NULL, FALSE);
525 repr->setAttribute(key, (val) ? "true" : "false");
526 return true;
527 }
529 unsigned int
530 sp_repr_set_int(Inkscape::XML::Node *repr, gchar const *key, int val)
531 {
532 gchar c[32];
534 g_return_val_if_fail(repr != NULL, FALSE);
535 g_return_val_if_fail(key != NULL, FALSE);
537 g_snprintf(c, 32, "%d", val);
539 repr->setAttribute(key, c);
540 return true;
541 }
543 /**
544 * Set a property attribute to \a val [slightly rounded], in the format
545 * required for CSS properties: in particular, it never uses exponent
546 * notation.
547 */
548 unsigned int
549 sp_repr_set_css_double(Inkscape::XML::Node *repr, gchar const *key, double val)
550 {
551 g_return_val_if_fail(repr != NULL, FALSE);
552 g_return_val_if_fail(key != NULL, FALSE);
554 Inkscape::CSSOStringStream os;
555 os << val;
557 repr->setAttribute(key, os.str().c_str());
558 return true;
559 }
561 /**
562 * For attributes where an exponent is allowed.
563 *
564 * Not suitable for property attributes (fill-opacity, font-size etc.).
565 */
566 unsigned int
567 sp_repr_set_svg_double(Inkscape::XML::Node *repr, gchar const *key, double val)
568 {
569 g_return_val_if_fail(repr != NULL, FALSE);
570 g_return_val_if_fail(key != NULL, FALSE);
572 Inkscape::SVGOStringStream os;
573 os << val;
575 repr->setAttribute(key, os.str().c_str());
576 return true;
577 }
579 unsigned sp_repr_set_point(Inkscape::XML::Node *repr, gchar const *key, Geom::Point val)
580 {
581 g_return_val_if_fail(repr != NULL, FALSE);
582 g_return_val_if_fail(key != NULL, FALSE);
584 Inkscape::SVGOStringStream os;
585 os << val[Geom::X] << "," << val[Geom::Y];
587 repr->setAttribute(key, os.str().c_str());
588 return true;
589 }
591 unsigned int
592 sp_repr_get_point(Inkscape::XML::Node *repr, gchar const *key, Geom::Point *val)
593 {
594 g_return_val_if_fail(repr != NULL, FALSE);
595 g_return_val_if_fail(key != NULL, FALSE);
596 g_return_val_if_fail(val != NULL, FALSE);
598 gchar const *v = repr->attribute(key);
600 gchar ** strarray = g_strsplit(v, ",", 2);
602 if (strarray && strarray[0] && strarray[1]) {
603 double newx, newy;
604 newx = g_ascii_strtod(strarray[0], NULL);
605 newy = g_ascii_strtod(strarray[1], NULL);
606 g_strfreev (strarray);
607 *val = Geom::Point(newx, newy);
608 return TRUE;
609 }
611 g_strfreev (strarray);
612 return FALSE;
613 }
615 /*
616 Local Variables:
617 mode:c++
618 c-file-style:"stroustrup"
619 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
620 indent-tabs-mode:nil
621 fill-column:99
622 End:
623 */
624 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :