1 /** \file
2 * Miscellaneous helpers for reprs.
3 */
5 /*
6 * Authors:
7 * Lauris Kaplinski <lauris@ximian.com>
8 * Jon A. Cruz <jon@joncruz.org>
9 *
10 * Copyright (C) 1999-2000 Lauris Kaplinski
11 * Copyright (C) 2000-2001 Ximian, Inc.
12 * g++ port Copyright (C) 2003 Nathan Hurst
13 *
14 * Licensed under GNU GPL
15 */
17 #include "config.h"
19 #include <math.h>
21 #if HAVE_STRING_H
22 # include <cstring>
23 #endif
25 #if HAVE_STDLIB_H
26 # include <cstdlib>
27 #endif
30 #include <glib.h>
31 #include <2geom/point.h>
32 #include "svg/stringstream.h"
33 #include "svg/css-ostringstream.h"
35 #include "xml/repr.h"
36 #include "xml/repr-sorting.h"
39 #define OSB_NS_URI "http://www.openswatchbook.org/uri/2009/osb"
42 struct SPXMLNs {
43 SPXMLNs *next;
44 unsigned int uri, prefix;
45 };
47 /*#####################
48 # DEFINITIONS
49 #####################*/
51 #ifndef FALSE
52 # define FALSE 0
53 #endif
55 #ifndef TRUE
56 # define TRUE (!FALSE)
57 #endif
59 #ifndef MAX
60 # define MAX(a,b) (((a) < (b)) ? (b) : (a))
61 #endif
63 /*#####################
64 # FORWARD DECLARATIONS
65 #####################*/
67 static void sp_xml_ns_register_defaults();
68 static char *sp_xml_ns_auto_prefix(char const *uri);
70 /*#####################
71 # UTILITY
72 #####################*/
74 /**
75 * Locale-independent double to string conversion
76 */
77 unsigned int
78 sp_xml_dtoa(gchar *buf, double val, unsigned int tprec, unsigned int fprec, unsigned int padf)
79 {
80 double dival, fval, epsilon;
81 int idigits, ival, i;
82 i = 0;
83 if (val < 0.0) {
84 buf[i++] = '-';
85 val = -val;
86 }
87 /* Determine number of integral digits */
88 if (val >= 1.0) {
89 idigits = (int) floor(log10(val));
90 } else {
91 idigits = 0;
92 }
93 /* Determine the actual number of fractional digits */
94 fprec = MAX(fprec, tprec - idigits);
95 /* Find epsilon */
96 epsilon = 0.5 * pow(10.0, - (double) fprec);
97 /* Round value */
98 val += epsilon;
99 /* Extract integral and fractional parts */
100 dival = floor(val);
101 ival = (int) dival;
102 fval = val - dival;
103 /* Write integra */
104 if (ival > 0) {
105 char c[32];
106 int j;
107 j = 0;
108 while (ival > 0) {
109 c[32 - (++j)] = '0' + (ival % 10);
110 ival /= 10;
111 }
112 memcpy(buf + i, &c[32 - j], j);
113 i += j;
114 tprec -= j;
115 } else {
116 buf[i++] = '0';
117 tprec -= 1;
118 }
119 if ((fprec > 0) && (padf || (fval > epsilon))) {
120 buf[i++] = '.';
121 while ((fprec > 0) && (padf || (fval > epsilon))) {
122 fval *= 10.0;
123 dival = floor(fval);
124 fval -= dival;
125 buf[i++] = '0' + (int) dival;
126 fprec -= 1;
127 }
129 }
130 buf[i] = 0;
131 return i;
132 }
138 /*#####################
139 # MAIN
140 #####################*/
142 /**
143 * SPXMLNs
144 */
146 static SPXMLNs *namespaces=NULL;
148 /*
149 * There are the prefixes to use for the XML namespaces defined
150 * in repr.h
151 */
152 static void
153 sp_xml_ns_register_defaults()
154 {
155 static SPXMLNs defaults[11];
157 defaults[0].uri = g_quark_from_static_string(SP_SODIPODI_NS_URI);
158 defaults[0].prefix = g_quark_from_static_string("sodipodi");
159 defaults[0].next = &defaults[1];
161 defaults[1].uri = g_quark_from_static_string(SP_XLINK_NS_URI);
162 defaults[1].prefix = g_quark_from_static_string("xlink");
163 defaults[1].next = &defaults[2];
165 defaults[2].uri = g_quark_from_static_string(SP_SVG_NS_URI);
166 defaults[2].prefix = g_quark_from_static_string("svg");
167 defaults[2].next = &defaults[3];
169 defaults[3].uri = g_quark_from_static_string(SP_INKSCAPE_NS_URI);
170 defaults[3].prefix = g_quark_from_static_string("inkscape");
171 defaults[3].next = &defaults[4];
173 defaults[4].uri = g_quark_from_static_string(SP_RDF_NS_URI);
174 defaults[4].prefix = g_quark_from_static_string("rdf");
175 defaults[4].next = &defaults[5];
177 defaults[5].uri = g_quark_from_static_string(SP_CC_NS_URI);
178 defaults[5].prefix = g_quark_from_static_string("cc");
179 defaults[5].next = &defaults[6];
181 defaults[6].uri = g_quark_from_static_string(SP_DC_NS_URI);
182 defaults[6].prefix = g_quark_from_static_string("dc");
183 defaults[6].next = &defaults[7];
185 defaults[7].uri = g_quark_from_static_string(OSB_NS_URI);
186 defaults[7].prefix = g_quark_from_static_string("osb");
187 defaults[7].next = &defaults[8];
189 // Inkscape versions prior to 0.44 would write this namespace
190 // URI instead of the correct sodipodi namespace; by adding this
191 // entry to the table last (where it gets used for URI -> prefix
192 // lookups, but not prefix -> URI lookups), we effectively transfer
193 // elements in this namespace to the correct sodipodi namespace:
195 defaults[8].uri = g_quark_from_static_string(SP_BROKEN_SODIPODI_NS_URI);
196 defaults[8].prefix = g_quark_from_static_string("sodipodi");
197 defaults[8].next = &defaults[9];
199 // "Duck prion"
200 // This URL became widespread due to a bug in versions <= 0.43
202 defaults[9].uri = g_quark_from_static_string("http://inkscape.sourceforge.net/DTD/s odipodi-0.dtd");
203 defaults[9].prefix = g_quark_from_static_string("sodipodi");
204 defaults[9].next = &defaults[10];
206 // This namespace URI is being phased out by Creative Commons
208 defaults[10].uri = g_quark_from_static_string(SP_OLD_CC_NS_URI);
209 defaults[10].prefix = g_quark_from_static_string("cc");
210 defaults[10].next = NULL;
212 namespaces = &defaults[0];
213 }
215 char *
216 sp_xml_ns_auto_prefix(char const *uri)
217 {
218 char const *start, *end;
219 char *new_prefix;
220 start = uri;
221 while ((end = strpbrk(start, ":/"))) {
222 start = end + 1;
223 }
224 end = start + strspn(start, "abcdefghijklmnopqrstuvwxyz");
225 if (end == start) {
226 start = "ns";
227 end = start + 2;
228 }
229 new_prefix = g_strndup(start, end - start);
230 if (sp_xml_ns_prefix_uri(new_prefix)) {
231 char *temp;
232 int counter=0;
233 do {
234 temp = g_strdup_printf("%s%d", new_prefix, counter++);
235 } while (sp_xml_ns_prefix_uri(temp));
236 g_free(new_prefix);
237 new_prefix = temp;
238 }
239 return new_prefix;
240 }
242 gchar const *
243 sp_xml_ns_uri_prefix(gchar const *uri, gchar const *suggested)
244 {
245 char const *prefix;
247 if (!uri) return NULL;
249 if (!namespaces) {
250 sp_xml_ns_register_defaults();
251 }
253 GQuark const key = g_quark_from_string(uri);
254 prefix = NULL;
255 for ( SPXMLNs *iter=namespaces ; iter ; iter = iter->next ) {
256 if ( iter->uri == key ) {
257 prefix = g_quark_to_string(iter->prefix);
258 break;
259 }
260 }
262 if (!prefix) {
263 char *new_prefix;
264 SPXMLNs *ns;
265 if (suggested) {
266 GQuark const prefix_key=g_quark_from_string(suggested);
268 SPXMLNs *found=namespaces;
269 while ( found && found->prefix != prefix_key ) {
270 found = found->next;
271 }
273 if (found) { // prefix already used?
274 new_prefix = sp_xml_ns_auto_prefix(uri);
275 } else { // safe to use suggested
276 new_prefix = g_strdup(suggested);
277 }
278 } else {
279 new_prefix = sp_xml_ns_auto_prefix(uri);
280 }
282 ns = g_new(SPXMLNs, 1);
283 g_assert( ns != NULL );
284 ns->uri = g_quark_from_string(uri);
285 ns->prefix = g_quark_from_string(new_prefix);
287 g_free(new_prefix);
289 ns->next = namespaces;
290 namespaces = ns;
292 prefix = g_quark_to_string(ns->prefix);
293 }
295 return prefix;
296 }
298 gchar const *
299 sp_xml_ns_prefix_uri(gchar const *prefix)
300 {
301 SPXMLNs *iter;
302 char const *uri;
304 if (!prefix) return NULL;
306 if (!namespaces) {
307 sp_xml_ns_register_defaults();
308 }
310 GQuark const key = g_quark_from_string(prefix);
311 uri = NULL;
312 for ( iter = namespaces ; iter ; iter = iter->next ) {
313 if ( iter->prefix == key ) {
314 uri = g_quark_to_string(iter->uri);
315 break;
316 }
317 }
318 return uri;
319 }
321 double sp_repr_get_double_attribute(Inkscape::XML::Node *repr, char const *key, double def)
322 {
323 char *result;
325 g_return_val_if_fail(repr != NULL, def);
326 g_return_val_if_fail(key != NULL, def);
328 result = (char *) repr->attribute(key);
330 if (result == NULL) return def;
332 return g_ascii_strtod(result, NULL);
333 }
335 long long int sp_repr_get_int_attribute(Inkscape::XML::Node *repr, char const *key, long long int def)
336 {
337 char *result;
339 g_return_val_if_fail(repr != NULL, def);
340 g_return_val_if_fail(key != NULL, def);
342 result = (char *) repr->attribute(key);
344 if (result == NULL) return def;
346 return atoll(result);
347 }
349 /**
350 * Works for different-parent objects, so long as they have a common ancestor. Return value:
351 * 0 positions are equivalent
352 * 1 first object's position is greater than the second
353 * -1 first object's position is less than the second
354 * @todo Rewrite this function's description to be understandable
355 */
356 int sp_repr_compare_position(Inkscape::XML::Node const *first, Inkscape::XML::Node const *second)
357 {
358 int p1, p2;
359 if (sp_repr_parent(first) == sp_repr_parent(second)) {
360 /* Basic case - first and second have same parent */
361 p1 = first->position();
362 p2 = second->position();
363 } else {
364 /* Special case - the two objects have different parents. They
365 could be in different groups or on different layers for
366 instance. */
368 // Find the lowest common ancestor(LCA)
369 Inkscape::XML::Node const *ancestor = LCA(first, second);
370 g_assert(ancestor != NULL);
372 if (ancestor == first) {
373 return 1;
374 } else if (ancestor == second) {
375 return -1;
376 } else {
377 Inkscape::XML::Node const *to_first = AncetreFils(first, ancestor);
378 Inkscape::XML::Node const *to_second = AncetreFils(second, ancestor);
379 g_assert(sp_repr_parent(to_second) == sp_repr_parent(to_first));
380 p1 = to_first->position();
381 p2 = to_second->position();
382 }
383 }
385 if (p1 > p2) return 1;
386 if (p1 < p2) return -1;
387 return 0;
389 /* effic: Assuming that the parent--child relationship is consistent
390 (i.e. that the parent really does contain first and second among
391 its list of children), it should be equivalent to walk along the
392 children and see which we encounter first (returning 0 iff first
393 == second).
395 Given that this function is used solely for sorting, we can use a
396 similar approach to do the sort: gather the things to be sorted,
397 into an STL vector (to allow random access and faster
398 traversals). Do a single pass of the parent's children; for each
399 child, do a pass on whatever items in the vector we haven't yet
400 encountered. If the child is found, then swap it to the
401 beginning of the yet-unencountered elements of the vector.
402 Continue until no more than one remains unencountered. --
403 pjrm */
404 }
406 /**
407 * @brief Find an element node using an unique attribute
408 *
409 * This function returns the first child of the specified node that has the attribute
410 * @c key equal to @c value. Note that this function does not recurse.
411 *
412 * @param repr The node to start from
413 * @param key The name of the attribute to use for comparisons
414 * @param value The value of the attribute to look for
415 * @relatesalso Inkscape::XML::Node
416 */
417 Inkscape::XML::Node *
418 sp_repr_lookup_child(Inkscape::XML::Node *repr,
419 gchar const *key,
420 gchar const *value)
421 {
422 g_return_val_if_fail(repr != NULL, NULL);
423 for ( Inkscape::XML::Node *child = repr->firstChild() ; child ; child = child->next() ) {
424 gchar const *child_value = child->attribute(key);
425 if ( (child_value == value) ||
426 (value && child_value && !strcmp(child_value, value)) )
427 {
428 return child;
429 }
430 }
431 return NULL;
432 }
434 Inkscape::XML::Node const *sp_repr_lookup_name( Inkscape::XML::Node const *repr, gchar const *name, gint maxdepth )
435 {
436 Inkscape::XML::Node const *found = 0;
437 g_return_val_if_fail(repr != NULL, NULL);
438 g_return_val_if_fail(name != NULL, NULL);
440 GQuark const quark = g_quark_from_string(name);
442 if ( (GQuark)repr->code() == quark ) {
443 found = repr;
444 } else if ( maxdepth != 0 ) {
445 // maxdepth == -1 means unlimited
446 if ( maxdepth == -1 ) {
447 maxdepth = 0;
448 }
450 for (Inkscape::XML::Node const *child = repr->firstChild() ; child && !found; child = child->next() ) {
451 found = sp_repr_lookup_name( child, name, maxdepth - 1 );
452 }
453 }
454 return found;
455 }
457 Inkscape::XML::Node *sp_repr_lookup_name( Inkscape::XML::Node *repr, gchar const *name, gint maxdepth )
458 {
459 Inkscape::XML::Node const *found = sp_repr_lookup_name( const_cast<Inkscape::XML::Node const *>(repr), name, maxdepth );
460 return const_cast<Inkscape::XML::Node *>(found);
461 }
463 /**
464 * Determine if the node is a 'title', 'desc' or 'metadata' element.
465 */
466 bool
467 sp_repr_is_meta_element(const Inkscape::XML::Node *node)
468 {
469 if (node == NULL) return false;
470 if (node->type() != Inkscape::XML::ELEMENT_NODE) return false;
471 gchar const *name = node->name();
472 if (name == NULL) return false;
473 if (!std::strcmp(name, "svg:title")) return true;
474 if (!std::strcmp(name, "svg:desc")) return true;
475 if (!std::strcmp(name, "svg:metadata")) return true;
476 return false;
477 }
479 /**
480 * Parses the boolean value of an attribute "key" in repr and sets val accordingly, or to FALSE if
481 * the attr is not set.
482 *
483 * \return TRUE if the attr was set, FALSE otherwise.
484 */
485 unsigned int
486 sp_repr_get_boolean(Inkscape::XML::Node *repr, gchar const *key, unsigned int *val)
487 {
488 gchar const *v;
490 g_return_val_if_fail(repr != NULL, FALSE);
491 g_return_val_if_fail(key != NULL, FALSE);
492 g_return_val_if_fail(val != NULL, FALSE);
494 v = repr->attribute(key);
496 if (v != NULL) {
497 if (!g_strcasecmp(v, "true") ||
498 !g_strcasecmp(v, "yes" ) ||
499 !g_strcasecmp(v, "y" ) ||
500 (atoi(v) != 0)) {
501 *val = TRUE;
502 } else {
503 *val = FALSE;
504 }
505 return TRUE;
506 } else {
507 *val = FALSE;
508 return FALSE;
509 }
510 }
512 unsigned int
513 sp_repr_get_int(Inkscape::XML::Node *repr, gchar const *key, int *val)
514 {
515 gchar const *v;
517 g_return_val_if_fail(repr != NULL, FALSE);
518 g_return_val_if_fail(key != NULL, FALSE);
519 g_return_val_if_fail(val != NULL, FALSE);
521 v = repr->attribute(key);
523 if (v != NULL) {
524 *val = atoi(v);
525 return TRUE;
526 }
528 return FALSE;
529 }
531 unsigned int
532 sp_repr_get_double(Inkscape::XML::Node *repr, gchar const *key, double *val)
533 {
534 gchar const *v;
536 g_return_val_if_fail(repr != NULL, FALSE);
537 g_return_val_if_fail(key != NULL, FALSE);
538 g_return_val_if_fail(val != NULL, FALSE);
540 v = repr->attribute(key);
542 if (v != NULL) {
543 *val = g_ascii_strtod(v, NULL);
544 return TRUE;
545 }
547 return FALSE;
548 }
550 unsigned int
551 sp_repr_set_boolean(Inkscape::XML::Node *repr, gchar const *key, unsigned int val)
552 {
553 g_return_val_if_fail(repr != NULL, FALSE);
554 g_return_val_if_fail(key != NULL, FALSE);
556 repr->setAttribute(key, (val) ? "true" : "false");
557 return true;
558 }
560 unsigned int
561 sp_repr_set_int(Inkscape::XML::Node *repr, gchar const *key, int val)
562 {
563 gchar c[32];
565 g_return_val_if_fail(repr != NULL, FALSE);
566 g_return_val_if_fail(key != NULL, FALSE);
568 g_snprintf(c, 32, "%d", val);
570 repr->setAttribute(key, c);
571 return true;
572 }
574 /**
575 * Set a property attribute to \a val [slightly rounded], in the format
576 * required for CSS properties: in particular, it never uses exponent
577 * notation.
578 */
579 unsigned int
580 sp_repr_set_css_double(Inkscape::XML::Node *repr, gchar const *key, double val)
581 {
582 g_return_val_if_fail(repr != NULL, FALSE);
583 g_return_val_if_fail(key != NULL, FALSE);
585 Inkscape::CSSOStringStream os;
586 os << val;
588 repr->setAttribute(key, os.str().c_str());
589 return true;
590 }
592 /**
593 * For attributes where an exponent is allowed.
594 *
595 * Not suitable for property attributes (fill-opacity, font-size etc.).
596 */
597 unsigned int
598 sp_repr_set_svg_double(Inkscape::XML::Node *repr, gchar const *key, double val)
599 {
600 g_return_val_if_fail(repr != NULL, FALSE);
601 g_return_val_if_fail(key != NULL, FALSE);
603 Inkscape::SVGOStringStream os;
604 os << val;
606 repr->setAttribute(key, os.str().c_str());
607 return true;
608 }
610 unsigned sp_repr_set_point(Inkscape::XML::Node *repr, gchar const *key, Geom::Point const & val)
611 {
612 g_return_val_if_fail(repr != NULL, FALSE);
613 g_return_val_if_fail(key != NULL, FALSE);
615 Inkscape::SVGOStringStream os;
616 os << val[Geom::X] << "," << val[Geom::Y];
618 repr->setAttribute(key, os.str().c_str());
619 return true;
620 }
622 unsigned int
623 sp_repr_get_point(Inkscape::XML::Node *repr, gchar const *key, Geom::Point *val)
624 {
625 g_return_val_if_fail(repr != NULL, FALSE);
626 g_return_val_if_fail(key != NULL, FALSE);
627 g_return_val_if_fail(val != NULL, FALSE);
629 gchar const *v = repr->attribute(key);
631 gchar ** strarray = g_strsplit(v, ",", 2);
633 if (strarray && strarray[0] && strarray[1]) {
634 double newx, newy;
635 newx = g_ascii_strtod(strarray[0], NULL);
636 newy = g_ascii_strtod(strarray[1], NULL);
637 g_strfreev (strarray);
638 *val = Geom::Point(newx, newy);
639 return TRUE;
640 }
642 g_strfreev (strarray);
643 return FALSE;
644 }
646 /*
647 Local Variables:
648 mode:c++
649 c-file-style:"stroustrup"
650 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
651 indent-tabs-mode:nil
652 fill-column:99
653 End:
654 */
655 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 :