Code

Resolve ID clashes when pasting (fixes bug 165936).
authorsasilver <sasilver@users.sourceforge.net>
Tue, 24 Jun 2008 10:49:28 +0000 (10:49 +0000)
committersasilver <sasilver@users.sourceforge.net>
Tue, 24 Jun 2008 10:49:28 +0000 (10:49 +0000)
src/CMakeLists.txt
src/Makefile_insert
src/file.cpp
src/id-clash.cpp [new file with mode: 0644]
src/id-clash.h [new file with mode: 0644]
src/ui/clipboard.cpp

index 5dfd490c5bae95188c0a74b290804642bb0725a5..23ac82fb60d692419cd90a2912b120c00caef404 100644 (file)
@@ -147,6 +147,7 @@ gradient-context.cpp
 gradient-drag.cpp
 guide-snapper.cpp
 help.cpp
+id-clash.cpp
 ige-mac-menu.c
 ink-action.cpp
 inkscape.cpp
index 033d2781079f064e230d32c77df68da67c2aacc4..f60e482e6fe0c9a9efedb9dfa3e95038fb032a32 100644 (file)
@@ -88,6 +88,7 @@ libinkpre_a_SOURCES = \
        help.cpp help.h \
        helper-fns.h \
        icon-size.h \
+       id-clash.cpp id-clash.h \
        ige-mac-menu.h ige-mac-menu.c \
        inkscape-stock.cpp inkscape-stock.h\
        inkscape.cpp inkscape.h inkscape-private.h      \
index cdd76941fc8a615ba9de41f84eb5efa8b96cd58d..633dc08ceea7f38dfc887d29696a7a23124bb439 100644 (file)
@@ -66,7 +66,7 @@
 #include "application/editor.h"
 #include "inkscape.h"
 #include "uri.h"
-#include "extract-uri.h"
+#include "id-clash.h"
 
 #ifdef WITH_GNOME_VFS
 # include <libgnomevfs/gnome-vfs.h>
