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 * @todo Rewrite this function's description to be understandable
348 */
349 int
350 sp_repr_compare_position(Inkscape::XML::Node *first, Inkscape::XML::Node *second)
351 {
352 int p1, p2;
353 if (sp_repr_parent(first) == sp_repr_parent(second)) {
354 /* Basic case - first and second have same parent */
355 p1 = first->position();
356 p2 = second->position();
357 } else {
358 /* Special case - the two objects have different parents. They
359 could be in different groups or on different layers for
360 instance. */
362 // Find the lowest common ancestor(LCA)
363 Inkscape::XML::Node *ancestor = LCA(first, second);
364 g_assert(ancestor != NULL);
366 if (ancestor == first) {
367 return 1;
368 } else if (ancestor == second) {
369 return -1;
370 } else {
371 Inkscape::XML::Node const *to_first = AncetreFils(first, ancestor);
372 Inkscape::XML::Node const *to_second = AncetreFils(second, ancestor);
373 g_assert(sp_repr_parent(to_second) == sp_repr_parent(to_first));
374 p1 = to_first->position();
375 p2 = to_second->position();
376 }
377 }
379 if (p1 > p2) return 1;
380 if (p1 < p2) return -1;
381 return 0;
383 /* effic: Assuming that the parent--child relationship is consistent
384 (i.e. that the parent really does contain first and second among
385 its list of children), it should be equivalent to walk along the
386 children and see which we encounter first (returning 0 iff first
387 == second).
389 Given that this function is used solely for sorting, we can use a
390 similar approach to do the sort: gather the things to be sorted,
391 into an STL vector (to allow random access and faster
392 traversals). Do a single pass of the parent's children; for each
393 child, do a pass on whatever items in the vector we haven't yet
394 encountered. If the child is found, then swap it to the
395 beginning of the yet-unencountered elements of the vector.
396 Continue until no more than one remains unencountered. --
397 pjrm */
398 }
400 /**
401 * @brief Find an element node using an unique attribute
402 *
403 * This function returns the first child of the specified node that has the attribute
404 * @c key equal to @c value. Note that this function does not recurse.
405 *
406 * @param repr The node to start from
407 * @param key The name of the attribute to use for comparisons
408 * @param value The value of the attribute to look for
409 * @relatesalso Inkscape::XML::Node
410 */
411 Inkscape::XML::Node *
412 sp_repr_lookup_child(Inkscape::XML::Node *repr,
413 gchar const *key,
414 gchar const *value)
415 {
416 g_return_val_if_fail(repr != NULL, NULL);
417 for ( Inkscape::XML::Node *child = repr->firstChild() ; child ; child = child->next() ) {
418 gchar const *child_value = child->attribute(key);
419 if ( (child_value == value) ||
420 (value && child_value && !strcmp(child_value, value)) )
421 {
422 return child;
423 }
424 }
425 return NULL;
426 }
428 /**
429 * @brief Find an element node with the given name
430 *
431 * This function searches the descendants of the specified node depth-first for
432 * the first XML node with the specified name.
433 *
434 * @param repr The node to start from
435 * @param name The name of the element node to find
436 * @param maxdepth Maximum search depth, or -1 for an unlimited depth
437 * @return A pointer to the matching Inkscape::XML::Node
438 * @relatesalso Inkscape::XML::Node
439 */
440 Inkscape::XML::Node *
441 sp_repr_lookup_name( Inkscape::XML::Node *repr, gchar const *name, gint maxdepth )
442 {
443 g_return_val_if_fail(repr != NULL, NULL);
444 g_return_val_if_fail(name != NULL, NULL);
446 GQuark const quark = g_quark_from_string(name);
448 if ( (GQuark)repr->code() == quark ) return repr;
449 if ( maxdepth == 0 ) return NULL;
451 // maxdepth == -1 means unlimited
452 if ( maxdepth == -1 ) maxdepth = 0;
454 Inkscape::XML::Node *found = NULL;
455 for (Inkscape::XML::Node *child = repr->firstChild() ; child && !found; child = child->next() ) {
456 found = sp_repr_lookup_name( child, name, maxdepth-1 );
457 }
459 return found;
460 }
462 /**
463 * Determine if the node is a 'title', 'desc' or 'metadata' element.
464 */
465 bool
466 sp_repr_is_meta_element(const Inkscape::XML::Node *node)
467 {
468 if (node == NULL) return false;
469 if (node->type() != Inkscape::XML::ELEMENT_NODE) return false;
470 gchar const *name = node->name();
471 if (name == NULL) return false;
472 if (!std::strcmp(name, "svg:title")) return true;
473 if (!std::strcmp(name, "svg:desc")) return true;
474 if (!std::strcmp(name, "svg:metadata")) return true;
475 return false;
476 }
478 /**
479 * Parses the boolean value of an attribute "key" in repr and sets val accordingly, or to FALSE if
480 * the attr is not set.
481 *
482 * \return TRUE if the attr was set, FALSE otherwise.
483 */
484 unsigned int
485 sp_repr_get_boolean(Inkscape::XML::Node *repr, gchar const *key, unsigned int *val)
486 {
487 gchar const *v;
489 g_return_val_if_fail(repr != NULL, FALSE);
490 g_return_val_if_fail(key != NULL, FALSE);
491 g_return_val_if_fail(val != NULL, FALSE);
493 v = repr->attribute(key);
495 if (v != NULL) {
496 if (!g_strcasecmp(v, "true") ||
497 !g_strcasecmp(v, "yes" ) ||
498 !g_strcasecmp(v, "y" ) ||
499 (atoi(v) != 0)) {
500 *val = TRUE;
501 } else {
502 *val = FALSE;
503 }
504 return TRUE;
505 } else {
506 *val = FALSE;
507 return FALSE;
508 }
509 }
511 unsigned int
512 sp_repr_get_int(Inkscape::XML::Node *repr, gchar const *key, int *val)
513 {
514 gchar const *v;
516 g_return_val_if_fail(repr != NULL, FALSE);
517 g_return_val_if_fail(key != NULL, FALSE);
518 g_return_val_if_fail(val != NULL, FALSE);
520 v = repr->attribute(key);
522 if (v != NULL) {
523 *val = atoi(v);
524 return TRUE;
525 }
527 return FALSE;
528 }
530 unsigned int
531 sp_repr_get_double(Inkscape::XML::Node *repr, gchar const *key, double *val)
532 {
533 gchar const *v;
535 g_return_val_if_fail(repr != NULL, FALSE);
536 g_return_val_if_fail(key != NULL, FALSE);
537 g_return_val_if_fail(val != NULL, FALSE);
539 v = repr->attribute(key);
541 if (v != NULL) {
542 *val = g_ascii_strtod(v, NULL);
543 return TRUE;
544 }
546 return FALSE;
547 }
549 unsigned int
550 sp_repr_set_boolean(Inkscape::XML::Node *repr, gchar const *key, unsigned int val)
551 {
552 g_return_val_if_fail(repr != NULL, FALSE);
553 g_return_val_if_fail(key != NULL, FALSE);
555 repr->setAttribute(key, (val) ? "true" : "false");
556 return true;
557 }
559 unsigned int
560 sp_repr_set_int(Inkscape::XML::Node *repr, gchar const *key, int val)
561 {
562 gchar c[32];
564 g_return_val_if_fail(repr != NULL, FALSE);
565 g_return_val_if_fail(key != NULL, FALSE);
567 g_snprintf(c, 32, "%d", val);
569 repr->setAttribute(key, c);
570 return true;
571 }
573 /**
574 * Set a property attribute to \a val [slightly rounded], in the format
575 * required for CSS properties: in particular, it never uses exponent
576 * notation.
577 */
578 unsigned int
579 sp_repr_set_css_double(Inkscape::XML::Node *repr, gchar const *key, double val)
580 {
581 g_return_val_if_fail(repr != NULL, FALSE);
582 g_return_val_if_fail(key != NULL, FALSE);
584 Inkscape::CSSOStringStream os;
585 os << val;
587 repr->setAttribute(key, os.str().c_str());
588 return true;
589 }
591 /**
592 * For attributes where an exponent is allowed.
593 *
594 * Not suitable for property attributes (fill-opacity, font-size etc.).
595 */
596 unsigned int
597 sp_repr_set_svg_double(Inkscape::XML::Node *repr, gchar const *key, double val)
598 {
599 g_return_val_if_fail(repr != NULL, FALSE);
600 g_return_val_if_fail(key != NULL, FALSE);
602 Inkscape::SVGOStringStream os;
603 os << val;
605 repr->setAttribute(key, os.str().c_str());
606 return true;
607 }
609 unsigned sp_repr_set_point(Inkscape::XML::Node *repr, gchar const *key, Geom::Point const & val)
610 {
611 g_return_val_if_fail(repr != NULL, FALSE);
612 g_return_val_if_fail(key != NULL, FALSE);
614 Inkscape::SVGOStringStream os;
615 os << val[Geom::X] << "," << val[Geom::Y];
617 repr->setAttribute(key, os.str().c_str());
618 return true;
619 }
621 unsigned int
622 sp_repr_get_point(Inkscape::XML::Node *repr, gchar const *key, Geom::Point *val)
623 {
624 g_return_val_if_fail(repr != NULL, FALSE);
625 g_return_val_if_fail(key != NULL, FALSE);
626 g_return_val_if_fail(val != NULL, FALSE);
628 gchar const *v = repr->attribute(key);
630 gchar ** strarray = g_strsplit(v, ",", 2);
632 if (strarray && strarray[0] && strarray[1]) {
633 double newx, newy;
634 newx = g_ascii_strtod(strarray[0], NULL);
635 newy = g_ascii_strtod(strarray[1], NULL);
636 g_strfreev (strarray);
637 *val = Geom::Point(newx, newy);
638 return TRUE;
639 }
641 g_strfreev (strarray);
642 return FALSE;
643 }
645 /*
646 Local Variables:
647 mode:c++
648 c-file-style:"stroustrup"
649 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
650 indent-tabs-mode:nil
651 fill-column:99
652 End:
653 */
654 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :