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"
40 #define OSB_NS_URI "http://www.openswatchbook.org/uri/2009/osb"
43 struct SPXMLNs {
44 SPXMLNs *next;
45 unsigned int uri, prefix;
46 };
48 /*#####################
49 # DEFINITIONS
50 #####################*/
52 #ifndef FALSE
53 # define FALSE 0
54 #endif
56 #ifndef TRUE
57 # define TRUE (!FALSE)
58 #endif
60 #ifndef MAX
61 # define MAX(a,b) (((a) < (b)) ? (b) : (a))
62 #endif
64 /*#####################
65 # FORWARD DECLARATIONS
66 #####################*/
68 static void sp_xml_ns_register_defaults();
69 static char *sp_xml_ns_auto_prefix(char const *uri);
71 /*#####################
72 # UTILITY
73 #####################*/
75 /**
76 * Locale-independent double to string conversion
77 */
78 unsigned int
79 sp_xml_dtoa(gchar *buf, double val, unsigned int tprec, unsigned int fprec, unsigned int padf)
80 {
81 double dival, fval, epsilon;
82 int idigits, ival, i;
83 i = 0;
84 if (val < 0.0) {
85 buf[i++] = '-';
86 val = -val;
87 }
88 /* Determine number of integral digits */
89 if (val >= 1.0) {
90 idigits = (int) floor(log10(val));
91 } else {
92 idigits = 0;
93 }
94 /* Determine the actual number of fractional digits */
95 fprec = MAX(fprec, tprec - idigits);
96 /* Find epsilon */
97 epsilon = 0.5 * pow(10.0, - (double) fprec);
98 /* Round value */
99 val += epsilon;
100 /* Extract integral and fractional parts */
101 dival = floor(val);
102 ival = (int) dival;
103 fval = val - dival;
104 /* Write integra */
105 if (ival > 0) {
106 char c[32];
107 int j;
108 j = 0;
109 while (ival > 0) {
110 c[32 - (++j)] = '0' + (ival % 10);
111 ival /= 10;
112 }
113 memcpy(buf + i, &c[32 - j], j);
114 i += j;
115 tprec -= j;
116 } else {
117 buf[i++] = '0';
118 tprec -= 1;
119 }
120 if ((fprec > 0) && (padf || (fval > epsilon))) {
121 buf[i++] = '.';
122 while ((fprec > 0) && (padf || (fval > epsilon))) {
123 fval *= 10.0;
124 dival = floor(fval);
125 fval -= dival;
126 buf[i++] = '0' + (int) dival;
127 fprec -= 1;
128 }
130 }
131 buf[i] = 0;
132 return i;
133 }
139 /*#####################
140 # MAIN
141 #####################*/
143 /**
144 * SPXMLNs
145 */
147 static SPXMLNs *namespaces=NULL;
149 /*
150 * There are the prefixes to use for the XML namespaces defined
151 * in repr.h
152 */
153 static void
154 sp_xml_ns_register_defaults()
155 {
156 static SPXMLNs defaults[11];
158 defaults[0].uri = g_quark_from_static_string(SP_SODIPODI_NS_URI);
159 defaults[0].prefix = g_quark_from_static_string("sodipodi");
160 defaults[0].next = &defaults[1];
162 defaults[1].uri = g_quark_from_static_string(SP_XLINK_NS_URI);
163 defaults[1].prefix = g_quark_from_static_string("xlink");
164 defaults[1].next = &defaults[2];
166 defaults[2].uri = g_quark_from_static_string(SP_SVG_NS_URI);
167 defaults[2].prefix = g_quark_from_static_string("svg");
168 defaults[2].next = &defaults[3];
170 defaults[3].uri = g_quark_from_static_string(SP_INKSCAPE_NS_URI);
171 defaults[3].prefix = g_quark_from_static_string("inkscape");
172 defaults[3].next = &defaults[4];
174 defaults[4].uri = g_quark_from_static_string(SP_RDF_NS_URI);
175 defaults[4].prefix = g_quark_from_static_string("rdf");
176 defaults[4].next = &defaults[5];
178 defaults[5].uri = g_quark_from_static_string(SP_CC_NS_URI);
179 defaults[5].prefix = g_quark_from_static_string("cc");
180 defaults[5].next = &defaults[6];
182 defaults[6].uri = g_quark_from_static_string(SP_DC_NS_URI);
183 defaults[6].prefix = g_quark_from_static_string("dc");
184 defaults[6].next = &defaults[7];
186 defaults[7].uri = g_quark_from_static_string(OSB_NS_URI);
187 defaults[7].prefix = g_quark_from_static_string("osb");
188 defaults[7].next = &defaults[8];
190 // Inkscape versions prior to 0.44 would write this namespace
191 // URI instead of the correct sodipodi namespace; by adding this
192 // entry to the table last (where it gets used for URI -> prefix
193 // lookups, but not prefix -> URI lookups), we effectively transfer
194 // elements in this namespace to the correct sodipodi namespace:
196 defaults[8].uri = g_quark_from_static_string(SP_BROKEN_SODIPODI_NS_URI);
197 defaults[8].prefix = g_quark_from_static_string("sodipodi");
198 defaults[8].next = &defaults[9];
200 // "Duck prion"
201 // This URL became widespread due to a bug in versions <= 0.43
203 defaults[9].uri = g_quark_from_static_string("http://inkscape.sourceforge.net/DTD/s odipodi-0.dtd");
204 defaults[9].prefix = g_quark_from_static_string("sodipodi");
205 defaults[9].next = &defaults[10];
207 // This namespace URI is being phased out by Creative Commons
209 defaults[10].uri = g_quark_from_static_string(SP_OLD_CC_NS_URI);
210 defaults[10].prefix = g_quark_from_static_string("cc");
211 defaults[10].next = NULL;
213 namespaces = &defaults[0];
214 }
216 char *
217 sp_xml_ns_auto_prefix(char const *uri)
218 {
219 char const *start, *end;
220 char *new_prefix;
221 start = uri;
222 while ((end = strpbrk(start, ":/"))) {
223 start = end + 1;
224 }
225 end = start + strspn(start, "abcdefghijklmnopqrstuvwxyz");
226 if (end == start) {
227 start = "ns";
228 end = start + 2;
229 }
230 new_prefix = g_strndup(start, end - start);
231 if (sp_xml_ns_prefix_uri(new_prefix)) {
232 char *temp;
233 int counter=0;
234 do {
235 temp = g_strdup_printf("%s%d", new_prefix, counter++);
236 } while (sp_xml_ns_prefix_uri(temp));
237 g_free(new_prefix);
238 new_prefix = temp;
239 }
240 return new_prefix;
241 }
243 gchar const *
244 sp_xml_ns_uri_prefix(gchar const *uri, gchar const *suggested)
245 {
246 char const *prefix;
248 if (!uri) return NULL;
250 if (!namespaces) {
251 sp_xml_ns_register_defaults();
252 }
254 GQuark const key = g_quark_from_string(uri);
255 prefix = NULL;
256 for ( SPXMLNs *iter=namespaces ; iter ; iter = iter->next ) {
257 if ( iter->uri == key ) {
258 prefix = g_quark_to_string(iter->prefix);
259 break;
260 }
261 }
263 if (!prefix) {
264 char *new_prefix;
265 SPXMLNs *ns;
266 if (suggested) {
267 GQuark const prefix_key=g_quark_from_string(suggested);
269 SPXMLNs *found=namespaces;
270 while ( found && found->prefix != prefix_key ) {
271 found = found->next;
272 }
274 if (found) { // prefix already used?
275 new_prefix = sp_xml_ns_auto_prefix(uri);
276 } else { // safe to use suggested
277 new_prefix = g_strdup(suggested);
278 }
279 } else {
280 new_prefix = sp_xml_ns_auto_prefix(uri);
281 }
283 ns = g_new(SPXMLNs, 1);
284 g_assert( ns != NULL );
285 ns->uri = g_quark_from_string(uri);
286 ns->prefix = g_quark_from_string(new_prefix);
288 g_free(new_prefix);
290 ns->next = namespaces;
291 namespaces = ns;
293 prefix = g_quark_to_string(ns->prefix);
294 }
296 return prefix;
297 }
299 gchar const *
300 sp_xml_ns_prefix_uri(gchar const *prefix)
301 {
302 SPXMLNs *iter;
303 char const *uri;
305 if (!prefix) return NULL;
307 if (!namespaces) {
308 sp_xml_ns_register_defaults();
309 }
311 GQuark const key = g_quark_from_string(prefix);
312 uri = NULL;
313 for ( iter = namespaces ; iter ; iter = iter->next ) {
314 if ( iter->prefix == key ) {
315 uri = g_quark_to_string(iter->uri);
316 break;
317 }
318 }
319 return uri;
320 }
322 double sp_repr_get_double_attribute(Inkscape::XML::Node *repr, char const *key, double 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 g_ascii_strtod(result, NULL);
334 }
336 long long int sp_repr_get_int_attribute(Inkscape::XML::Node *repr, char const *key, long long int def)
337 {
338 char *result;
340 g_return_val_if_fail(repr != NULL, def);
341 g_return_val_if_fail(key != NULL, def);
343 result = (char *) repr->attribute(key);
345 if (result == NULL) return def;
347 return atoll(result);
348 }
350 /**
351 * Works for different-parent objects, so long as they have a common ancestor. Return value:
352 * 0 positions are equivalent
353 * 1 first object's position is greater than the second
354 * -1 first object's position is less than the second
355 * @todo Rewrite this function's description to be understandable
356 */
357 int
358 sp_repr_compare_position(Inkscape::XML::Node *first, Inkscape::XML::Node *second)
359 {
360 int p1, p2;
361 if (sp_repr_parent(first) == sp_repr_parent(second)) {
362 /* Basic case - first and second have same parent */
363 p1 = first->position();
364 p2 = second->position();
365 } else {
366 /* Special case - the two objects have different parents. They
367 could be in different groups or on different layers for
368 instance. */
370 // Find the lowest common ancestor(LCA)
371 Inkscape::XML::Node *ancestor = LCA(first, second);
372 g_assert(ancestor != NULL);
374 if (ancestor == first) {
375 return 1;
376 } else if (ancestor == second) {
377 return -1;
378 } else {
379 Inkscape::XML::Node const *to_first = AncetreFils(first, ancestor);
380 Inkscape::XML::Node const *to_second = AncetreFils(second, ancestor);
381 g_assert(sp_repr_parent(to_second) == sp_repr_parent(to_first));
382 p1 = to_first->position();
383 p2 = to_second->position();
384 }
385 }
387 if (p1 > p2) return 1;
388 if (p1 < p2) return -1;
389 return 0;
391 /* effic: Assuming that the parent--child relationship is consistent
392 (i.e. that the parent really does contain first and second among
393 its list of children), it should be equivalent to walk along the
394 children and see which we encounter first (returning 0 iff first
395 == second).
397 Given that this function is used solely for sorting, we can use a
398 similar approach to do the sort: gather the things to be sorted,
399 into an STL vector (to allow random access and faster
400 traversals). Do a single pass of the parent's children; for each
401 child, do a pass on whatever items in the vector we haven't yet
402 encountered. If the child is found, then swap it to the
403 beginning of the yet-unencountered elements of the vector.
404 Continue until no more than one remains unencountered. --
405 pjrm */
406 }
408 /**
409 * @brief Find an element node using an unique attribute
410 *
411 * This function returns the first child of the specified node that has the attribute
412 * @c key equal to @c value. Note that this function does not recurse.
413 *
414 * @param repr The node to start from
415 * @param key The name of the attribute to use for comparisons
416 * @param value The value of the attribute to look for
417 * @relatesalso Inkscape::XML::Node
418 */
419 Inkscape::XML::Node *
420 sp_repr_lookup_child(Inkscape::XML::Node *repr,
421 gchar const *key,
422 gchar const *value)
423 {
424 g_return_val_if_fail(repr != NULL, NULL);
425 for ( Inkscape::XML::Node *child = repr->firstChild() ; child ; child = child->next() ) {
426 gchar const *child_value = child->attribute(key);
427 if ( (child_value == value) ||
428 (value && child_value && !strcmp(child_value, value)) )
429 {
430 return child;
431 }
432 }
433 return NULL;
434 }
436 /**
437 * @brief Find an element node with the given name
438 *
439 * This function searches the descendants of the specified node depth-first for
440 * the first XML node with the specified name.
441 *
442 * @param repr The node to start from
443 * @param name The name of the element node to find
444 * @param maxdepth Maximum search depth, or -1 for an unlimited depth
445 * @return A pointer to the matching Inkscape::XML::Node
446 * @relatesalso Inkscape::XML::Node
447 */
448 Inkscape::XML::Node *
449 sp_repr_lookup_name( Inkscape::XML::Node *repr, gchar const *name, gint maxdepth )
450 {
451 g_return_val_if_fail(repr != NULL, NULL);
452 g_return_val_if_fail(name != NULL, NULL);
454 GQuark const quark = g_quark_from_string(name);
456 if ( (GQuark)repr->code() == quark ) return repr;
457 if ( maxdepth == 0 ) return NULL;
459 // maxdepth == -1 means unlimited
460 if ( maxdepth == -1 ) maxdepth = 0;
462 Inkscape::XML::Node *found = NULL;
463 for (Inkscape::XML::Node *child = repr->firstChild() ; child && !found; child = child->next() ) {
464 found = sp_repr_lookup_name( child, name, maxdepth-1 );
465 }
467 return found;
468 }
470 /**
471 * Determine if the node is a 'title', 'desc' or 'metadata' element.
472 */
473 bool
474 sp_repr_is_meta_element(const Inkscape::XML::Node *node)
475 {
476 if (node == NULL) return false;
477 if (node->type() != Inkscape::XML::ELEMENT_NODE) return false;
478 gchar const *name = node->name();
479 if (name == NULL) return false;
480 if (!std::strcmp(name, "svg:title")) return true;
481 if (!std::strcmp(name, "svg:desc")) return true;
482 if (!std::strcmp(name, "svg:metadata")) return true;
483 return false;
484 }
486 /**
487 * Parses the boolean value of an attribute "key" in repr and sets val accordingly, or to FALSE if
488 * the attr is not set.
489 *
490 * \return TRUE if the attr was set, FALSE otherwise.
491 */
492 unsigned int
493 sp_repr_get_boolean(Inkscape::XML::Node *repr, gchar const *key, unsigned int *val)
494 {
495 gchar const *v;
497 g_return_val_if_fail(repr != NULL, FALSE);
498 g_return_val_if_fail(key != NULL, FALSE);
499 g_return_val_if_fail(val != NULL, FALSE);
501 v = repr->attribute(key);
503 if (v != NULL) {
504 if (!g_strcasecmp(v, "true") ||
505 !g_strcasecmp(v, "yes" ) ||
506 !g_strcasecmp(v, "y" ) ||
507 (atoi(v) != 0)) {
508 *val = TRUE;
509 } else {
510 *val = FALSE;
511 }
512 return TRUE;
513 } else {
514 *val = FALSE;
515 return FALSE;
516 }
517 }
519 unsigned int
520 sp_repr_get_int(Inkscape::XML::Node *repr, gchar const *key, int *val)
521 {
522 gchar const *v;
524 g_return_val_if_fail(repr != NULL, FALSE);
525 g_return_val_if_fail(key != NULL, FALSE);
526 g_return_val_if_fail(val != NULL, FALSE);
528 v = repr->attribute(key);
530 if (v != NULL) {
531 *val = atoi(v);
532 return TRUE;
533 }
535 return FALSE;
536 }
538 unsigned int
539 sp_repr_get_double(Inkscape::XML::Node *repr, gchar const *key, double *val)
540 {
541 gchar const *v;
543 g_return_val_if_fail(repr != NULL, FALSE);
544 g_return_val_if_fail(key != NULL, FALSE);
545 g_return_val_if_fail(val != NULL, FALSE);
547 v = repr->attribute(key);
549 if (v != NULL) {
550 *val = g_ascii_strtod(v, NULL);
551 return TRUE;
552 }
554 return FALSE;
555 }
557 unsigned int
558 sp_repr_set_boolean(Inkscape::XML::Node *repr, gchar const *key, unsigned int val)
559 {
560 g_return_val_if_fail(repr != NULL, FALSE);
561 g_return_val_if_fail(key != NULL, FALSE);
563 repr->setAttribute(key, (val) ? "true" : "false");
564 return true;
565 }
567 unsigned int
568 sp_repr_set_int(Inkscape::XML::Node *repr, gchar const *key, int val)
569 {
570 gchar c[32];
572 g_return_val_if_fail(repr != NULL, FALSE);
573 g_return_val_if_fail(key != NULL, FALSE);
575 g_snprintf(c, 32, "%d", val);
577 repr->setAttribute(key, c);
578 return true;
579 }
581 /**
582 * Set a property attribute to \a val [slightly rounded], in the format
583 * required for CSS properties: in particular, it never uses exponent
584 * notation.
585 */
586 unsigned int
587 sp_repr_set_css_double(Inkscape::XML::Node *repr, gchar const *key, double val)
588 {
589 g_return_val_if_fail(repr != NULL, FALSE);
590 g_return_val_if_fail(key != NULL, FALSE);
592 Inkscape::CSSOStringStream os;
593 os << val;
595 repr->setAttribute(key, os.str().c_str());
596 return true;
597 }
599 /**
600 * For attributes where an exponent is allowed.
601 *
602 * Not suitable for property attributes (fill-opacity, font-size etc.).
603 */
604 unsigned int
605 sp_repr_set_svg_double(Inkscape::XML::Node *repr, gchar const *key, double val)
606 {
607 g_return_val_if_fail(repr != NULL, FALSE);
608 g_return_val_if_fail(key != NULL, FALSE);
610 Inkscape::SVGOStringStream os;
611 os << val;
613 repr->setAttribute(key, os.str().c_str());
614 return true;
615 }
617 unsigned sp_repr_set_point(Inkscape::XML::Node *repr, gchar const *key, Geom::Point const & val)
618 {
619 g_return_val_if_fail(repr != NULL, FALSE);
620 g_return_val_if_fail(key != NULL, FALSE);
622 Inkscape::SVGOStringStream os;
623 os << val[Geom::X] << "," << val[Geom::Y];
625 repr->setAttribute(key, os.str().c_str());
626 return true;
627 }
629 unsigned int
630 sp_repr_get_point(Inkscape::XML::Node *repr, gchar const *key, Geom::Point *val)
631 {
632 g_return_val_if_fail(repr != NULL, FALSE);
633 g_return_val_if_fail(key != NULL, FALSE);
634 g_return_val_if_fail(val != NULL, FALSE);
636 gchar const *v = repr->attribute(key);
638 gchar ** strarray = g_strsplit(v, ",", 2);
640 if (strarray && strarray[0] && strarray[1]) {
641 double newx, newy;
642 newx = g_ascii_strtod(strarray[0], NULL);
643 newy = g_ascii_strtod(strarray[1], NULL);
644 g_strfreev (strarray);
645 *val = Geom::Point(newx, newy);
646 return TRUE;
647 }
649 g_strfreev (strarray);
650 return FALSE;
651 }
653 /*
654 Local Variables:
655 mode:c++
656 c-file-style:"stroustrup"
657 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
658 indent-tabs-mode:nil
659 fill-column:99
660 End:
661 */
662 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :