diff options
| author | Stephen Silver <sasilver@yahoo.com> | 2008-06-24 10:49:28 +0000 |
|---|---|---|
| committer | sasilver <sasilver@users.sourceforge.net> | 2008-06-24 10:49:28 +0000 |
| commit | 48d54d31775d7822ac5ab65614cd71f9a088b65f (patch) | |
| tree | 60597f523326e0d5777ad033ffc7a6baf7436276 /src/id-clash.cpp | |
| parent | Cmake: Add GSL to LIBS and Includes (diff) | |
| download | inkscape-48d54d31775d7822ac5ab65614cd71f9a088b65f.tar.gz inkscape-48d54d31775d7822ac5ab65614cd71f9a088b65f.zip | |
Resolve ID clashes when pasting (fixes bug 165936).
(bzr r6048)
Diffstat (limited to 'src/id-clash.cpp')
| -rw-r--r-- | src/id-clash.cpp | 275 |
1 files changed, 275 insertions, 0 deletions
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 <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 : |
