Code

A simple layout document as to what, why and how is cppification.
[inkscape.git] / src / xml / repr-util.cpp
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;
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()
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];
216 char *
217 sp_xml_ns_auto_prefix(char const *uri)
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;
243 gchar const *
244 sp_xml_ns_uri_prefix(gchar const *uri, gchar const *suggested)
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;
299 gchar const *
300 sp_xml_ns_prefix_uri(gchar const *prefix)
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;
322 double sp_repr_get_double_attribute(Inkscape::XML::Node *repr, char const *key, double def)
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);
336 long long int sp_repr_get_int_attribute(Inkscape::XML::Node *repr, char const *key, long long int def)
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);
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)
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).
396        
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 */
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)
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;
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 )
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;
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)
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;
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)
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     }
519 unsigned int
520 sp_repr_get_int(Inkscape::XML::Node *repr, gchar const *key, int *val)
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;
538 unsigned int
539 sp_repr_get_double(Inkscape::XML::Node *repr, gchar const *key, double *val)
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;
557 unsigned int
558 sp_repr_set_boolean(Inkscape::XML::Node *repr, gchar const *key, unsigned int val)
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;
567 unsigned int
568 sp_repr_set_int(Inkscape::XML::Node *repr, gchar const *key, int val)
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;
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)
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;
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)
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;
617 unsigned sp_repr_set_point(Inkscape::XML::Node *repr, gchar const *key, Geom::Point const & val)
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;
629 unsigned int
630 sp_repr_get_point(Inkscape::XML::Node *repr, gchar const *key, Geom::Point *val)
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;
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 :