@@ -873,206 +873,6 @@ sp_file_save_a_copy(Gtk::Window &parentWindow, gpointer /*object*/, gpointer /*d
 ## I M P O R T
 ######################*/
 
-typedef enum { REF_HREF, REF_STYLE, REF_URL } ID_REF_TYPE;
-
-struct IdReference {
-    ID_REF_TYPE type;
-    SPObject *elem;
-    const char *attr;  // property or href-like attribute
-};
-
-typedef std::map<std::string, std::list<IdReference> > refmap_type;
-
-typedef std::pair<SPObject*, std::string> id_changeitem_type;
-typedef std::list<id_changeitem_type> id_changelist_type;
-
-const char *href_like_attributes[] = {
-    "inkscape:href",
-    "inkscape:path-effect",
-    "inkscape:perspectiveID",
-    "inkscape:tiled-clone-of",
-    "xlink:href",
-};
-#define NUM_HREF_LIKE_ATTRIBUTES (sizeof(href_like_attributes) / sizeof(*href_like_attributes))
-
-const SPIPaint SPStyle::* SPIPaint_members[] = {
-    &SPStyle::color,
-    &SPStyle::fill,
-    &SPStyle::stroke,
-};
-const char* SPIPaint_properties[] = {
-    "color",
-    "fill",
-    "stroke",
-};
-#define NUM_SPIPAINT_PROPERTIES (sizeof(SPIPaint_properties) / sizeof(*SPIPaint_properties))
-
-const char* other_url_properties[] = {
-    "clip-path",
-    "color-profile",
-    "cursor",
-    "marker-end",
-    "marker-mid",
-    "marker-start",
-    "mask",
-};
-#define NUM_OTHER_URL_PROPERTIES (sizeof(other_url_properties) / sizeof(*other_url_properties))
-
-/**
- *  Build a table of places where ids are referenced, for a given element.
- *  FIXME: There are some types of references not yet dealt with here
- *         (e.g., ID selectors in CSS stylesheets).
- */
-static void
-find_references(SPObject *elem, refmap_type *refmap)
-{
-    Inkscape::XML::Node *repr_elem = SP_OBJECT_REPR(elem);
-    SPStyle *style = SP_OBJECT_STYLE(elem);
-
-    /* check for xlink:href="#..." and similar */
-    for (unsigned i = 0; i < NUM_HREF_LIKE_ATTRIBUTES; ++i) {
-        const char *attr = href_like_attributes[i];
-        const gchar *val = repr_elem->attribute(attr);
-        if (val && val[0] == '#') {
-            std::string id(val+1);
-            IdReference idref = { REF_HREF, elem, attr };
-            (*refmap)[id].push_back(idref);
-        }
-    }
-
-    /* check for url(#...) references in 'fill' or 'stroke' */
-    for (unsigned i = 0; i < NUM_SPIPAINT_PROPERTIES; ++i) {
-        const SPIPaint SPStyle::*prop = SPIPaint_members[i];
-        const SPIPaint *paint = &(style->*prop);
-        if (paint->isPaintserver()) {
-            const gchar *id = SP_OBJECT_ID(paint->value.href->getObject());
-            IdReference idref = { REF_STYLE, elem, SPIPaint_properties[i] };
-            (*refmap)[id].push_back(idref);
-        }
-    }
-
-    /* check for url(#...) references in 'filter' */
-    const SPIFilter *filter = &(style->filter);
-    if (filter->href) {
-        const gchar *id = SP_OBJECT_ID(filter->href->getObject());
-        IdReference idref = { REF_STYLE, elem, "filter" };
-        (*refmap)[id].push_back(idref);
-    }
-
-    /* check for other url(#...) references */
-    for (unsigned i = 0; i < NUM_OTHER_URL_PROPERTIES; ++i) {
-        const char *attr = other_url_properties[i];
-        const gchar *value = repr_elem->attribute(attr);
-        if (value) {
-            const gchar *uri = extract_uri(value);
-            if (uri && uri[0] == '#') {
-                IdReference idref = { REF_URL, elem, attr };
-                (*refmap)[uri+1].push_back(idref);
-            }
-        }
-    }
-    
-    /* recurse */
-    for (SPObject *child = sp_object_first_child(elem);
-         child; child = SP_OBJECT_NEXT(child) )
-    {
-        find_references(child, refmap);
-    }
-}
-
-/**
- *  Change any ids that clash with ids in the current document, and make
- *  a list of those changes that will require fixing up references.
- */
-static void
-change_clashing_ids(SPDocument *imported_doc, SPDocument *current_doc,
-                    SPObject *elem, const refmap_type *refmap,
-                    id_changelist_type *id_changes)
-{
-    const gchar *id = SP_OBJECT_ID(elem);
-
-    if (id && current_doc->getObjectById(id)) {
-        // Choose a new id.
-        // To try to preserve any meaningfulness that the original id
-        // may have had, the new id is the old id followed by a hyphen
-        // and one or more digits.
-        std::string old_id(id);
-        std::string new_id(old_id + '-');
-        for (;;) {
-            new_id += "0123456789"[std::rand() % 10];
-            const char *str = new_id.c_str();
-            if (current_doc->getObjectById(str) == NULL &&
-                imported_doc->getObjectById(str) == NULL) break;
-        }
-        // Change to the new id
-        SP_OBJECT_REPR(elem)->setAttribute("id", new_id.c_str());
-        // Make a note of this change, if we need to fix up refs to it
-        if (refmap->find(old_id) != refmap->end())
-            id_changes->push_back(id_changeitem_type(elem, old_id));
-    }
-
-    /* recurse */
-    for (SPObject *child = sp_object_first_child(elem);
-         child; child = SP_OBJECT_NEXT(child) )
-    {
-        change_clashing_ids(imported_doc, current_doc, child, refmap, id_changes);
-    }
-}
-
-/**
- *  Fix up references to changed ids.
- */
-static void
-fix_up_refs(const refmap_type *refmap, const id_changelist_type &id_changes)
-{
-    id_changelist_type::const_iterator pp;
-    const id_changelist_type::const_iterator pp_end = id_changes.end();
-    for (pp = id_changes.begin(); pp != pp_end; ++pp) {
-        SPObject *obj = pp->first;
-        refmap_type::const_iterator pos = refmap->find(pp->second);
-        std::list<IdReference>::const_iterator it;
-        const std::list<IdReference>::const_iterator it_end = pos->second.end();
-        for (it = pos->second.begin(); it != it_end; ++it) {
-            if (it->type == REF_HREF) {
-                gchar *new_uri = g_strdup_printf("#%s", SP_OBJECT_ID(obj));
-                SP_OBJECT_REPR(it->elem)->setAttribute(it->attr, new_uri);
-                g_free(new_uri);
-            }
-            else if (it->type == REF_STYLE) {
-                sp_style_set_property_url(it->elem, it->attr, obj, false);
-            }
-            else if (it->type == REF_URL) {
-                gchar *url = g_strdup_printf("url(#%s)", SP_OBJECT_ID(obj));
-                SP_OBJECT_REPR(it->elem)->setAttribute(it->attr, url);
-                g_free(url);
-            }
-            else g_assert(0); // shouldn't happen
-        }
-    }
-}
-
-/**
- *  This function resolves ID clashes between the document being imported
- *  and the current open document: IDs in the imported document that would
- *  clash with IDs in the existing document are changed, and references to
- *  those IDs are updated accordingly.
- */
-void
-prevent_id_clashes(SPDocument *imported_doc, SPDocument *current_doc)
-{
-    refmap_type *refmap = new refmap_type;
-    id_changelist_type id_changes;
-    SPObject *imported_root = SP_DOCUMENT_ROOT(imported_doc);
-        
-    find_references(imported_root, refmap);
-    change_clashing_ids(imported_doc, current_doc, imported_root, refmap,
-                        &id_changes);
-    fix_up_refs(refmap, id_changes);
-
-    delete refmap;
-}
-
-
 /**
  *  Import a resource.  Called by sp_file_import()
  */
diff --git a/src/id-clash.cpp b/src/id-clash.cpp
new file mode 100644 (file)
index 0000000..1ce30e0
--- /dev/null
@@ -0,0 +1,275 @@
+#define __ID_CLASH_C__
+/** \file
+ * Routines for resolving ID clashes when importing or pasting.
+ *
+ * Authors:
+ *   Stephen Silver <sasilver@users.sourceforge.net>
+ *
+ * Copyright (C) 2008 authors
+ *
+ * Released under GNU GPL, read the file 'COPYING' for more information
+ */
+
+#include <cstdlib>
+#include <cstring>
+#include <list>
+#include <map>
+#include <string>
+#include <utility>
+
+#include "extract-uri.h"
+#include "id-clash.h"
+#include "sp-object.h"
+#include "style.h"
+#include "xml/node.h"
+#include "xml/repr.h"
+
+typedef enum { REF_HREF, REF_STYLE, REF_URL, REF_CLIPBOARD } ID_REF_TYPE;
+
+struct IdReference {
+    ID_REF_TYPE type;
+    SPObject *elem;
+    const char *attr;  // property or href-like attribute
+};
+
+typedef std::map<std::string, std::list<IdReference> > refmap_type;
+
+typedef std::pair<SPObject*, std::string> id_changeitem_type;
+typedef std::list<id_changeitem_type> id_changelist_type;
+
+const char *href_like_attributes[] = {
+    "inkscape:href",
+    "inkscape:path-effect",
+    "inkscape:perspectiveID",
+    "inkscape:tiled-clone-of",
+    "xlink:href",
+};
+#define NUM_HREF_LIKE_ATTRIBUTES (sizeof(href_like_attributes) / sizeof(*href_like_attributes))
+
+const SPIPaint SPStyle::* SPIPaint_members[] = {
+    &SPStyle::color,
+    &SPStyle::fill,
+    &SPStyle::stroke,
+};
+const char* SPIPaint_properties[] = {
+    "color",
+    "fill",
+    "stroke",
+};
+#define NUM_SPIPAINT_PROPERTIES (sizeof(SPIPaint_properties) / sizeof(*SPIPaint_properties))
+
+const char* other_url_properties[] = {
+    "clip-path",
+    "color-profile",
+    "cursor",
+    "marker-end",
+    "marker-mid",
+    "marker-start",
+    "mask",
+};
+#define NUM_OTHER_URL_PROPERTIES (sizeof(other_url_properties) / sizeof(*other_url_properties))
+
+const char* clipboard_properties[] = {
+    "color",
+    "fill",
+    "filter",
+    "stroke",
+};
+#define NUM_CLIPBOARD_PROPERTIES (sizeof(clipboard_properties) / sizeof(*clipboard_properties))
+
+/**
+ *  Build a table of places where IDs are referenced, for a given element.
+ *  FIXME: There are some types of references not yet dealt with here
+ *         (e.g., ID selectors in CSS stylesheets, and references in scripts).
+ */
+static void
+find_references(SPObject *elem, refmap_type *refmap)
+{
+    Inkscape::XML::Node *repr_elem = SP_OBJECT_REPR(elem);
+    if (repr_elem->type() != Inkscape::XML::ELEMENT_NODE) return;
+
+    /* check for references in inkscape:clipboard elements */
+    if (!std::strcmp(repr_elem->name(), "inkscape:clipboard")) {
+        SPCSSAttr *css = sp_repr_css_attr(repr_elem, "style");
+        if (css) {
+            for (unsigned i = 0; i < NUM_CLIPBOARD_PROPERTIES; ++i) {
+                const char *attr = clipboard_properties[i];
+                const gchar *value = sp_repr_css_property(css, attr, NULL);
+                if (value) {
+                    gchar *uri = extract_uri(value);
+                    if (uri && uri[0] == '#') {
+                        IdReference idref = { REF_CLIPBOARD, elem, attr };
+                        (*refmap)[uri+1].push_back(idref);
+                    }
+                    g_free(uri);
+                }
+            }
+        }
+        return; // nothing more to do for inkscape:clipboard elements
+    }
+
+    /* check for xlink:href="#..." and similar */
+    for (unsigned i = 0; i < NUM_HREF_LIKE_ATTRIBUTES; ++i) {
+        const char *attr = href_like_attributes[i];
+        const gchar *val = repr_elem->attribute(attr);
+        if (val && val[0] == '#') {
+            std::string id(val+1);
+            IdReference idref = { REF_HREF, elem, attr };
+            (*refmap)[id].push_back(idref);
+        }
+    }
+
+    SPStyle *style = SP_OBJECT_STYLE(elem);
+
+    /* check for url(#...) references in 'fill' or 'stroke' */
+    for (unsigned i = 0; i < NUM_SPIPAINT_PROPERTIES; ++i) {
+        const SPIPaint SPStyle::*prop = SPIPaint_members[i];
+        const SPIPaint *paint = &(style->*prop);
+        if (paint->isPaintserver()) {
+            const gchar *id = SP_OBJECT_ID(paint->value.href->getObject());
+            IdReference idref = { REF_STYLE, elem, SPIPaint_properties[i] };
+            (*refmap)[id].push_back(idref);
+        }
+    }
+
+    /* check for url(#...) references in 'filter' */
+    const SPIFilter *filter = &(style->filter);
+    if (filter->href) {
+        const gchar *id = SP_OBJECT_ID(filter->href->getObject());
+        IdReference idref = { REF_STYLE, elem, "filter" };
+        (*refmap)[id].push_back(idref);
+    }
+
+    /* check for other url(#...) references */
+    for (unsigned i = 0; i < NUM_OTHER_URL_PROPERTIES; ++i) {
+        const char *attr = other_url_properties[i];
+        const gchar *value = repr_elem->attribute(attr);
+        if (value) {
+            gchar *uri = extract_uri(value);
+            if (uri && uri[0] == '#') {
+                IdReference idref = { REF_URL, elem, attr };
+                (*refmap)[uri+1].push_back(idref);
+            }
+            g_free(uri);
+        }
+    }
+    
+    /* recurse */
+    for (SPObject *child = sp_object_first_child(elem);
+         child; child = SP_OBJECT_NEXT(child) )
+    {
+        find_references(child, refmap);
+    }
+}
+
+/**
+ *  Change any IDs that clash with IDs in the current document, and make
+ *  a list of those changes that will require fixing up references.
+ */
+static void
+change_clashing_ids(SPDocument *imported_doc, SPDocument *current_doc,
+                    SPObject *elem, const refmap_type *refmap,
+                    id_changelist_type *id_changes)
+{
+    const gchar *id = SP_OBJECT_ID(elem);
+
+    if (id && current_doc->getObjectById(id)) {
+        // Choose a new ID.
+        // To try to preserve any meaningfulness that the original ID
+        // may have had, the new ID is the old ID followed by a hyphen
+        // and one or more digits.
+        std::string old_id(id);
+        std::string new_id(old_id + '-');
+        for (;;) {
+            new_id += "0123456789"[std::rand() % 10];
+            const char *str = new_id.c_str();
+            if (current_doc->getObjectById(str) == NULL &&
+                imported_doc->getObjectById(str) == NULL) break;
+        }
+        // Change to the new ID
+        SP_OBJECT_REPR(elem)->setAttribute("id", new_id.c_str());
+        // Make a note of this change, if we need to fix up refs to it
+        if (refmap->find(old_id) != refmap->end())
+            id_changes->push_back(id_changeitem_type(elem, old_id));
+    }
+
+    /* recurse */
+    for (SPObject *child = sp_object_first_child(elem);
+         child; child = SP_OBJECT_NEXT(child) )
+    {
+        change_clashing_ids(imported_doc, current_doc, child, refmap, id_changes);
+    }
+}
+
+/**
+ *  Fix up references to changed IDs.
+ */
+static void
+fix_up_refs(const refmap_type *refmap, const id_changelist_type &id_changes)
+{
+    id_changelist_type::const_iterator pp;
+    const id_changelist_type::const_iterator pp_end = id_changes.end();
+    for (pp = id_changes.begin(); pp != pp_end; ++pp) {
+        SPObject *obj = pp->first;
+        refmap_type::const_iterator pos = refmap->find(pp->second);
+        std::list<IdReference>::const_iterator it;
+        const std::list<IdReference>::const_iterator it_end = pos->second.end();
+        for (it = pos->second.begin(); it != it_end; ++it) {
+            if (it->type == REF_HREF) {
+                gchar *new_uri = g_strdup_printf("#%s", SP_OBJECT_ID(obj));
+                SP_OBJECT_REPR(it->elem)->setAttribute(it->attr, new_uri);
+                g_free(new_uri);
+            }
+            else if (it->type == REF_STYLE) {
+                sp_style_set_property_url(it->elem, it->attr, obj, false);
+            }
+            else if (it->type == REF_URL) {
+                gchar *url = g_strdup_printf("url(#%s)", SP_OBJECT_ID(obj));
+                SP_OBJECT_REPR(it->elem)->setAttribute(it->attr, url);
+                g_free(url);
+            }
+            else if (it->type == REF_CLIPBOARD) {
+                SPCSSAttr *style = sp_repr_css_attr(SP_OBJECT_REPR(it->elem), "style");
+                gchar *url = g_strdup_printf("url(#%s)", SP_OBJECT_ID(obj));
+                sp_repr_css_set_property(style, it->attr, url);
+                g_free(url);
+                gchar *style_string = sp_repr_css_write_string(style);
+                SP_OBJECT_REPR(it->elem)->setAttribute("style", style_string);
+                g_free(style_string);
+            }
+            else g_assert(0); // shouldn't happen
+        }
+    }
+}
+
+/**
+ *  This function resolves ID clashes between the document being imported
+ *  and the current open document: IDs in the imported document that would
+ *  clash with IDs in the existing document are changed, and references to
+ *  those IDs are updated accordingly.
+ */
+void
+prevent_id_clashes(SPDocument *imported_doc, SPDocument *current_doc)
+{
+    refmap_type *refmap = new refmap_type;
+    id_changelist_type id_changes;
+    SPObject *imported_root = SP_DOCUMENT_ROOT(imported_doc);
+        
+    find_references(imported_root, refmap);
+    change_clashing_ids(imported_doc, current_doc, imported_root, refmap,
+                        &id_changes);
+    fix_up_refs(refmap, id_changes);
+
+    delete refmap;
+}
+
+/*
+  Local Variables:
+  mode:c++
+  c-file-style:"stroustrup"
+  c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
+  indent-tabs-mode:nil
+  fill-column:99
+  End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 :
diff --git a/src/id-clash.h b/src/id-clash.h
new file mode 100644 (file)
index 0000000..4186427
--- /dev/null
@@ -0,0 +1,19 @@
+#ifndef SEEN_ID_CLASH_H
+#define SEEN_ID_CLASH_H
+
+#include "document.h"
+
+void prevent_id_clashes(SPDocument *imported_doc, SPDocument *current_doc);
+
+#endif /* !SEEN_ID_CLASH_H */
+
+/*
+  Local Variables:
+  mode:c++
+  c-file-style:"stroustrup"
+  c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
+  indent-tabs-mode:nil
+  fill-column:99
+  End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :
index f7967bd40d7546f48b572601872a0825caa44f70..28dc7d937c451d2ae0ae6cd7e5606833b6fd7695 100644 (file)
@@ -72,6 +72,7 @@
 #include "tools-switch.h"
 #include "libnr/n-art-bpath-2geom.h"
 #include "path-chemistry.h"
+#include "id-clash.h"
 
 /// @brief Made up mimetype to represent Gdk::Pixbuf clipboard contents
 #define CLIPBOARD_GDK_PIXBUF_TARGET "image/x-gdk-pixbuf"
@@ -787,29 +788,9 @@ void ClipboardManagerImpl::_pasteDefs(SPDocument *clipdoc)
         *target_defs = SP_OBJECT_REPR(SP_DOCUMENT_DEFS(target_document));
     Inkscape::XML::Document *target_xmldoc = sp_document_repr_doc(target_document);
 
+    prevent_id_clashes(clipdoc, target_document);
+    
     for (Inkscape::XML::Node *def = defs->firstChild() ; def ; def = def->next()) {
-        /// @todo TODO: implement def id collision resolution in ClipboardManagerImpl::_pasteDefs()
-
-        /*
-        // simplistic solution: when a collision occurs, add "a" to id until it's unique
-        Glib::ustring pasted_id = def->attribute("id");
-        if ( pasted_id.empty() ) continue; // defs without id are useless
-        Glib::ustring pasted_id_original = pasted_id;
-
-        while(sp_repr_lookup_child(target_defs, "id", pasted_id.data())) {
-            pasted_id.append("a");
-        }
-
-        if ( pasted_id != pasted_id_original ) {
-            def->setAttribute("id", pasted_id.data());
-            // Update the id in the rest of the document so there are no dangling references
-            // How to do that?
-            _changeIdReferences(clipdoc, pasted_id_original, pasted_id);
-        }
-        */
-        if (sp_repr_lookup_child(target_defs, "id", def->attribute("id")))
-            continue; // skip duplicate defs - temporary non-solution
-
         _copyNode(def, target_xmldoc, target_defs);
     }
 }