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>
32 #include <2geom/point.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 * Determine if the node is a 'title', 'desc' or 'metadata' element.
450 */
451 bool
452 sp_repr_is_meta_element(const Inkscape::XML::Node *node)
453 {
454 if (node == NULL) return false;
455 if (node->type() != Inkscape::XML::ELEMENT_NODE) return false;
456 gchar const *name = node->name();
457 if (name == NULL) return false;
458 if (!std::strcmp(name, "svg:title")) return true;
459 if (!std::strcmp(name, "svg:desc")) return true;
460 if (!std::strcmp(name, "svg:metadata")) return true;
461 return false;
462 }
464 /**
465 * Parses the boolean value of an attribute "key" in repr and sets val accordingly, or to FALSE if
466 * the attr is not set.
467 *
468 * \return TRUE if the attr was set, FALSE otherwise.
469 */
470 unsigned int
471 sp_repr_get_boolean(Inkscape::XML::Node *repr, gchar const *key, unsigned int *val)
472 {
473 gchar const *v;
475 g_return_val_if_fail(repr != NULL, FALSE);
476 g_return_val_if_fail(key != NULL, FALSE);
477 g_return_val_if_fail(val != NULL, FALSE);
479 v = repr->attribute(key);
481 if (v != NULL) {
482 if (!g_strcasecmp(v, "true") ||
483 !g_strcasecmp(v, "yes" ) ||
484 !g_strcasecmp(v, "y" ) ||
485 (atoi(v) != 0)) {
486 *val = TRUE;
487 } else {
488 *val = FALSE;
489 }
490 return TRUE;
491 } else {
492 *val = FALSE;
493 return FALSE;
494 }
495 }
497 unsigned int
498 sp_repr_get_int(Inkscape::XML::Node *repr, gchar const *key, int *val)
499 {
500 gchar const *v;
502 g_return_val_if_fail(repr != NULL, FALSE);
503 g_return_val_if_fail(key != NULL, FALSE);
504 g_return_val_if_fail(val != NULL, FALSE);
506 v = repr->attribute(key);
508 if (v != NULL) {
509 *val = atoi(v);
510 return TRUE;
511 }
513 return FALSE;
514 }
516 unsigned int
517 sp_repr_get_double(Inkscape::XML::Node *repr, gchar const *key, double *val)
518 {
519 gchar const *v;
521 g_return_val_if_fail(repr != NULL, FALSE);
522 g_return_val_if_fail(key != NULL, FALSE);
523 g_return_val_if_fail(val != NULL, FALSE);
525 v = repr->attribute(key);
527 if (v != NULL) {
528 *val = g_ascii_strtod(v, NULL);
529 return TRUE;
530 }
532 return FALSE;
533 }
535 unsigned int
536 sp_repr_set_boolean(Inkscape::XML::Node *repr, gchar const *key, unsigned int val)
537 {
538 g_return_val_if_fail(repr != NULL, FALSE);
539 g_return_val_if_fail(key != NULL, FALSE);
541 repr->setAttribute(key, (val) ? "true" : "false");
542 return true;
543 }
545 unsigned int
546 sp_repr_set_int(Inkscape::XML::Node *repr, gchar const *key, int val)
547 {
548 gchar c[32];
550 g_return_val_if_fail(repr != NULL, FALSE);
551 g_return_val_if_fail(key != NULL, FALSE);
553 g_snprintf(c, 32, "%d", val);
555 repr->setAttribute(key, c);
556 return true;
557 }
559 /**
560 * Set a property attribute to \a val [slightly rounded], in the format
561 * required for CSS properties: in particular, it never uses exponent
562 * notation.
563 */
564 unsigned int
565 sp_repr_set_css_double(Inkscape::XML::Node *repr, gchar const *key, double val)
566 {
567 g_return_val_if_fail(repr != NULL, FALSE);
568 g_return_val_if_fail(key != NULL, FALSE);
570 Inkscape::CSSOStringStream os;
571 os << val;
573 repr->setAttribute(key, os.str().c_str());
574 return true;
575 }
577 /**
578 * For attributes where an exponent is allowed.
579 *
580 * Not suitable for property attributes (fill-opacity, font-size etc.).
581 */
582 unsigned int
583 sp_repr_set_svg_double(Inkscape::XML::Node *repr, gchar const *key, double val)
584 {
585 g_return_val_if_fail(repr != NULL, FALSE);
586 g_return_val_if_fail(key != NULL, FALSE);
588 Inkscape::SVGOStringStream os;
589 os << val;
591 repr->setAttribute(key, os.str().c_str());
592 return true;
593 }
595 unsigned sp_repr_set_point(Inkscape::XML::Node *repr, gchar const *key, Geom::Point const & val)
596 {
597 g_return_val_if_fail(repr != NULL, FALSE);
598 g_return_val_if_fail(key != NULL, FALSE);
600 Inkscape::SVGOStringStream os;
601 os << val[Geom::X] << "," << val[Geom::Y];
603 repr->setAttribute(key, os.str().c_str());
604 return true;
605 }
607 unsigned int
608 sp_repr_get_point(Inkscape::XML::Node *repr, gchar const *key, Geom::Point *val)
609 {
610 g_return_val_if_fail(repr != NULL, FALSE);
611 g_return_val_if_fail(key != NULL, FALSE);
612 g_return_val_if_fail(val != NULL, FALSE);
614 gchar const *v = repr->attribute(key);
616 gchar ** strarray = g_strsplit(v, ",", 2);
618 if (strarray && strarray[0] && strarray[1]) {
619 double newx, newy;
620 newx = g_ascii_strtod(strarray[0], NULL);
621 newy = g_ascii_strtod(strarray[1], NULL);
622 g_strfreev (strarray);
623 *val = Geom::Point(newx, newy);
624 return TRUE;
625 }
627 g_strfreev (strarray);
628 return FALSE;
629 }
631 /*
632 Local Variables:
633 mode:c++
634 c-file-style:"stroustrup"
635 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
636 indent-tabs-mode:nil
637 fill-column:99
638 End:
639 */
640 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :