83231e5da54586d686949ca60b97112382b84690
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[9];
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 = NULL;
199 namespaces = &defaults[0];
200 }
202 char *
203 sp_xml_ns_auto_prefix(char const *uri)
204 {
205 char const *start, *end;
206 char *new_prefix;
207 start = uri;
208 while ((end = strpbrk(start, ":/"))) {
209 start = end + 1;
210 }
211 end = start + strspn(start, "abcdefghijklmnopqrstuvwxyz");
212 if (end == start) {
213 start = "ns";
214 end = start + 2;
215 }
216 new_prefix = g_strndup(start, end - start);
217 if (sp_xml_ns_prefix_uri(new_prefix)) {
218 char *temp;
219 int counter=0;
220 do {
221 temp = g_strdup_printf("%s%d", new_prefix, counter++);
222 } while (sp_xml_ns_prefix_uri(temp));
223 g_free(new_prefix);
224 new_prefix = temp;
225 }
226 return new_prefix;
227 }
229 gchar const *
230 sp_xml_ns_uri_prefix(gchar const *uri, gchar const *suggested)
231 {
232 char const *prefix;
234 if (!uri) return NULL;
236 if (!namespaces) {
237 sp_xml_ns_register_defaults();
238 }
240 GQuark const key = g_quark_from_string(uri);
241 prefix = NULL;
242 for ( SPXMLNs *iter=namespaces ; iter ; iter = iter->next ) {
243 if ( iter->uri == key ) {
244 prefix = g_quark_to_string(iter->prefix);
245 break;
246 }
247 }
249 if (!prefix) {
250 char *new_prefix;
251 SPXMLNs *ns;
252 if (suggested) {
253 GQuark const prefix_key=g_quark_from_string(suggested);
255 SPXMLNs *found=namespaces;
256 while ( found && found->prefix != prefix_key ) {
257 found = found->next;
258 }
260 if (found) { // prefix already used?
261 new_prefix = sp_xml_ns_auto_prefix(uri);
262 } else { // safe to use suggested
263 new_prefix = g_strdup(suggested);
264 }
265 } else {
266 new_prefix = sp_xml_ns_auto_prefix(uri);
267 }
269 ns = g_new(SPXMLNs, 1);
270 g_assert( ns != NULL );
271 ns->uri = g_quark_from_string(uri);
272 ns->prefix = g_quark_from_string(new_prefix);
274 g_free(new_prefix);
276 ns->next = namespaces;
277 namespaces = ns;
279 prefix = g_quark_to_string(ns->prefix);
280 }
282 return prefix;
283 }
285 gchar const *
286 sp_xml_ns_prefix_uri(gchar const *prefix)
287 {
288 SPXMLNs *iter;
289 char const *uri;
291 if (!prefix) return NULL;
293 if (!namespaces) {
294 sp_xml_ns_register_defaults();
295 }
297 GQuark const key = g_quark_from_string(prefix);
298 uri = NULL;
299 for ( iter = namespaces ; iter ; iter = iter->next ) {
300 if ( iter->prefix == key ) {
301 uri = g_quark_to_string(iter->uri);
302 break;
303 }
304 }
305 return uri;
306 }
308 double sp_repr_get_double_attribute(Inkscape::XML::Node *repr, char const *key, double def)
309 {
310 char *result;
312 g_return_val_if_fail(repr != NULL, def);
313 g_return_val_if_fail(key != NULL, def);
315 result = (char *) repr->attribute(key);
317 if (result == NULL) return def;
319 return g_ascii_strtod(result, NULL);
320 }
322 int sp_repr_get_int_attribute(Inkscape::XML::Node *repr, char const *key, int def)
323 {
324 char *result;
326 g_return_val_if_fail(repr != NULL, def);
327 g_return_val_if_fail(key != NULL, def);
329 result = (char *) repr->attribute(key);
331 if (result == NULL) return def;
333 return atoi(result);
334 }
336 /**
337 * Works for different-parent objects, so long as they have a common ancestor. Return value:
338 * 0 positions are equivalent
339 * 1 first object's position is greater than the second
340 * -1 first object's position is less than the second
341 */
342 int
343 sp_repr_compare_position(Inkscape::XML::Node *first, Inkscape::XML::Node *second)
344 {
345 int p1, p2;
346 if (sp_repr_parent(first) == sp_repr_parent(second)) {
347 /* Basic case - first and second have same parent */
348 p1 = first->position();
349 p2 = second->position();
350 } else {
351 /* Special case - the two objects have different parents. They
352 could be in different groups or on different layers for
353 instance. */
355 // Find the lowest common ancestor(LCA)
356 Inkscape::XML::Node *ancestor = LCA(first, second);
357 g_assert(ancestor != NULL);
359 if (ancestor == first) {
360 return 1;
361 } else if (ancestor == second) {
362 return -1;
363 } else {
364 Inkscape::XML::Node const *to_first = AncetreFils(first, ancestor);
365 Inkscape::XML::Node const *to_second = AncetreFils(second, ancestor);
366 g_assert(sp_repr_parent(to_second) == sp_repr_parent(to_first));
367 p1 = to_first->position();
368 p2 = to_second->position();
369 }
370 }
372 if (p1 > p2) return 1;
373 if (p1 < p2) return -1;
374 return 0;
376 /* effic: Assuming that the parent--child relationship is consistent
377 (i.e. that the parent really does contain first and second among
378 its list of children), it should be equivalent to walk along the
379 children and see which we encounter first (returning 0 iff first
380 == second).
382 Given that this function is used solely for sorting, we can use a
383 similar approach to do the sort: gather the things to be sorted,
384 into an STL vector (to allow random access and faster
385 traversals). Do a single pass of the parent's children; for each
386 child, do a pass on whatever items in the vector we haven't yet
387 encountered. If the child is found, then swap it to the
388 beginning of the yet-unencountered elements of the vector.
389 Continue until no more than one remains unencountered. --
390 pjrm */
391 }
393 /**
394 * lookup child by \a key, \a value.
395 */
396 Inkscape::XML::Node *
397 sp_repr_lookup_child(Inkscape::XML::Node *repr,
398 gchar const *key,
399 gchar const *value)
400 {
401 g_return_val_if_fail(repr != NULL, NULL);
402 for ( Inkscape::XML::Node *child = repr->firstChild() ; child ; child = child->next() ) {
403 gchar const *child_value = child->attribute(key);
404 if ( child_value == value ||
405 value && child_value && !strcmp(child_value, value) )
406 {
407 return child;
408 }
409 }
410 return NULL;
411 }
413 /**
414 * \brief Recursively find the Inkscape::XML::Node matching the given XML name.
415 * \return A pointer to the matching Inkscape::XML::Node
416 * \param repr The Inkscape::XML::Node to start from
417 * \param name The desired XML name
418 *
419 */
420 Inkscape::XML::Node *
421 sp_repr_lookup_name( Inkscape::XML::Node *repr, gchar const *name, gint maxdepth )
422 {
423 g_return_val_if_fail(repr != NULL, NULL);
424 g_return_val_if_fail(name != NULL, NULL);
426 GQuark const quark = g_quark_from_string(name);
428 if ( (GQuark)repr->code() == quark ) return repr;
429 if ( maxdepth == 0 ) return NULL;
431 // maxdepth == -1 means unlimited
432 if ( maxdepth == -1 ) maxdepth = 0;
434 Inkscape::XML::Node *found = NULL;
435 for (Inkscape::XML::Node *child = repr->firstChild() ; child && !found; child = child->next() ) {
436 found = sp_repr_lookup_name( child, name, maxdepth-1 );
437 }
439 return found;
440 }
442 /**
443 * Parses the boolean value of an attribute "key" in repr and sets val accordingly, or to FALSE if
444 * the attr is not set.
445 *
446 * \return TRUE if the attr was set, FALSE otherwise.
447 */
448 unsigned int
449 sp_repr_get_boolean(Inkscape::XML::Node *repr, gchar const *key, unsigned int *val)
450 {
451 gchar const *v;
453 g_return_val_if_fail(repr != NULL, FALSE);
454 g_return_val_if_fail(key != NULL, FALSE);
455 g_return_val_if_fail(val != NULL, FALSE);
457 v = repr->attribute(key);
459 if (v != NULL) {
460 if (!g_strcasecmp(v, "true") ||
461 !g_strcasecmp(v, "yes" ) ||
462 !g_strcasecmp(v, "y" ) ||
463 (atoi(v) != 0)) {
464 *val = TRUE;
465 } else {
466 *val = FALSE;
467 }
468 return TRUE;
469 } else {
470 *val = FALSE;
471 return FALSE;
472 }
473 }
475 unsigned int
476 sp_repr_get_int(Inkscape::XML::Node *repr, gchar const *key, int *val)
477 {
478 gchar const *v;
480 g_return_val_if_fail(repr != NULL, FALSE);
481 g_return_val_if_fail(key != NULL, FALSE);
482 g_return_val_if_fail(val != NULL, FALSE);
484 v = repr->attribute(key);
486 if (v != NULL) {
487 *val = atoi(v);
488 return TRUE;
489 }
491 return FALSE;
492 }
494 unsigned int
495 sp_repr_get_double(Inkscape::XML::Node *repr, gchar const *key, double *val)
496 {
497 gchar const *v;
499 g_return_val_if_fail(repr != NULL, FALSE);
500 g_return_val_if_fail(key != NULL, FALSE);
501 g_return_val_if_fail(val != NULL, FALSE);
503 v = repr->attribute(key);
505 if (v != NULL) {
506 *val = g_ascii_strtod(v, NULL);
507 return TRUE;
508 }
510 return FALSE;
511 }
513 unsigned int
514 sp_repr_set_boolean(Inkscape::XML::Node *repr, gchar const *key, unsigned int val)
515 {
516 g_return_val_if_fail(repr != NULL, FALSE);
517 g_return_val_if_fail(key != NULL, FALSE);
519 repr->setAttribute(key, (val) ? "true" : "false");
520 return true;
521 }
523 unsigned int
524 sp_repr_set_int(Inkscape::XML::Node *repr, gchar const *key, int val)
525 {
526 gchar c[32];
528 g_return_val_if_fail(repr != NULL, FALSE);
529 g_return_val_if_fail(key != NULL, FALSE);
531 g_snprintf(c, 32, "%d", val);
533 repr->setAttribute(key, c);
534 return true;
535 }
537 /**
538 * Set a property attribute to \a val [slightly rounded], in the format
539 * required for CSS properties: in particular, it never uses exponent
540 * notation.
541 */
542 unsigned int
543 sp_repr_set_css_double(Inkscape::XML::Node *repr, gchar const *key, double val)
544 {
545 g_return_val_if_fail(repr != NULL, FALSE);
546 g_return_val_if_fail(key != NULL, FALSE);
548 Inkscape::CSSOStringStream os;
549 os << val;
551 repr->setAttribute(key, os.str().c_str());
552 return true;
553 }
555 /**
556 * For attributes where an exponent is allowed.
557 *
558 * Not suitable for property attributes (fill-opacity, font-size etc.).
559 */
560 unsigned int
561 sp_repr_set_svg_double(Inkscape::XML::Node *repr, gchar const *key, double val)
562 {
563 g_return_val_if_fail(repr != NULL, FALSE);
564 g_return_val_if_fail(key != NULL, FALSE);
566 Inkscape::SVGOStringStream os;
567 os << val;
569 repr->setAttribute(key, os.str().c_str());
570 return true;
571 }
574 /*
575 Local Variables:
576 mode:c++
577 c-file-style:"stroustrup"
578 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
579 indent-tabs-mode:nil
580 fill-column:99
581 End:
582 */
583 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :