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:href",
42 "inkscape:path-effect",
43 "inkscape:perspectiveID",
44 "inkscape:tiled-clone-of",
45 "xlink:href",
46 };
47 #define NUM_HREF_LIKE_ATTRIBUTES (sizeof(href_like_attributes) / sizeof(*href_like_attributes))
49 const SPIPaint SPStyle::* SPIPaint_members[] = {
50 &SPStyle::color,
51 &SPStyle::fill,
52 &SPStyle::stroke,
53 };
54 const char* SPIPaint_properties[] = {
55 "color",
56 "fill",
57 "stroke",
58 };
59 #define NUM_SPIPAINT_PROPERTIES (sizeof(SPIPaint_properties) / sizeof(*SPIPaint_properties))
61 const char* other_url_properties[] = {
62 "clip-path",
63 "color-profile",
64 "cursor",
65 "marker-end",
66 "marker-mid",
67 "marker-start",
68 "mask",
69 };
70 #define NUM_OTHER_URL_PROPERTIES (sizeof(other_url_properties) / sizeof(*other_url_properties))
72 const char* clipboard_properties[] = {
73 "color",
74 "fill",
75 "filter",
76 "stroke",
77 };
78 #define NUM_CLIPBOARD_PROPERTIES (sizeof(clipboard_properties) / sizeof(*clipboard_properties))
80 /**
81 * Build a table of places where IDs are referenced, for a given element.
82 * FIXME: There are some types of references not yet dealt with here
83 * (e.g., ID selectors in CSS stylesheets, and references in scripts).
84 */
85 static void
86 find_references(SPObject *elem, refmap_type *refmap)
87 {
88 if (SP_OBJECT_IS_CLONED(elem)) return;
89 Inkscape::XML::Node *repr_elem = SP_OBJECT_REPR(elem);
90 if (!repr_elem) return;
91 if (repr_elem->type() != Inkscape::XML::ELEMENT_NODE) return;
93 /* check for references in inkscape:clipboard elements */
94 if (!std::strcmp(repr_elem->name(), "inkscape:clipboard")) {
95 SPCSSAttr *css = sp_repr_css_attr(repr_elem, "style");
96 if (css) {
97 for (unsigned i = 0; i < NUM_CLIPBOARD_PROPERTIES; ++i) {
98 const char *attr = clipboard_properties[i];
99 const gchar *value = sp_repr_css_property(css, attr, NULL);
100 if (value) {
101 gchar *uri = extract_uri(value);
102 if (uri && uri[0] == '#') {
103 IdReference idref = { REF_CLIPBOARD, elem, attr };
104 (*refmap)[uri+1].push_back(idref);
105 }
106 g_free(uri);
107 }
108 }
109 }
110 return; // nothing more to do for inkscape:clipboard elements
111 }
113 /* check for xlink:href="#..." and similar */
114 for (unsigned i = 0; i < NUM_HREF_LIKE_ATTRIBUTES; ++i) {
115 const char *attr = href_like_attributes[i];
116 const gchar *val = repr_elem->attribute(attr);
117 if (val && val[0] == '#') {
118 std::string id(val+1);
119 IdReference idref = { REF_HREF, elem, attr };
120 (*refmap)[id].push_back(idref);
121 }
122 }
124 SPStyle *style = SP_OBJECT_STYLE(elem);
126 /* check for url(#...) references in 'fill' or 'stroke' */
127 for (unsigned i = 0; i < NUM_SPIPAINT_PROPERTIES; ++i) {
128 const SPIPaint SPStyle::*prop = SPIPaint_members[i];
129 const SPIPaint *paint = &(style->*prop);
130 if (paint->isPaintserver() && paint->value.href) {
131 const SPObject *obj = paint->value.href->getObject();
132 if (obj) {
133 const gchar *id = SP_OBJECT_ID(obj);
134 IdReference idref = { REF_STYLE, elem, SPIPaint_properties[i] };
135 (*refmap)[id].push_back(idref);
136 }
137 }
138 }
140 /* check for url(#...) references in 'filter' */
141 const SPIFilter *filter = &(style->filter);
142 if (filter->href) {
143 const SPObject *obj = filter->href->getObject();
144 if (obj) {
145 const gchar *id = SP_OBJECT_ID(obj);
146 IdReference idref = { REF_STYLE, elem, "filter" };
147 (*refmap)[id].push_back(idref);
148 }
149 }
151 /* check for other url(#...) references */
152 for (unsigned i = 0; i < NUM_OTHER_URL_PROPERTIES; ++i) {
153 const char *attr = other_url_properties[i];
154 const gchar *value = repr_elem->attribute(attr);
155 if (value) {
156 gchar *uri = extract_uri(value);
157 if (uri && uri[0] == '#') {
158 IdReference idref = { REF_URL, elem, attr };
159 (*refmap)[uri+1].push_back(idref);
160 }
161 g_free(uri);
162 }
163 }
165 /* recurse */
166 for (SPObject *child = sp_object_first_child(elem);
167 child; child = SP_OBJECT_NEXT(child) )
168 {
169 find_references(child, refmap);
170 }
171 }
173 /**
174 * Change any IDs that clash with IDs in the current document, and make
175 * a list of those changes that will require fixing up references.
176 */
177 static void
178 change_clashing_ids(SPDocument *imported_doc, SPDocument *current_doc,
179 SPObject *elem, const refmap_type *refmap,
180 id_changelist_type *id_changes)
181 {
182 const gchar *id = SP_OBJECT_ID(elem);
184 if (id && current_doc->getObjectById(id)) {
185 // Choose a new ID.
186 // To try to preserve any meaningfulness that the original ID
187 // may have had, the new ID is the old ID followed by a hyphen
188 // and one or more digits.
189 std::string old_id(id);
190 std::string new_id(old_id + '-');
191 for (;;) {
192 new_id += "0123456789"[std::rand() % 10];
193 const char *str = new_id.c_str();
194 if (current_doc->getObjectById(str) == NULL &&
195 imported_doc->getObjectById(str) == NULL) break;
196 }
197 // Change to the new ID
198 SP_OBJECT_REPR(elem)->setAttribute("id", new_id.c_str());
199 // Make a note of this change, if we need to fix up refs to it
200 if (refmap->find(old_id) != refmap->end())
201 id_changes->push_back(id_changeitem_type(elem, old_id));
202 }
204 /* recurse */
205 for (SPObject *child = sp_object_first_child(elem);
206 child; child = SP_OBJECT_NEXT(child) )
207 {
208 change_clashing_ids(imported_doc, current_doc, child, refmap, id_changes);
209 }
210 }
212 /**
213 * Fix up references to changed IDs.
214 */
215 static void
216 fix_up_refs(const refmap_type *refmap, const id_changelist_type &id_changes)
217 {
218 id_changelist_type::const_iterator pp;
219 const id_changelist_type::const_iterator pp_end = id_changes.end();
220 for (pp = id_changes.begin(); pp != pp_end; ++pp) {
221 SPObject *obj = pp->first;
222 refmap_type::const_iterator pos = refmap->find(pp->second);
223 std::list<IdReference>::const_iterator it;
224 const std::list<IdReference>::const_iterator it_end = pos->second.end();
225 for (it = pos->second.begin(); it != it_end; ++it) {
226 if (it->type == REF_HREF) {
227 gchar *new_uri = g_strdup_printf("#%s", SP_OBJECT_ID(obj));
228 SP_OBJECT_REPR(it->elem)->setAttribute(it->attr, new_uri);
229 g_free(new_uri);
230 }
231 else if (it->type == REF_STYLE) {
232 sp_style_set_property_url(it->elem, it->attr, obj, false);
233 }
234 else if (it->type == REF_URL) {
235 gchar *url = g_strdup_printf("url(#%s)", SP_OBJECT_ID(obj));
236 SP_OBJECT_REPR(it->elem)->setAttribute(it->attr, url);
237 g_free(url);
238 }
239 else if (it->type == REF_CLIPBOARD) {
240 SPCSSAttr *style = sp_repr_css_attr(SP_OBJECT_REPR(it->elem), "style");
241 gchar *url = g_strdup_printf("url(#%s)", SP_OBJECT_ID(obj));
242 sp_repr_css_set_property(style, it->attr, url);
243 g_free(url);
244 gchar *style_string = sp_repr_css_write_string(style);
245 SP_OBJECT_REPR(it->elem)->setAttribute("style", style_string);
246 g_free(style_string);
247 }
248 else g_assert(0); // shouldn't happen
249 }
250 }
251 }
253 /**
254 * This function resolves ID clashes between the document being imported
255 * and the current open document: IDs in the imported document that would
256 * clash with IDs in the existing document are changed, and references to
257 * those IDs are updated accordingly.
258 */
259 void
260 prevent_id_clashes(SPDocument *imported_doc, SPDocument *current_doc)
261 {
262 refmap_type *refmap = new refmap_type;
263 id_changelist_type id_changes;
264 SPObject *imported_root = SP_DOCUMENT_ROOT(imported_doc);
266 find_references(imported_root, refmap);
267 change_clashing_ids(imported_doc, current_doc, imported_root, refmap,
268 &id_changes);
269 fix_up_refs(refmap, id_changes);
271 delete refmap;
272 }
274 /*
275 Local Variables:
276 mode:c++
277 c-file-style:"stroustrup"
278 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
279 indent-tabs-mode:nil
280 fill-column:99
281 End:
282 */
283 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 :