Code

Merge and cleanup of GSoC C++-ification project.
[inkscape.git] / src / id-clash.cpp
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     }
167     
168     // recurse
169     for (SPObject *child = elem->firstChild(); child; child = child->getNext() )
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 = elem->firstChild(); child; child = child->getNext() )
208     {
209         change_clashing_ids(imported_doc, current_doc, child, refmap, id_changes);
210     }
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)
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     }
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)
263     refmap_type *refmap = new refmap_type;
264     id_changelist_type id_changes;
265     SPObject *imported_root = imported_doc->getRoot();
266         
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;
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 :