Code

Move Inkscape::IO::fixupHrefs to Inkscape::XML::rebase_hrefs in new file xml/rebase...
authorpjrm <pjrm@users.sourceforge.net>
Tue, 7 Apr 2009 05:31:32 +0000 (05:31 +0000)
committerpjrm <pjrm@users.sourceforge.net>
Tue, 7 Apr 2009 05:31:32 +0000 (05:31 +0000)
Also create a new function sp_repr_save_rebased_file that changes all xlink:href attributes as it writes them, so that we don't need to modify the owning document.  (Especially useful for "Save a copy" or exporting.)
extension/system.cpp: (Inkscape::Extension::save): Don't call sp_document_set_uri even temporarily other than at the same time as calling rebase_hrefs.  (Otherwise, existing relative hrefs will point nowhere.)
When exporting plain SVG, change the relative hrefs according to the destination filename.

12 files changed:
src/extension/implementation/xslt.cpp
src/extension/internal/gdkpixbuf-input.cpp
src/extension/internal/svg.cpp
src/extension/system.cpp
src/file.cpp
src/file.h
src/main.cpp
src/xml/Makefile_insert
src/xml/rebase-hrefs.cpp [new file with mode: 0644]
src/xml/rebase-hrefs.h [new file with mode: 0644]
src/xml/repr-io.cpp
src/xml/repr.h

