Code

Pot and Dutch translation update
[inkscape.git] / src / id-clash.cpp
1 #define __ID_CLASH_C__
2 /** \file
3  * Routines for resolving ID clashes when importing or pasting.
4  *
5  * Authors:
6  *   Stephen Silver <sasilver@users.sourceforge.net>
7  *
8  * Copyright (C) 2008 authors
9  *
10  * Released under GNU GPL, read the file 'COPYING' for more information
11  */
13 #include <cstdlib>
14 #include <cstring>
15 #include <list>
16 #include <map>
17 #include <string>
18 #include <utility>
20 #include "extract-uri.h"
21 #include "id-clash.h"
22 #include "sp-object.h"
23 #include "style.h"
24 #include "xml/node.h"
25 #include "xml/repr.h"
27 typedef enum { REF_HREF, REF_STYLE, REF_URL, REF_CLIPBOARD } ID_REF_TYPE;
29 struct IdReference {
30     ID_REF_TYPE type;
31     SPObject *elem;
32     const char *attr;  // property or href-like attribute
33 };
35 typedef std::map<std::string, std::list<IdReference> > refmap_type;
37 typedef std::pair<SPObject*, std::string> id_changeitem_type;
38 typedef std::list<id_changeitem_type> id_changelist_type;
40 const char *href_like_attributes[] = {
41     "inkscape:connection-end",
42     "inkscape:connection-start",
43     "inkscape:href",
44     "inkscape:path-effect",
45     "inkscape:perspectiveID",
46     "inkscape:tiled-clone-of",
47     "xlink:href",
48 };
49 #define NUM_HREF_LIKE_ATTRIBUTES (sizeof(href_like_attributes) / sizeof(*href_like_attributes))
51 const SPIPaint SPStyle::* SPIPaint_members[] = {
52     &SPStyle::color,
53     &SPStyle::fill,
54     &SPStyle::stroke,
55 };
56 const char* SPIPaint_properties[] = {
57     "color",
58     "fill",
59     "stroke",
60 };
61 #define NUM_SPIPAINT_PROPERTIES (sizeof(SPIPaint_properties) / sizeof(*SPIPaint_properties))
63 const char* other_url_properties[] = {
64     "clip-path",
65     "color-profile",
66     "cursor",
67     "marker-end",
68     "marker-mid",
69     "marker-start",
70     "mask",
71 };
72 #define NUM_OTHER_URL_PROPERTIES (sizeof(other_url_properties) / sizeof(*other_url_properties))
74 const char* clipboard_properties[] = {
75     "color",
76     "fill",
77     "filter",
78     "stroke",
79 };
80 #define NUM_CLIPBOARD_PROPERTIES (sizeof(clipboard_properties) / sizeof(*clipboard_properties))
82 /**
83  *  Build a table of places where IDs are referenced, for a given element.
84  *  FIXME: There are some types of references not yet dealt with here
85  *         (e.g., ID selectors in CSS stylesheets, and references in scripts).
86  */
87 static void
88 find_references(SPObject *elem, refmap_type *refmap)
89 {
90     if (SP_OBJECT_IS_CLONED(elem)) return;
91     Inkscape::XML::Node *repr_elem = SP_OBJECT_REPR(elem);
92     if (!repr_elem) return;
93     if (repr_elem->type() != Inkscape::XML::ELEMENT_NODE) return;
95     /* check for references in inkscape:clipboard elements */
96     if (!std::strcmp(repr_elem->name(), "inkscape:clipboard")) {
97         SPCSSAttr *css = sp_repr_css_attr(repr_elem, "style");
98         if (css) {
99             for (unsigned i = 0; i < NUM_CLIPBOARD_PROPERTIES; ++i) {
100                 const char *attr = clipboard_properties[i];
101                 const gchar *value = sp_repr_css_property(css, attr, NULL);
102                 if (value) {
103                     gchar *uri = extract_uri(value);
104                     if (uri && uri[0] == '#') {
105                         IdReference idref = { REF_CLIPBOARD, elem, attr };
106                         (*refmap)[uri+1].push_back(idref);
107                     }
108                     g_free(uri);
109                 }
110             }
111         }
112         return; // nothing more to do for inkscape:clipboard elements
113     }
115     /* check for xlink:href="#..." and similar */
116     for (unsigned i = 0; i < NUM_HREF_LIKE_ATTRIBUTES; ++i) {
117         const char *attr = href_like_attributes[i];
118         const gchar *val = repr_elem->attribute(attr);
119         if (val && val[0] == '#') {
120             std::string id(val+1);
121             IdReference idref = { REF_HREF, elem, attr };
122             (*refmap)[id].push_back(idref);
123         }
124     }
126     SPStyle *style = SP_OBJECT_STYLE(elem);
128     /* check for url(#...) references in 'fill' or 'stroke' */
129     for (unsigned i = 0; i < NUM_SPIPAINT_PROPERTIES; ++i) {
130         const SPIPaint SPStyle::*prop = SPIPaint_members[i];
131         const SPIPaint *paint = &(style->*prop);
132         if (paint->isPaintserver() && paint->value.href) {
133             const SPObject *obj = paint->value.href->getObject();
134             if (obj) {
135                 const gchar *id = obj->getId();
136                 IdReference idref = { REF_STYLE, elem, SPIPaint_properties[i] };
137                 (*refmap)[id].push_back(idref);
138             }
139         }
140     }
142     /* check for url(#...) references in 'filter' */
143     const SPIFilter *filter = &(style->filter);
144     if (filter->href) {
145         const SPObject *obj = filter->href->getObject();
146         if (obj) {
147             const gchar *id = obj->getId();
148             IdReference idref = { REF_STYLE, elem, "filter" };
149             (*refmap)[id].push_back(idref);
150         }
151     }
153     /* check for other url(#...) references */
154     for (unsigned i = 0; i < NUM_OTHER_URL_PROPERTIES; ++i) {
155         const char *attr = other_url_properties[i];
156         const gchar *value = repr_elem->attribute(attr);
157         if (value) {
158             gchar *uri = extract_uri(value);
159             if (uri && uri[0] == '#') {
160                 IdReference idref = { REF_URL, elem, attr };
161                 (*refmap)[uri+1].push_back(idref);
162             }
163             g_free(uri);
164         }
165     }
166     
167     /* recurse */
168     for (SPObject *child = sp_object_first_child(elem);
169          child; child = SP_OBJECT_NEXT(child) )
170     {
171         find_references(child, refmap);
172     }
175 /**
176  *  Change any IDs that clash with IDs in the current document, and make
177  *  a list of those changes that will require fixing up references.
178  */
179 static void
180 change_clashing_ids(SPDocument *imported_doc, SPDocument *current_doc,
181                     SPObject *elem, const refmap_type *refmap,
182                     id_changelist_type *id_changes)
184     const gchar *id = elem->getId();
186     if (id && current_doc->getObjectById(id)) {
187         // Choose a new ID.
188         // To try to preserve any meaningfulness that the original ID
189         // may have had, the new ID is the old ID followed by a hyphen
190         // and one or more digits.
191         std::string old_id(id);
192         std::string new_id(old_id + '-');
193         for (;;) {
194             new_id += "0123456789"[std::rand() % 10];
195             const char *str = new_id.c_str();
196             if (current_doc->getObjectById(str) == NULL &&
197                 imported_doc->getObjectById(str) == NULL) break;
198         }
199         // Change to the new ID
200         SP_OBJECT_REPR(elem)->setAttribute("id", new_id.c_str());
201         // Make a note of this change, if we need to fix up refs to it
202         if (refmap->find(old_id) != refmap->end())
203             id_changes->push_back(id_changeitem_type(elem, old_id));
204     }
206     /* recurse */
207     for (SPObject *child = sp_object_first_child(elem);
208          child; child = SP_OBJECT_NEXT(child) )
209     {
210         change_clashing_ids(imported_doc, current_doc, child, refmap, id_changes);
211     }
214 /**
215  *  Fix up references to changed IDs.
216  */
217 static void
218 fix_up_refs(const refmap_type *refmap, const id_changelist_type &id_changes)
220     id_changelist_type::const_iterator pp;
221     const id_changelist_type::const_iterator pp_end = id_changes.end();
222     for (pp = id_changes.begin(); pp != pp_end; ++pp) {
223         SPObject *obj = pp->first;
224         refmap_type::const_iterator pos = refmap->find(pp->second);
225         std::list<IdReference>::const_iterator it;
226         const std::list<IdReference>::const_iterator it_end = pos->second.end();
227         for (it = pos->second.begin(); it != it_end; ++it) {
228             if (it->type == REF_HREF) {
229                 gchar *new_uri = g_strdup_printf("#%s", obj->getId());
230                 SP_OBJECT_REPR(it->elem)->setAttribute(it->attr, new_uri);
231                 g_free(new_uri);
232             }
233             else if (it->type == REF_STYLE) {
234                 sp_style_set_property_url(it->elem, it->attr, obj, false);
235             }
236             else if (it->type == REF_URL) {
237                 gchar *url = g_strdup_printf("url(#%s)", obj->getId());
238                 SP_OBJECT_REPR(it->elem)->setAttribute(it->attr, url);
239                 g_free(url);
240             }
241             else if (it->type == REF_CLIPBOARD) {
242                 SPCSSAttr *style = sp_repr_css_attr(SP_OBJECT_REPR(it->elem), "style");
243                 gchar *url = g_strdup_printf("url(#%s)", obj->getId());
244                 sp_repr_css_set_property(style, it->attr, url);
245                 g_free(url);
246                 gchar *style_string = sp_repr_css_write_string(style);
247                 SP_OBJECT_REPR(it->elem)->setAttribute("style", style_string);
248                 g_free(style_string);
249             }
250             else g_assert(0); // shouldn't happen
251         }
252     }
255 /**
256  *  This function resolves ID clashes between the document being imported
257  *  and the current open document: IDs in the imported document that would
258  *  clash with IDs in the existing document are changed, and references to
259  *  those IDs are updated accordingly.
260  */
261 void
262 prevent_id_clashes(SPDocument *imported_doc, SPDocument *current_doc)
264     refmap_type *refmap = new refmap_type;
265     id_changelist_type id_changes;
266     SPObject *imported_root = SP_DOCUMENT_ROOT(imported_doc);
267         
268     find_references(imported_root, refmap);
269     change_clashing_ids(imported_doc, current_doc, imported_root, refmap,
270                         &id_changes);
271     fix_up_refs(refmap, id_changes);
273     delete refmap;
276 /*
277   Local Variables:
278   mode:c++
279   c-file-style:"stroustrup"
280   c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
281   indent-tabs-mode:nil
282   fill-column:99
283   End:
284 */
285 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 :