From: sasilver Date: Tue, 24 Jun 2008 10:49:28 +0000 (+0000) Subject: Resolve ID clashes when pasting (fixes bug 165936). X-Git-Url: https://git.tokkee.org/?a=commitdiff_plain;h=c8f31ca031aaccc98c131ac22c63e043ca60a049;p=inkscape.git Resolve ID clashes when pasting (fixes bug 165936). --- diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 5dfd490c5..23ac82fb6 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -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 diff --git a/src/Makefile_insert b/src/Makefile_insert index 033d27810..f60e482e6 100644 --- a/src/Makefile_insert +++ b/src/Makefile_insert @@ -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 \ diff --git a/src/file.cpp b/src/file.cpp index cdd76941f..633dc08ce 100644 --- a/src/file.cpp +++ b/src/file.cpp @@ -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 @@ -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 > refmap_type; - -typedef std::pair id_changeitem_type; -typedef std::list 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::const_iterator it; - const std::list::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 index 000000000..1ce30e0dd --- /dev/null +++ b/src/id-clash.cpp @@ -0,0 +1,275 @@ +#define __ID_CLASH_C__ +/** \file + * Routines for resolving ID clashes when importing or pasting. + * + * Authors: + * Stephen Silver + * + * Copyright (C) 2008 authors + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#include +#include +#include +#include +#include +#include + +#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 > refmap_type; + +typedef std::pair id_changeitem_type; +typedef std::list 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::const_iterator it; + const std::list::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 index 000000000..418642738 --- /dev/null +++ b/src/id-clash.h @@ -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 : diff --git a/src/ui/clipboard.cpp b/src/ui/clipboard.cpp index f7967bd40..28dc7d937 100644 --- a/src/ui/clipboard.cpp +++ b/src/ui/clipboard.cpp @@ -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); } }