index c331cac6069a4ab7aa0e356e0998412ce15f0b89..f34fea64a36a30daaa4f489ec70f20814be59a5c 100644 (file)
@@ -184,16 +184,14 @@ XSLT::open(Inkscape::Extension::Input */*module*/, gchar const *filename)
 void
 XSLT::save(Inkscape::Extension::Output */*module*/, SPDocument *doc, gchar const *filename)
 {
+    /* TODO: Should we assume filename to be in utf8 or to be a raw filename?
+     * See JavaFXOutput::save for discussion. */
     g_return_if_fail(doc != NULL);
     g_return_if_fail(filename != NULL);
 
     Inkscape::XML::Node *repr = NULL;
     repr = sp_document_repr_root (doc);
 
-    gchar *save_path = g_path_get_dirname (filename);
-    Inkscape::IO::fixupHrefs( doc, save_path, true );
-    g_free(save_path);
-
     std::string tempfilename_out;
     int tempfd_out = 0;
     try {
@@ -203,8 +201,8 @@ XSLT::save(Inkscape::Extension::Output */*module*/, SPDocument *doc, gchar const
         return;
     }
 
-    gboolean const s = sp_repr_save_file (repr->document(), tempfilename_out.c_str(), SP_SVG_NS_URI);
-    if (s == FALSE) {
+    if (!sp_repr_save_rebased_file(repr->document(), tempfilename_out.c_str(), SP_SVG_NS_URI,
+                                   doc->base, filename)) {
         throw Inkscape::Extension::Output::save_failed();
     }
 
index e8af0ec9d338b665b8150913e9f35cd26c12dced..f28b017b47cec8a29fcb5faec66780348c1504fd 100644 (file)
@@ -54,7 +54,7 @@ GdkpixbufInput::open(Inkscape::Extension::Input */*mod*/, char const *uri)
         Inkscape::XML::Document *xml_doc = sp_document_repr_doc(doc);
         // import as <image>
         repr = xml_doc->createElement("svg:image");
-        // both are the same, as we don't know our base dir here and cannot relativate href (importer will fixupHrefs):
+        // both are the same, as we don't know our base dir here and cannot relativate href (importer will rebase_hrefs):
         repr->setAttribute("xlink:href", uri);
         repr->setAttribute("sodipodi:absref", uri);
 
index 4a323d7d76ddb814cbdec3312c5def5c89699672..a3589e9051556fe27c523cd368df1b79dfda34b1 100644 (file)
@@ -198,7 +198,7 @@ Svg::save(Inkscape::Extension::Output *mod, SPDocument *doc, gchar const *filena
 
     gchar *save_path = g_path_get_dirname(filename);
 
-    gboolean const spns = (!mod->get_id()
+    bool const spns = ( !mod->get_id()
       || !strcmp (mod->get_id(), SP_MODULE_KEY_OUTPUT_SVG_INKSCAPE)
       || !strcmp (mod->get_id(), SP_MODULE_KEY_OUTPUT_SVGZ_INKSCAPE));
 
@@ -212,10 +212,8 @@ Svg::save(Inkscape::Extension::Output *mod, SPDocument *doc, gchar const *filena
         repr = sp_document_root (doc)->updateRepr(rdoc, repr, SP_OBJECT_WRITE_BUILD);
     }
 
-    Inkscape::IO::fixupHrefs( doc, save_path, spns );
-
-    gboolean const s = sp_repr_save_file(repr->document(), filename, SP_SVG_NS_URI);
-    if (s == FALSE) {
+    if (!sp_repr_save_rebased_file(repr->document(), filename, SP_SVG_NS_URI,
+                                   doc->base, filename)) {
         throw Inkscape::Extension::Output::save_failed();
     }
 
index 6442f2f7eb3638dbc66508eef550f73ab663acdc..37b70fec8325269104d58eb95541f77fa40b5b22 100644 (file)
@@ -29,6 +29,7 @@
 #include "print.h"
 #include "implementation/script.h"
 #include "implementation/xslt.h"
+#include "xml/rebase-hrefs.h"
 /* #include "implementation/plugin.h" */
 
 namespace Inkscape {
@@ -250,12 +251,20 @@ save(Extension *key, SPDocument *doc, gchar const *filename, bool setextension,
     bool saved_modified = false;
     gchar *saved_output_extension = NULL;
     gchar *saved_dataloss = NULL;
-    gchar *saved_uri = NULL;
     if (!official) {
         saved_modified = doc->isModifiedSinceSave();
         saved_output_extension = g_strdup(repr->attribute("inkscape:output_extension"));
         saved_dataloss = g_strdup(repr->attribute("inkscape:dataloss"));
-        saved_uri = g_strdup(doc->uri);
+    } else {
+        /* The document is changing name/uri. */
+
+        /* TODO: Don't treat URIs and filenames interchangeably.
+         * So call g_filename_to_uri when passing to sp_document_set_uri,
+         * and change rebase_hrefs to accept a base URI/LEIRI instead of dir name. */
+        gchar *const new_base = g_path_get_dirname(fileName);
+        Inkscape::XML::rebase_hrefs(doc, new_base, true);
+        sp_document_set_uri(doc, fileName);
+        g_free(new_base);
     }
 
     // Update attributes:
@@ -263,8 +272,6 @@ save(Extension *key, SPDocument *doc, gchar const *filename, bool setextension,
         bool const saved = sp_document_get_undo_sensitive(doc);
         sp_document_set_undo_sensitive(doc, false);
         {
-            // save the filename for next use
-            sp_document_set_uri(doc, fileName);
             // also save the extension for next use
             repr->setAttribute("inkscape:output_extension", omod->get_id());
             // set the "dataloss" attribute if the chosen extension is lossy
@@ -286,14 +293,12 @@ save(Extension *key, SPDocument *doc, gchar const *filename, bool setextension,
         {
             repr->setAttribute("inkscape:output_extension", saved_output_extension);
             repr->setAttribute("inkscape:dataloss", saved_dataloss);
-            sp_document_set_uri(doc, saved_uri);
         }
         sp_document_set_undo_sensitive(doc, saved);
         doc->setModifiedSinceSave(saved_modified);
 
         g_free(saved_output_extension);
         g_free(saved_dataloss);
-        g_free(saved_uri);
     }
 
     g_free(fileName);
index f2a4884d41325644c96c33ece9a38a54b135870d..0bc68f86269c8db7777400a9594704a3e1180368 100644 (file)
@@ -63,6 +63,7 @@
 #include "ui/dialog/ocaldialogs.h"
 #include "ui/view/view-widget.h"
 #include "uri.h"
+#include "xml/rebase-hrefs.h"
 
 #ifdef WITH_GNOME_VFS
 # include <libgnomevfs/gnome-vfs.h>
@@ -924,7 +925,7 @@ file_import(SPDocument *in_doc, const Glib::ustring &uri,
     }
 
     if (doc != NULL) {
-        Inkscape::IO::fixupHrefs(doc, in_doc->base, true);
+        Inkscape::XML::rebase_hrefs(doc, in_doc->base, true);
         Inkscape::XML::Document *xml_in_doc = sp_document_repr_doc(in_doc);
 
         prevent_id_clashes(doc, in_doc);
@@ -1479,102 +1480,6 @@ sp_file_print_preview(gpointer /*object*/, gpointer /*data*/)
 
 }
 
-void Inkscape::IO::fixupHrefs( SPDocument *doc, const gchar *base, gboolean spns )
-{
-    //g_message("Inkscape::IO::fixupHrefs( , [%s], )", base );
-
-    if ( 0 ) {
-        gchar const* things[] = {
-            "data:foo,bar",
-            "http://www.google.com/image.png",
-            "ftp://ssd.com/doo",
-            "/foo/dee/bar.svg",
-            "foo.svg",
-            "file:/foo/dee/bar.svg",
-            "file:///foo/dee/bar.svg",
-            "file:foo.svg",
-            "/foo/bar\xe1\x84\x92.svg",
-            "file:///foo/bar\xe1\x84\x92.svg",
-            "file:///foo/bar%e1%84%92.svg",
-            "/foo/bar%e1%84%92.svg",
-            "bar\xe1\x84\x92.svg",
-            "bar%e1%84%92.svg",
-            NULL
-        };
-        g_message("+------");
-        for ( int i = 0; things[i]; i++ )
-        {
-            try
-            {
-                URI uri(things[i]);
-                gboolean isAbs = g_path_is_absolute( things[i] );
-                gchar *str = uri.toString();
-                g_message( "abs:%d  isRel:%d  scheme:[%s]  path:[%s][%s]   uri[%s] / [%s]", (int)isAbs,
-                           (int)uri.isRelative(),
-                           uri.getScheme(),
-                           uri.getPath(),
-                           uri.getOpaque(),
-                           things[i],
-                           str );
-                g_free(str);
-            }
-            catch ( MalformedURIException err )
-            {
-                dump_str( things[i], "MalformedURIException" );
-                xmlChar *redo = xmlURIEscape((xmlChar const *)things[i]);
-                g_message("    gone from [%s] to [%s]", things[i], redo );
-                if ( redo == NULL )
-                {
-                    URI again = URI::fromUtf8( things[i] );
-                    gboolean isAbs = g_path_is_absolute( things[i] );
-                    gchar *str = again.toString();
-                    g_message( "abs:%d  isRel:%d  scheme:[%s]  path:[%s][%s]   uri[%s] / [%s]", (int)isAbs,
-                               (int)again.isRelative(),
-                               again.getScheme(),
-                               again.getPath(),
-                               again.getOpaque(),
-                               things[i],
-                               str );
-                    g_free(str);
-                    g_message("    ----");
-                }
-            }
-        }
-        g_message("+------");
-    }
-
-    GSList const *images = sp_document_get_resource_list(doc, "image");
-    for (GSList const *l = images; l != NULL; l = l->next) {
-        Inkscape::XML::Node *ir = SP_OBJECT_REPR(l->data);
-
-        const gchar *href = ir->attribute("xlink:href");
-
-        // First try to figure out an absolute path to the asset
-        //g_message("image href [%s]", href );
-        if (spns && !g_path_is_absolute(href)) {
-            const gchar *absref = ir->attribute("sodipodi:absref");
-            const gchar *base_href = g_build_filename(base, href, NULL);
-            //g_message("      absr [%s]", absref );
-
-            if ( absref && Inkscape::IO::file_test(absref, G_FILE_TEST_EXISTS) && !Inkscape::IO::file_test(base_href, G_FILE_TEST_EXISTS))
-            {
-                // only switch over if the absref is valid while href is not
-                href = absref;
-                //g_message("     copied absref to href");
-            }
-        }
-
-        // Once we have an absolute path, convert it relative to the new location
-        if (href && g_path_is_absolute(href)) {
-            const gchar *relname = sp_relative_path_from_path(href, base);
-            //g_message("     setting to [%s]", relname );
-            ir->setAttribute("xlink:href", relname);
-        }
-// TODO next refinement is to make the first choice keeping the relative path as-is if
-//      based on the new location it gives us a valid file.
-    }
-}
-
 
 /*
   Local Variables:
index e90e555bb11d09dcad7191c71a334ea5e0b2a980..ce75a61a7cfff5f0a689bd0574a343a1b1837d2d 100644 (file)
@@ -196,13 +196,16 @@ void sp_file_print_preview (gpointer object, gpointer data);
 void sp_file_vacuum ();
 
 
-namespace Inkscape {
-namespace IO {
-
-void fixupHrefs( SPDocument *doc, const gchar *uri, gboolean spns );
-
-}
-}
+#endif
 
 
-#endif
+/*
+  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:
+*/
+// vi: set autoindent shiftwidth=4 tabstop=8 filetype=cpp expandtab softtabstop=4 encoding=utf-8 textwidth=99 :
index db3255cad750e060643b53a33bfc6c18996f4810..05cfc85787a117035c5141debe49cf6699fc6772 100644 (file)
@@ -983,7 +983,8 @@ void sp_process_file_list(GSList *fl)
                 rdoc = sp_repr_document_new("svg:svg");
                 repr = rdoc->root();
                 repr = sp_document_root(doc)->updateRepr(rdoc, repr, SP_OBJECT_WRITE_BUILD);
-                sp_repr_save_file(repr->document(), sp_export_svg, SP_SVG_NS_URI);
+                sp_repr_save_rebased_file(repr->document(), sp_export_svg, SP_SVG_NS_URI,
+                                          doc->base, sp_export_svg);
             }
             if (sp_export_ps) {
                 do_export_ps_pdf(doc, sp_export_ps, "image/x-postscript");
index f585f140749cd0e109afd2075b5ecfb8bf5a0aad..e4269026c8cde39a0e76d10c1af697eb846e4570 100644 (file)
@@ -16,6 +16,8 @@ ink_common_sources += \
        xml/node-fns.cpp        \
        xml/node-fns.h  \
        xml/pi-node.h   \
+       xml/rebase-hrefs.cpp    \
+       xml/rebase-hrefs.h      \
        xml/repr-io.cpp \
        xml/repr-sorting.cpp    \
        xml/repr-sorting.h      \
diff --git a/src/xml/rebase-hrefs.cpp b/src/xml/rebase-hrefs.cpp
new file mode 100644 (file)
index 0000000..cfe6bef
--- /dev/null
@@ -0,0 +1,281 @@
+#include "xml/rebase-hrefs.h"
+#include "dir-util.h"
+#include "../document.h"  /* Unfortunately there's a separate xml/document.h. */
+#include "io/sys.h"
+#include "sp-object.h"
+#include "streq.h"
+#include "util/share.h"
+#include "xml/attribute-record.h"
+#include "xml/node.h"
+#include <glib/gmem.h>
+#include <glib/gurifuncs.h>
+#include <glib/gutils.h>
+using Inkscape::XML::AttributeRecord;
+
+
+/**
+ * \pre href.
+ */
+static bool
+href_needs_rebasing(char const *const href)
+{
+    g_return_val_if_fail(href, false);
+
+    if (!*href) {
+        return false;
+        /* False (no change) is the right behaviour even when the base URI differs from the
+         * document URI: RFC 3986 defines empty string relative URL as referring to the containing
+         * document, rather than referring to the base URI. */
+    }
+
+    /* Don't change data or http hrefs. */
+    {
+        char *const scheme = g_uri_parse_scheme(href);
+        if (scheme) {
+            /* Assume it shouldn't be changed.  This is probably wrong if the scheme is `file'
+             * (or if the scheme of the new base is non-file, though I believe that never
+             * happens at the time of writing), but that's rare, and we won't try too hard to
+             * handle this now: wait until after the freeze, then add liburiparser (or similar)
+             * as a dependency and do it properly.  For now we'll just try to be simple (while
+             * at least still correctly handling data hrefs). */
+            free(scheme);
+            return false;
+        }
+    }
+
+    /* If absolute then keep it as is.
+     *
+     * Even in the following borderline cases:
+     *
+     *   - We keep it absolute even if it is in new_base (directly or indirectly).
+     *
+     *   - We assume that if xlink:href is absolute then we honour it in preference to
+     *     sodipodi:absref even if sodipodi:absref points to an existing file while xlink:href
+     *     doesn't.  This is because we aren't aware of any bugs in xlink:href handling when
+     *     it's absolute, so we assume that it's the best value to use even in this case.)
+     */
+    if (g_path_is_absolute(href)) {
+        /* No strong preference on what we do for sodipodi:absref.  Once we're
+         * confident of our handling of xlink:href and xlink:base, we should clear it.
+         * Though for the moment we do the simple thing: neither clear nor set it. */
+        return false;
+    }
+
+    return true;
+}
+
+static gchar *
+calc_abs_href(gchar const *const abs_base_dir, gchar const *const href,
+              gchar const *const sp_absref)
+{
+    gchar *ret = g_build_filename(abs_base_dir, href, NULL);
+
+    if ( sp_absref
+         && !Inkscape::IO::file_test(ret,       G_FILE_TEST_EXISTS)
+         &&  Inkscape::IO::file_test(sp_absref, G_FILE_TEST_EXISTS) )
+    {
+        /* sodipodi:absref points to an existing file while xlink:href doesn't.
+         * This could mean that xlink:href is wrong, or it could mean that the user
+         * intends to supply the missing file later.
+         *
+         * Given that we aren't sure what the right behaviour is, and given that a
+         * wrong xlink:href value may mean a bug (as has occurred in the past), we
+         * write a message to stderr. */
+        g_warning("xlink:href points to non-existent file, so using sodipodi:absref instead");
+
+        /* Currently, we choose to use sodipodi:absref in this situation (because we
+         * aren't yet confident in xlink:href interpretation); though note that
+         * honouring a foreign attribute in preference to standard SVG xlink:href and
+         * xlink:base means that we're not a conformant SVG user agent, so eventually
+         * we hope to have enough confidence in our xlink:href and xlink:base handling
+         * to be able to disregard sodipodi:absref.
+         *
+         * effic: Once we no longer consult sodipodi:absref, we can do
+         * `if (base unchanged) { return; }' at the start of rebase_hrefs.
+         */
+        g_free(ret);
+        ret = g_strdup(sp_absref);
+    }
+
+    return ret;
+}
+
+/**
+ * Change relative xlink:href attributes to be relative to \a new_abs_base instead of old_abs_base.
+ *
+ * Note that old_abs_base and new_abs_base must each be non-NULL, absolute directory paths.
+ */
+Inkscape::Util::List<AttributeRecord const>
+Inkscape::XML::rebase_href_attrs(gchar const *const old_abs_base,
+                                 gchar const *const new_abs_base,
+                                 Inkscape::Util::List<AttributeRecord const> attributes)
+{
+    using Inkscape::Util::List;
+    using Inkscape::Util::cons;
+    using Inkscape::Util::ptr_shared;
+    using Inkscape::Util::share_string;
+
+    if (old_abs_base == new_abs_base) {
+        return attributes;
+    }
+
+    GQuark const href_key = g_quark_from_static_string("xlink:href");
+    GQuark const absref_key = g_quark_from_static_string("sodipodi:absref");
+
+    /* First search attributes for xlink:href and sodipodi:absref, putting the rest in ret.
+     *
+     * However, if we find that xlink:href doesn't need rebasing, then return immediately
+     * with no change to attributes. */
+    ptr_shared<char> old_href;
+    ptr_shared<char> sp_absref;
+    List<AttributeRecord const> ret;
+    {
+        for (List<AttributeRecord const> ai(attributes); ai; ++ai) {
+            if (ai->key == href_key) {
+                old_href = ai->value;
+                if (!href_needs_rebasing(old_href)) {
+                    return attributes;
+                }
+            } else if (ai->key == absref_key) {
+                sp_absref = ai->value;
+            } else {
+                ret = cons(AttributeRecord(ai->key, ai->value), ret);
+            }
+        }
+    }
+
+    if (!old_href) {
+        return attributes;
+        /* We could instead return ret in this case, i.e. ensure that sodipodi:absref is cleared if
+         * no xlink:href attribute.  However, retaining it might be more cautious.
+         *
+         * (For the usual case of not present, attributes and ret will be the same except
+         * reversed.) */
+    }
+
+    gchar *const abs_href(calc_abs_href(old_abs_base, old_href, sp_absref));
+    gchar const *const new_href = sp_relative_path_from_path(abs_href, new_abs_base);
+    ret = cons(AttributeRecord(href_key, share_string(new_href)), ret);
+    if (sp_absref) {
+        /* We assume that if there wasn't previously a sodipodi:absref attribute
+         * then we shouldn't create one. */
+        ret = cons(AttributeRecord(absref_key, ( streq(abs_href, sp_absref)
+                                                 ? sp_absref
+                                                 : share_string(abs_href) )),
+                   ret);
+    }
+    g_free(abs_href);
+    return ret;
+}
+
+gchar *
+Inkscape::XML::calc_abs_doc_base(gchar const *const doc_base)
+{
+    /* Note that we don't currently try to handle the case of doc_base containing
+     * `..' or `.' path components.  This non-handling means that sometimes
+     * sp_relative_path_from_path will needlessly give an absolute path.
+     *
+     * It's probably not worth trying to address this until we're using proper
+     * relative URL/IRI href processing (with liburiparser).
+     *
+     * (Note that one possibile difficulty with `..' is symlinks.) */
+
+    if (!doc_base) {
+        return g_get_current_dir();
+    } else if (g_path_is_absolute(doc_base)) {
+        return g_strdup(doc_base);
+    } else {
+        gchar *const cwd = g_get_current_dir();
+        gchar *const ret = g_build_filename(cwd, doc_base, NULL);
+        g_free(cwd);
+        return ret;
+    }
+}
+
+/**
+ * Change relative hrefs in doc to be relative to \a new_base instead of doc.base.
+ *
+ * (NULL doc base or new_base is interpreted as current working directory.)
+ *
+ * \param spns True iff doc should contain sodipodi:absref attributes.
+ */
+void Inkscape::XML::rebase_hrefs(SPDocument *const doc, gchar const *const new_base, bool const spns)
+{
+    gchar *const old_abs_base = calc_abs_doc_base(doc->base);
+    gchar *const new_abs_base = calc_abs_doc_base(new_base);
+
+    /* TODO: Should handle not just image but also:
+     *
+     *    a, altGlyph, animElementAttrs, animate, animateColor, animateMotion, animateTransform,
+     *    animation, audio, color-profile, cursor, definition-src, discard, feImage, filter,
+     *    font-face-uri, foreignObject, glyphRef, handler, linearGradient, mpath, pattern,
+     *    prefetch, radialGradient, script, set, textPath, tref, use, video
+     *
+     * (taken from the union of the xlink:href elements listed at
+     * http://www.w3.org/TR/SVG11/attindex.html and
+     * http://www.w3.org/TR/SVGMobile12/attributeTable.html).
+     *
+     * Also possibly some other attributes of type <URI> or <IRI> or list-thereof, or types like
+     * <paint> that can include an IRI/URI, and stylesheets and style attributes.  (xlink:base is a
+     * special case.  xlink:role and xlink:arcrole can be assumed to be already absolute, based on
+     * http://www.w3.org/TR/SVG11/struct.html#xlinkRefAttrs .)
+     *
+     * Note that it may not useful to set sodipodi:absref for anything other than image.
+     *
+     * Note also that Inkscape only supports fragment hrefs (href="#pattern257") for many of these
+     * cases. */
+    GSList const *images = sp_document_get_resource_list(doc, "image");
+    for (GSList const *l = images; l != NULL; l = l->next) {
+        Inkscape::XML::Node *ir = SP_OBJECT_REPR(l->data);
+
+        gchar const *const href = ir->attribute("xlink:href");
+        /* TODO: Most of this function currently treats href as if it were a simple filename
+         * (e.g. passing it to g_path_is_absolute, g_build_filename or IO::file_test, or avoiding
+         * changing non-file hrefs), which breaks if href starts with a scheme or if href contains
+         * any escaping. */
+
+        if (!href || !href_needs_rebasing(href)) {
+            continue;
+        }
+
+        gchar *const abs_href(calc_abs_href(old_abs_base, href, ir->attribute("sodipodi:absref")));
+
+        /* todo: One difficult case once we support writing to non-file locations is where
+         * existing hrefs in the document point to local files.  In this case, we should
+         * probably copy those referenced files to the new location at the same time.  It's
+         * less clear what to do when copying from one non-file location to another.  We may
+         * need to ask the user in some way (even if it's as a checkbox), but we'd like to
+         * bother the user as little as possible yet also want to warn the user about the case
+         * of file hrefs. */
+
+        gchar const *const new_href = sp_relative_path_from_path(abs_href, new_abs_base);
+        ir->setAttribute("xlink:href", new_href);
+        ir->setAttribute("sodipodi:absref", ( spns
+                                              ? abs_href
+                                              : NULL ));
+        /* impl: I assume that if !spns then any existing sodipodi:absref is about to get
+         * cleared (or is already cleared) anyway, in which case it doesn't matter whether we
+         * clear or leave any existing sodipodi:absref value.  If that assumption turns out to
+         * be wrong, then leaving it means risking leaving the wrong value (if xlink:href
+         * referred to a different file than sodipodi:absref) while clearing it means risking
+         * losing information. */
+
+        g_free(abs_href);
+        /* (No need to free new_href, it's guaranteed to point into used_abs_href.) */
+    }
+
+    g_free(new_abs_base);
+    g_free(old_abs_base);
+}
+
+
+/*
+  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:
+*/
+// vi: set autoindent shiftwidth=4 tabstop=8 filetype=cpp expandtab softtabstop=4 encoding=utf-8 textwidth=99 :
diff --git a/src/xml/rebase-hrefs.h b/src/xml/rebase-hrefs.h
new file mode 100644 (file)
index 0000000..b4f288c
--- /dev/null
@@ -0,0 +1,36 @@
+#ifndef REBASE_HREFS_H_SEEN
+#define REBASE_HREFS_H_SEEN
+
+#include <glib/gtypes.h>
+#include "util/list.h"
+#include "xml/attribute-record.h"
+struct SPDocument;
+
+namespace Inkscape {
+namespace XML {
+
+gchar *calc_abs_doc_base(gchar const *doc_base);
+
+void rebase_hrefs(SPDocument *doc, gchar const *new_base, bool spns);
+
+Inkscape::Util::List<AttributeRecord const> rebase_href_attrs(
+    gchar const *old_abs_base,
+    gchar const *new_abs_base,
+    Inkscape::Util::List<AttributeRecord const> attributes);
+
+}
+}
+
+
+#endif /* !REBASE_HREFS_H_SEEN */
+
+/*
+  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:
+*/
+// vi: set autoindent shiftwidth=4 tabstop=8 filetype=cpp expandtab softtabstop=4 encoding=utf-8 textwidth=99 :
index a659ab61f5ac3f0cb1345ab6ea837e1c28afd86f..fa5e9b6ed3bc8f78aac7061eede1511d61ce3af8 100644 (file)
@@ -22,6 +22,7 @@
 
 #include "xml/repr.h"
 #include "xml/attribute-record.h"
+#include "xml/rebase-hrefs.h"
 #include "xml/simple-document.h"
 
 #include "io/sys.h"
@@ -40,12 +41,24 @@ using Inkscape::XML::Document;
 using Inkscape::XML::SimpleDocument;
 using Inkscape::XML::Node;
 using Inkscape::XML::AttributeRecord;
+using Inkscape::XML::calc_abs_doc_base;
+using Inkscape::XML::rebase_href_attrs;
 
 Document *sp_repr_do_read (xmlDocPtr doc, const gchar *default_ns);
 static Node *sp_repr_svg_read_node (Document *xml_doc, xmlNodePtr node, const gchar *default_ns, GHashTable *prefix_map);
 static gint sp_repr_qualified_name (gchar *p, gint len, xmlNsPtr ns, const xmlChar *name, const gchar *default_ns, GHashTable *prefix_map);
-static void sp_repr_write_stream_root_element (Node *repr, Writer &out, bool add_whitespace, gchar const *default_ns, int inlineattrs, int indent);
-static void sp_repr_write_stream_element (Node *repr, Writer &out, gint indent_level, bool add_whitespace, Glib::QueryQuark elide_prefix, List<AttributeRecord const> attributes, int inlineattrs, int indent);
+static void sp_repr_write_stream_root_element(Node *repr, Writer &out,
+                                              bool add_whitespace, gchar const *default_ns,
+                                              int inlineattrs, int indent,
+                                              gchar const *old_href_abs_base,
+                                              gchar const *new_href_abs_base);
+static void sp_repr_write_stream_element(Node *repr, Writer &out,
+                                         gint indent_level, bool add_whitespace,
+                                         Glib::QueryQuark elide_prefix,
+                                         List<AttributeRecord const> attributes,
+                                         int inlineattrs, int indent,
+                                         gchar const *old_href_abs_base,
+                                         gchar const *new_href_abs_base);
 
 #ifdef HAVE_LIBWMF
 static xmlDocPtr sp_wmf_convert (const char * file_name);
@@ -530,7 +543,9 @@ sp_repr_svg_read_node (Document *xml_doc, xmlNodePtr node, const gchar *default_
 
 static void
 sp_repr_save_writer(Document *doc, Inkscape::IO::Writer *out,
-              gchar const *default_ns)
+                    gchar const *default_ns,
+                    gchar const *old_href_abs_base,
+                    gchar const *new_href_abs_base)
 {
     Inkscape::Preferences *prefs = Inkscape::Preferences::get();
     bool inlineattrs = prefs->getBool("/options/svgoutput/inlineattrs");
@@ -549,9 +564,11 @@ sp_repr_save_writer(Document *doc, Inkscape::IO::Writer *out,
     {
         Inkscape::XML::NodeType const node_type = repr->type();
         if ( node_type == Inkscape::XML::ELEMENT_NODE ) {
-            sp_repr_write_stream_root_element(repr, *out, TRUE, default_ns, inlineattrs, indent);
+            sp_repr_write_stream_root_element(repr, *out, TRUE, default_ns, inlineattrs, indent,
+                                              old_href_abs_base, new_href_abs_base);
         } else {
-            sp_repr_write_stream(repr, *out, 0, TRUE, GQuark(0), inlineattrs, indent);
+            sp_repr_write_stream(repr, *out, 0, TRUE, GQuark(0), inlineattrs, indent,
+                                 old_href_abs_base, new_href_abs_base);
             if ( node_type == Inkscape::XML::COMMENT_NODE ) {
                 out->writeChar('\n');
             }
@@ -568,7 +585,7 @@ sp_repr_save_buf(Document *doc)
     Inkscape::IO::StringOutputStream souts;
     Inkscape::IO::OutputStreamWriter outs(souts);
 
-    sp_repr_save_writer(doc, &outs, SP_INKSCAPE_NS_URI);
+    sp_repr_save_writer(doc, &outs, SP_INKSCAPE_NS_URI, 0, 0);
 
        outs.close();
        Glib::ustring buf = souts.getString();
@@ -581,29 +598,37 @@ sp_repr_save_buf(Document *doc)
 
 
 void
-sp_repr_save_stream (Document *doc, FILE *fp, gchar const *default_ns, bool compress)
+sp_repr_save_stream(Document *doc, FILE *fp, gchar const *default_ns, bool compress,
+                    gchar const *const old_href_abs_base,
+                    gchar const *const new_href_abs_base)
 {
     Inkscape::URI dummy("x");
     Inkscape::IO::UriOutputStream bout(fp, dummy);
     Inkscape::IO::GzipOutputStream *gout = compress ? new Inkscape::IO::GzipOutputStream(bout) : NULL;
     Inkscape::IO::OutputStreamWriter *out  = compress ? new Inkscape::IO::OutputStreamWriter( *gout ) : new Inkscape::IO::OutputStreamWriter( bout );
 
-    sp_repr_save_writer(doc, out, default_ns);
-    
+    sp_repr_save_writer(doc, out, default_ns, old_href_abs_base, new_href_abs_base);
+
     delete out;
     delete gout;
 }
 
 
 
-/* Returns TRUE if file successfully saved; FALSE if not
+/**
+ * Returns true iff file successfully saved.
+ *
+ * \param filename The actual file to do I/O to, which might be a temp file.
+ *
+ * \param for_filename The base URI [actually filename] to assume for purposes of rewriting
+ *              xlink:href attributes.
  */
 bool
-sp_repr_save_file (Document *doc, gchar const *filename,
-                   gchar const *default_ns)
+sp_repr_save_rebased_file(Document *doc, gchar const *const filename, gchar const *default_ns,
+                          gchar const *old_base, gchar const *for_filename)
 {
-    if (filename == NULL) {
-        return FALSE;
+    if (!filename) {
+        return false;
     }
 
     bool compress;
@@ -616,18 +641,49 @@ sp_repr_save_file (Document *doc, gchar const *filename,
     Inkscape::IO::dump_fopen_call( filename, "B" );
     FILE *file = Inkscape::IO::fopen_utf8name(filename, "w");
     if (file == NULL) {
-        return FALSE;
+        return false;
     }
 
-    sp_repr_save_stream (doc, file, default_ns, compress);
+    gchar *old_href_abs_base = NULL;
+    gchar *new_href_abs_base = NULL;
+    if (for_filename) {
+        old_href_abs_base = calc_abs_doc_base(old_base);
+        if (g_path_is_absolute(for_filename)) {
+            new_href_abs_base = g_path_get_dirname(for_filename);
+        } else {
+            gchar *const cwd = g_get_current_dir();
+            gchar *const for_abs_filename = g_build_filename(cwd, for_filename, NULL);
+            g_free(cwd);
+            new_href_abs_base = g_path_get_dirname(for_abs_filename);
+            g_free(for_abs_filename);
+        }
+
+        /* effic: Once we're confident that we never need (or never want) to resort
+         * to using sodipodi:absref instead of the xlink:href value,
+         * then we should do `if streq() { free them and set both to NULL; }'. */
+    }
+    sp_repr_save_stream(doc, file, default_ns, compress, old_href_abs_base, new_href_abs_base);
+
+    g_free(old_href_abs_base);
+    g_free(new_href_abs_base);
 
     if (fclose (file) != 0) {
-        return FALSE;
+        return false;
     }
 
-    return TRUE;
+    return true;
 }
 
+/**
+ * Returns true iff file successfully saved.
+ */
+bool
+sp_repr_save_file(Document *doc, gchar const *const filename, gchar const *default_ns)
+{
+    return sp_repr_save_rebased_file(doc, filename, default_ns, NULL, NULL);
+}
+
+
 /* (No doubt this function already exists elsewhere.) */
 static void
 repr_quote_write (Writer &out, const gchar * val)
@@ -737,11 +793,15 @@ void populate_ns_map(NSMap &ns_map, Node &repr) {
 
 }
 
-void
-sp_repr_write_stream_root_element (Node *repr, Writer &out, bool add_whitespace, gchar const *default_ns, 
-                                   int inlineattrs, int indent)
+static void
+sp_repr_write_stream_root_element(Node *repr, Writer &out,
+                                  bool add_whitespace, gchar const *default_ns,
+                                  int inlineattrs, int indent,
+                                  gchar const *const old_href_base,
+                                  gchar const *const new_href_base)
 {
     using Inkscape::Util::ptr_shared;
+
     g_assert(repr != NULL);
     Glib::QueryQuark xml_prefix=g_quark_from_static_string("xml");
 
@@ -777,11 +837,15 @@ sp_repr_write_stream_root_element (Node *repr, Writer &out, bool add_whitespace,
         }
     }
 
-    return sp_repr_write_stream_element(repr, out, 0, add_whitespace, elide_prefix, attributes, inlineattrs, indent);
+    return sp_repr_write_stream_element(repr, out, 0, add_whitespace, elide_prefix, attributes,
+                                        inlineattrs, indent, old_href_base, new_href_base);
 }
 
 void sp_repr_write_stream( Node *repr, Writer &out, gint indent_level,
-                           bool add_whitespace, Glib::QueryQuark elide_prefix, int inlineattrs, int indent)
+                           bool add_whitespace, Glib::QueryQuark elide_prefix,
+                           int inlineattrs, int indent,
+                           gchar const *const old_href_base,
+                           gchar const *const new_href_base)
 {
     switch (repr->type()) {
         case Inkscape::XML::TEXT_NODE: {
@@ -799,7 +863,9 @@ void sp_repr_write_stream( Node *repr, Writer &out, gint indent_level,
         case Inkscape::XML::ELEMENT_NODE: {
             sp_repr_write_stream_element( repr, out, indent_level,
                                           add_whitespace, elide_prefix,
-                                          repr->attributeList(), inlineattrs, indent);
+                                          repr->attributeList(),
+                                          inlineattrs, indent,
+                                          old_href_base, new_href_base);
             break;
         }
         case Inkscape::XML::DOCUMENT_NODE: {
@@ -813,12 +879,14 @@ void sp_repr_write_stream( Node *repr, Writer &out, gint indent_level,
 }
 
 
-void
+static void
 sp_repr_write_stream_element (Node * repr, Writer & out, gint indent_level,
                               bool add_whitespace,
                               Glib::QueryQuark elide_prefix,
                               List<AttributeRecord const> attributes, 
-                              int inlineattrs, int indent)
+                              int inlineattrs, int indent,
+                              gchar const *const old_href_base,
+                              gchar const *const new_href_base)
 {
     Node *child;
     bool loose;
@@ -850,10 +918,11 @@ sp_repr_write_stream_element (Node * repr, Writer & out, gint indent_level,
     // for its content and children:
     gchar const *xml_space_attr = repr->attribute("xml:space");
     if (xml_space_attr != NULL && !strcmp(xml_space_attr, "preserve")) {
-        add_whitespace = FALSE;
+        add_whitespace = false;
     }
 
-    for ( List<AttributeRecord const> iter = attributes ;
+    for ( List<AttributeRecord const> iter = rebase_href_attrs(old_href_base, new_href_base,
+                                                               attributes);
           iter ; ++iter )
     {
         if (!inlineattrs) {
@@ -884,7 +953,9 @@ sp_repr_write_stream_element (Node * repr, Writer & out, gint indent_level,
             out.writeString( "\n" );
         }
         for (child = repr->firstChild(); child != NULL; child = child->next()) {
-            sp_repr_write_stream (child, out, (loose) ? (indent_level + 1) : 0, add_whitespace, elide_prefix, inlineattrs, indent);
+            sp_repr_write_stream(child, out, ( loose ? indent_level + 1 : 0 ),
+                                 add_whitespace, elide_prefix, inlineattrs, indent,
+                                 old_href_base, new_href_base);
         }
 
         if (loose && add_whitespace && indent) {
index bbab619f0d656ce0866e3f72bd7e9a99ff28c4cf..549822e4e1a4c82e7509d51eb2ec805ee3ffe965 100644 (file)
@@ -71,13 +71,21 @@ inline Inkscape::XML::Node *sp_repr_next(Inkscape::XML::Node *repr) {
 
 Inkscape::XML::Document *sp_repr_read_file(gchar const *filename, gchar const *default_ns);
 Inkscape::XML::Document *sp_repr_read_mem(gchar const *buffer, int length, gchar const *default_ns);
-void sp_repr_write_stream (Inkscape::XML::Node *repr, Inkscape::IO::Writer &out,
-                 gint indent_level,  bool add_whitespace, Glib::QueryQuark elide_prefix,
-                                int inlineattrs, int indent);
+void sp_repr_write_stream(Inkscape::XML::Node *repr, Inkscape::IO::Writer &out,
+                          gint indent_level,  bool add_whitespace, Glib::QueryQuark elide_prefix,
+                          int inlineattrs, int indent,
+                          gchar const *old_href_base = NULL,
+                          gchar const *new_href_base = NULL);
 Inkscape::XML::Document *sp_repr_read_buf (const Glib::ustring &buf, const gchar *default_ns);
 Glib::ustring sp_repr_save_buf(Inkscape::XML::Document *doc);
-void sp_repr_save_stream(Inkscape::XML::Document *doc, FILE *to_file, gchar const *default_ns=NULL, bool compress = false);
+void sp_repr_save_stream(Inkscape::XML::Document *doc, FILE *to_file,
+                         gchar const *default_ns = NULL, bool compress = false,
+                         gchar const *old_href_base = NULL,
+                         gchar const *new_href_base = NULL);
 bool sp_repr_save_file(Inkscape::XML::Document *doc, gchar const *filename, gchar const *default_ns=NULL);
+bool sp_repr_save_rebased_file(Inkscape::XML::Document *doc, gchar const *filename_utf8,
+                               gchar const *default_ns,
+                               gchar const *old_base, gchar const *new_base_filename);
 
 
 /* CSS stuff */