Code

3a6b9aee45e7c3b192388f45d0ac0611a4542966
[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 <string.h>
24 #endif
26 #if HAVE_STDLIB_H
27 # include <stdlib.h>
28 #endif
31 #include <glib.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;
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()
152     static SPXMLNs defaults[8];
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 = NULL;
192     namespaces = &defaults[0];
195 char *
196 sp_xml_ns_auto_prefix(char const *uri)
198     char const *start, *end;
199     char *new_prefix;
200     start = uri;
201     while ((end = strpbrk(start, ":/"))) {
202         start = end + 1;
203     }
204     end = start + strspn(start, "abcdefghijklmnopqrstuvwxyz");
205     if (end == start) {
206         start = "ns";
207         end = start + 2;
208     }
209     new_prefix = g_strndup(start, end - start);
210     if (sp_xml_ns_prefix_uri(new_prefix)) {
211         char *temp;
212         int counter=0;
213         do {
214             temp = g_strdup_printf("%s%d", new_prefix, counter++);
215         } while (sp_xml_ns_prefix_uri(temp));
216         g_free(new_prefix);
217         new_prefix = temp;
218     }
219     return new_prefix;
222 gchar const *
223 sp_xml_ns_uri_prefix(gchar const *uri, gchar const *suggested)
225     char const *prefix;
227     if (!uri) return NULL;
229     if (!namespaces) {
230         sp_xml_ns_register_defaults();
231     }
233     GQuark const key = g_quark_from_string(uri);
234     prefix = NULL;
235     for ( SPXMLNs *iter=namespaces ; iter ; iter = iter->next ) {
236         if ( iter->uri == key ) {
237             prefix = g_quark_to_string(iter->prefix);
238             break;
239         }
240     }
242     if (!prefix) {
243         char *new_prefix;
244         SPXMLNs *ns;
245         if (suggested) {
246             GQuark const prefix_key=g_quark_from_string(suggested);
248             SPXMLNs *found=namespaces;
249             while ( found && found->prefix != prefix_key ) {
250                 found = found->next;
251             }
253             if (found) { // prefix already used?
254                 new_prefix = sp_xml_ns_auto_prefix(uri);
255             } else { // safe to use suggested
256                 new_prefix = g_strdup(suggested);
257             }
258         } else {
259             new_prefix = sp_xml_ns_auto_prefix(uri);
260         }
262         ns = g_new(SPXMLNs, 1);
263         g_assert( ns != NULL );
264         ns->uri = g_quark_from_string(uri);
265         ns->prefix = g_quark_from_string(new_prefix);
267         g_free(new_prefix);
269         ns->next = namespaces;
270         namespaces = ns;
272         prefix = g_quark_to_string(ns->prefix);
273     }
275     return prefix;
278 gchar const *
279 sp_xml_ns_prefix_uri(gchar const *prefix)
281     SPXMLNs *iter;
282     char const *uri;
284     if (!prefix) return NULL;
286     if (!namespaces) {
287         sp_xml_ns_register_defaults();
288     }
290     GQuark const key = g_quark_from_string(prefix);
291     uri = NULL;
292     for ( iter = namespaces ; iter ; iter = iter->next ) {
293         if ( iter->prefix == key ) {
294             uri = g_quark_to_string(iter->uri);
295             break;
296         }
297     }
298     return uri;
301 double sp_repr_get_double_attribute(Inkscape::XML::Node *repr, char const *key, double def)
303     char *result;
305     g_return_val_if_fail(repr != NULL, def);
306     g_return_val_if_fail(key != NULL, def);
308     result = (char *) repr->attribute(key);
310     if (result == NULL) return def;
312     return g_ascii_strtod(result, NULL);
315 int sp_repr_get_int_attribute(Inkscape::XML::Node *repr, char const *key, int def)
317     char *result;
319     g_return_val_if_fail(repr != NULL, def);
320     g_return_val_if_fail(key != NULL, def);
322     result = (char *) repr->attribute(key);
324     if (result == NULL) return def;
326     return atoi(result);
329 /** 
330  *  Works for different-parent objects, so long as they have a common ancestor. Return value:
331  *    0    positions are equivalent
332  *    1    first object's position is greater than the second
333  *   -1    first object's position is less than the second
334  */
335 int
336 sp_repr_compare_position(Inkscape::XML::Node *first, Inkscape::XML::Node *second)
338     int p1, p2;
339     if (sp_repr_parent(first) == sp_repr_parent(second)) {
340         /* Basic case - first and second have same parent */
341         p1 = first->position();
342         p2 = second->position();
343     } else {
344         /* Special case - the two objects have different parents.  They
345            could be in different groups or on different layers for
346            instance. */
348         // Find the lowest common ancestor(LCA)
349         Inkscape::XML::Node *ancestor = LCA(first, second);
350         g_assert(ancestor != NULL);
352         if (ancestor == first) {
353             return 1;
354         } else if (ancestor == second) {
355             return -1;
356         } else {
357             Inkscape::XML::Node const *to_first = AncetreFils(first, ancestor);
358             Inkscape::XML::Node const *to_second = AncetreFils(second, ancestor);
359             g_assert(sp_repr_parent(to_second) == sp_repr_parent(to_first));
360             p1 = to_first->position();
361             p2 = to_second->position();
362         }
363     }
365     if (p1 > p2) return 1;
366     if (p1 < p2) return -1;
367     return 0;
369     /* effic: Assuming that the parent--child relationship is consistent
370        (i.e. that the parent really does contain first and second among
371        its list of children), it should be equivalent to walk along the
372        children and see which we encounter first (returning 0 iff first
373        == second).
374        
375        Given that this function is used solely for sorting, we can use a
376        similar approach to do the sort: gather the things to be sorted,
377        into an STL vector (to allow random access and faster
378        traversals).  Do a single pass of the parent's children; for each
379        child, do a pass on whatever items in the vector we haven't yet
380        encountered.  If the child is found, then swap it to the
381        beginning of the yet-unencountered elements of the vector.
382        Continue until no more than one remains unencountered.  --
383        pjrm */
386 /**
387  * lookup child by \a key, \a value.
388  */
389 Inkscape::XML::Node *
390 sp_repr_lookup_child(Inkscape::XML::Node *repr,
391                      gchar const *key,
392                      gchar const *value)
394     g_return_val_if_fail(repr != NULL, NULL);
395     for ( Inkscape::XML::Node *child = repr->firstChild() ; child ; child = child->next() ) {
396         gchar const *child_value = child->attribute(key);
397         if ( child_value == value ||
398              value && child_value && !strcmp(child_value, value) )
399         {
400             return child;
401         }
402     }
403     return NULL;
406 /**
407  *  \brief   Recursively find the Inkscape::XML::Node matching the given XML name.
408  *  \return  A pointer to the matching Inkscape::XML::Node
409  *  \param   repr    The Inkscape::XML::Node to start from
410  *  \param   name    The desired XML name
411  *  
412  */
413 Inkscape::XML::Node *
414 sp_repr_lookup_name( Inkscape::XML::Node *repr, gchar const *name, gint maxdepth )
416     g_return_val_if_fail(repr != NULL, NULL);
417     g_return_val_if_fail(name != NULL, NULL);
419     GQuark const quark = g_quark_from_string(name);
421     if ( (GQuark)repr->code() == quark ) return repr;
422     if ( maxdepth == 0 ) return NULL;
424     // maxdepth == -1 means unlimited
425     if ( maxdepth == -1 ) maxdepth = 0;
427     Inkscape::XML::Node *found = NULL;
428     for (Inkscape::XML::Node *child = repr->firstChild() ; child && !found; child = child->next() ) {
429         found = sp_repr_lookup_name( child, name, maxdepth-1 );
430     }
432     return found;
435 /**
436  * Parses the boolean value of an attribute "key" in repr and sets val accordingly, or to FALSE if
437  * the attr is not set.
438  *
439  * \return TRUE if the attr was set, FALSE otherwise.
440  */
441 unsigned int
442 sp_repr_get_boolean(Inkscape::XML::Node *repr, gchar const *key, unsigned int *val)
444     gchar const *v;
446     g_return_val_if_fail(repr != NULL, FALSE);
447     g_return_val_if_fail(key != NULL, FALSE);
448     g_return_val_if_fail(val != NULL, FALSE);
450     v = repr->attribute(key);
452     if (v != NULL) {
453         if (!g_strcasecmp(v, "true") ||
454             !g_strcasecmp(v, "yes" ) ||
455             !g_strcasecmp(v, "y"   ) ||
456             (atoi(v) != 0)) {
457             *val = TRUE;
458         } else {
459             *val = FALSE;
460         }
461         return TRUE;
462     } else {
463         *val = FALSE;
464         return FALSE;
465     }
468 unsigned int
469 sp_repr_get_int(Inkscape::XML::Node *repr, gchar const *key, int *val)
471     gchar const *v;
473     g_return_val_if_fail(repr != NULL, FALSE);
474     g_return_val_if_fail(key != NULL, FALSE);
475     g_return_val_if_fail(val != NULL, FALSE);
477     v = repr->attribute(key);
479     if (v != NULL) {
480         *val = atoi(v);
481         return TRUE;
482     }
484     return FALSE;
487 unsigned int
488 sp_repr_get_double(Inkscape::XML::Node *repr, gchar const *key, double *val)
490     gchar const *v;
492     g_return_val_if_fail(repr != NULL, FALSE);
493     g_return_val_if_fail(key != NULL, FALSE);
494     g_return_val_if_fail(val != NULL, FALSE);
496     v = repr->attribute(key);
498     if (v != NULL) {
499         *val = g_ascii_strtod(v, NULL);
500         return TRUE;
501     }
503     return FALSE;
506 unsigned int
507 sp_repr_set_boolean(Inkscape::XML::Node *repr, gchar const *key, unsigned int val)
509     g_return_val_if_fail(repr != NULL, FALSE);
510     g_return_val_if_fail(key != NULL, FALSE);
512     repr->setAttribute(key, (val) ? "true" : "false");
513     return true;
516 unsigned int
517 sp_repr_set_int(Inkscape::XML::Node *repr, gchar const *key, int val)
519     gchar c[32];
521     g_return_val_if_fail(repr != NULL, FALSE);
522     g_return_val_if_fail(key != NULL, FALSE);
524     g_snprintf(c, 32, "%d", val);
526     repr->setAttribute(key, c);
527     return true;
530 /**
531  * Set a property attribute to \a val [slightly rounded], in the format
532  * required for CSS properties: in particular, it never uses exponent
533  * notation.
534  */
535 unsigned int
536 sp_repr_set_css_double(Inkscape::XML::Node *repr, gchar const *key, double val)
538     g_return_val_if_fail(repr != NULL, FALSE);
539     g_return_val_if_fail(key != NULL, FALSE);
541     Inkscape::CSSOStringStream os;
542     os << val;
544     repr->setAttribute(key, os.str().c_str());
545     return true;
548 /**
549  * For attributes where an exponent is allowed.
550  *
551  * Not suitable for property attributes (fill-opacity, font-size etc.).
552  */
553 unsigned int
554 sp_repr_set_svg_double(Inkscape::XML::Node *repr, gchar const *key, double val)
556     g_return_val_if_fail(repr != NULL, FALSE);
557     g_return_val_if_fail(key != NULL, FALSE);
559     Inkscape::SVGOStringStream os;
560     os << val;
562     repr->setAttribute(key, os.str().c_str());
563     return true;
567 /*
568   Local Variables:
569   mode:c++
570   c-file-style:"stroustrup"
571   c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
572   indent-tabs-mode:nil
573   fill-column:99
574   End:
575 */
576 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :