1 /** \file
2 * Routines for resolving ID clashes when importing or pasting.
3 *
4 * Authors:
5 * Stephen Silver <sasilver@users.sourceforge.net>
6 * Jon A. Cruz <jon@joncruz.org>
7 * Abhishek Sharma
8 *
9 * Copyright (C) 2008 authors
10 *
11 * Released under GNU GPL, read the file 'COPYING' for more information
12 */
14 #include <cstdlib>
15 #include <cstring>
16 #include <list>
17 #include <map>
18 #include <string>
19 #include <utility>
21 #include "extract-uri.h"
22 #include "id-clash.h"
23 #include "sp-object.h"
24 #include "style.h"
25 #include "xml/node.h"
26 #include "xml/repr.h"
28 typedef enum { REF_HREF, REF_STYLE, REF_URL, REF_CLIPBOARD } ID_REF_TYPE;
30 struct IdReference {
31 ID_REF_TYPE type;
32 SPObject *elem;
33 const char *attr; // property or href-like attribute
34 };
36 typedef std::map<std::string, std::list<IdReference> > refmap_type;
38 typedef std::pair<SPObject*, std::string> id_changeitem_type;
39 typedef std::list<id_changeitem_type> id_changelist_type;
41 const char *href_like_attributes[] = {
42 "inkscape:connection-end",
43 "inkscape:connection-start",
44 "inkscape:href",
45 "inkscape:path-effect",
46 "inkscape:perspectiveID",
47 "inkscape:tiled-clone-of",
48 "xlink:href",
49 };
50 #define NUM_HREF_LIKE_ATTRIBUTES (sizeof(href_like_attributes) / sizeof(*href_like_attributes))
52 const SPIPaint SPStyle::* SPIPaint_members[] = {
53 &SPStyle::color,
54 &SPStyle::fill,
55 &SPStyle::stroke,
56 };
57 const char* SPIPaint_properties[] = {
58 "color",
59 "fill",
60 "stroke",
61 };
62 #define NUM_SPIPAINT_PROPERTIES (sizeof(SPIPaint_properties) / sizeof(*SPIPaint_properties))
64 const char* other_url_properties[] = {
65 "clip-path",
66 "color-profile",
67 "cursor",
68 "marker-end",
69 "marker-mid",
70 "marker-start",
71 "mask",
72 };
73 #define NUM_OTHER_URL_PROPERTIES (sizeof(other_url_properties) / sizeof(*other_url_properties))
75 const char* clipboard_properties[] = {
76 "color",
77 "fill",
78 "filter",
79 "stroke",
80 };
81 #define NUM_CLIPBOARD_PROPERTIES (sizeof(clipboard_properties) / sizeof(*clipboard_properties))
83 /**
84 * Build a table of places where IDs are referenced, for a given element.
85 * FIXME: There are some types of references not yet dealt with here
86 * (e.g., ID selectors in CSS stylesheets, and references in scripts).
87 */
88 static void
89 find_references(SPObject *elem, refmap_type *refmap)
90 {
91 if (SP_OBJECT_IS_CLONED(elem)) return;
92 Inkscape::XML::Node *repr_elem = SP_OBJECT_REPR(elem);
93 if (!repr_elem) return;
94 if (repr_elem->type() != Inkscape::XML::ELEMENT_NODE) return;
96 /* check for references in inkscape:clipboard elements */
97 if (!std::strcmp(repr_elem->name(), "inkscape:clipboard")) {
98 SPCSSAttr *css = sp_repr_css_attr(repr_elem, "style");
99 if (css) {
100 for (unsigned i = 0; i < NUM_CLIPBOARD_PROPERTIES; ++i) {
101 const char *attr = clipboard_properties[i];
102 const gchar *value = sp_repr_css_property(css, attr, NULL);
103 if (value) {
104 gchar *uri = extract_uri(value);
105 if (uri && uri[0] == '#') {
106 IdReference idref = { REF_CLIPBOARD, elem, attr };
107 (*refmap)[uri+1].push_back(idref);
108 }
109 g_free(uri);
110 }
111 }
112 }
113 return; // nothing more to do for inkscape:clipboard elements
114 }
116 /* check for xlink:href="#..." and similar */
117 for (unsigned i = 0; i < NUM_HREF_LIKE_ATTRIBUTES; ++i) {
118 const char *attr = href_like_attributes[i];
119 const gchar *val = repr_elem->attribute(attr);
120 if (val && val[0] == '#') {
121 std::string id(val+1);
122 IdReference idref = { REF_HREF, elem, attr };
123 (*refmap)[id].push_back(idref);
124 }
125 }
127 SPStyle *style = SP_OBJECT_STYLE(elem);
129 /* check for url(#...) references in 'fill' or 'stroke' */
130 for (unsigned i = 0; i < NUM_SPIPAINT_PROPERTIES; ++i) {
131 const SPIPaint SPStyle::*prop = SPIPaint_members[i];
132 const SPIPaint *paint = &(style->*prop);
133 if (paint->isPaintserver() && paint->value.href) {
134 const SPObject *obj = paint->value.href->getObject();
135 if (obj) {
136 const gchar *id = obj->getId();
137 IdReference idref = { REF_STYLE, elem, SPIPaint_properties[i] };
138 (*refmap)[id].push_back(idref);
139 }
140 }
141 }
143 /* check for url(#...) references in 'filter' */
144 const SPIFilter *filter = &(style->filter);
145 if (filter->href) {
146 const SPObject *obj = filter->href->getObject();
147 if (obj) {
148 const gchar *id = obj->getId();
149 IdReference idref = { REF_STYLE, elem, "filter" };
150 (*refmap)[id].push_back(idref);
151 }
152 }
154 /* check for other url(#...) references */
155 for (unsigned i = 0; i < NUM_OTHER_URL_PROPERTIES; ++i) {
156 const char *attr = other_url_properties[i];
157 const gchar *value = repr_elem->attribute(attr);
158 if (value) {
159 gchar *uri = extract_uri(value);
160 if (uri && uri[0] == '#') {
161 IdReference idref = { REF_URL, elem, attr };
162 (*refmap)[uri+1].push_back(idref);
163 }
164 g_free(uri);
165 }
166 }
168 // recurse
169 for (SPObject *child = elem->firstChild(); child; child = child->getNext() )
170 {
171 find_references(child, refmap);
172 }
173 }
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)
183 {
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 = elem->firstChild(); child; child = child->getNext() )
208 {
209 change_clashing_ids(imported_doc, current_doc, child, refmap, id_changes);
210 }
211 }
213 /**
214 * Fix up references to changed IDs.
215 */
216 static void
217 fix_up_refs(const refmap_type *refmap, const id_changelist_type &id_changes)
218 {
219 id_changelist_type::const_iterator pp;
220 const id_changelist_type::const_iterator pp_end = id_changes.end();
221 for (pp = id_changes.begin(); pp != pp_end; ++pp) {
222 SPObject *obj = pp->first;
223 refmap_type::const_iterator pos = refmap->find(pp->second);
224 std::list<IdReference>::const_iterator it;
225 const std::list<IdReference>::const_iterator it_end = pos->second.end();
226 for (it = pos->second.begin(); it != it_end; ++it) {
227 if (it->type == REF_HREF) {
228 gchar *new_uri = g_strdup_printf("#%s", obj->getId());
229 SP_OBJECT_REPR(it->elem)->setAttribute(it->attr, new_uri);
230 g_free(new_uri);
231 }
232 else if (it->type == REF_STYLE) {
233 sp_style_set_property_url(it->elem, it->attr, obj, false);
234 }
235 else if (it->type == REF_URL) {
236 gchar *url = g_strdup_printf("url(#%s)", obj->getId());
237 SP_OBJECT_REPR(it->elem)->setAttribute(it->attr, url);
238 g_free(url);
239 }
240 else if (it->type == REF_CLIPBOARD) {
241 SPCSSAttr *style = sp_repr_css_attr(SP_OBJECT_REPR(it->elem), "style");
242 gchar *url = g_strdup_printf("url(#%s)", obj->getId());
243 sp_repr_css_set_property(style, it->attr, url);
244 g_free(url);
245 gchar *style_string = sp_repr_css_write_string(style);
246 SP_OBJECT_REPR(it->elem)->setAttribute("style", style_string);
247 g_free(style_string);
248 }
249 else g_assert(0); // shouldn't happen
250 }
251 }
252 }
254 /**
255 * This function resolves ID clashes between the document being imported
256 * and the current open document: IDs in the imported document that would
257 * clash with IDs in the existing document are changed, and references to
258 * those IDs are updated accordingly.
259 */
260 void
261 prevent_id_clashes(SPDocument *imported_doc, SPDocument *current_doc)
262 {
263 refmap_type *refmap = new refmap_type;
264 id_changelist_type id_changes;
265 SPObject *imported_root = imported_doc->getRoot();
267 find_references(imported_root, refmap);
268 change_clashing_ids(imported_doc, current_doc, imported_root, refmap,
269 &id_changes);
270 fix_up_refs(refmap, id_changes);
272 delete refmap;
273 }
275 /*
276 Local Variables:
277 mode:c++
278 c-file-style:"stroustrup"
279 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
280 indent-tabs-mode:nil
281 fill-column:99
282 End:
283 */
284 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 :