From: pjrm Date: Tue, 7 Apr 2009 05:31:32 +0000 (+0000) Subject: Move Inkscape::IO::fixupHrefs to Inkscape::XML::rebase_hrefs in new file xml/rebase... X-Git-Url: https://git.tokkee.org/?a=commitdiff_plain;h=ec35142cad8ce4f534989cacd6cc98e276263548;p=inkscape.git Move Inkscape::IO::fixupHrefs to Inkscape::XML::rebase_hrefs in new file xml/rebase-hrefs.*. Give it the old and new base directory, so that it can change hrefs without relying on sodipodi:absref. 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. --- diff --git a/src/extension/implementation/xslt.cpp b/src/extension/implementation/xslt.cpp index c331cac60..f34fea64a 100644 --- a/src/extension/implementation/xslt.cpp +++ b/src/extension/implementation/xslt.cpp @@ -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(); } diff --git a/src/extension/internal/gdkpixbuf-input.cpp b/src/extension/internal/gdkpixbuf-input.cpp index e8af0ec9d..f28b017b4 100644 --- a/src/extension/internal/gdkpixbuf-input.cpp +++ b/src/extension/internal/gdkpixbuf-input.cpp @@ -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 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); diff --git a/src/extension/internal/svg.cpp b/src/extension/internal/svg.cpp index 4a323d7d7..a3589e905 100644 --- a/src/extension/internal/svg.cpp +++ b/src/extension/internal/svg.cpp @@ -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(); } diff --git a/src/extension/system.cpp b/src/extension/system.cpp index 6442f2f7e..37b70fec8 100644 --- a/src/extension/system.cpp +++ b/src/extension/system.cpp @@ -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); diff --git a/src/file.cpp b/src/file.cpp index f2a4884d4..0bc68f862 100644 --- a/src/file.cpp +++ b/src/file.cpp @@ -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 @@ -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: diff --git a/src/file.h b/src/file.h index e90e555bb..ce75a61a7 100644 --- a/src/file.h +++ b/src/file.h @@ -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 : diff --git a/src/main.cpp b/src/main.cpp index db3255cad..05cfc8578 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -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"); diff --git a/src/xml/Makefile_insert b/src/xml/Makefile_insert index f585f1407..e4269026c 100644 --- a/src/xml/Makefile_insert +++ b/src/xml/Makefile_insert @@ -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 index 000000000..cfe6bef7c --- /dev/null +++ b/src/xml/rebase-hrefs.cpp @@ -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 +#include +#include +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 +Inkscape::XML::rebase_href_attrs(gchar const *const old_abs_base, + gchar const *const new_abs_base, + Inkscape::Util::List 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 old_href; + ptr_shared sp_absref; + List ret; + { + for (List 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 or or list-thereof, or types like + * 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 index 000000000..b4f288c4d --- /dev/null +++ b/src/xml/rebase-hrefs.h @@ -0,0 +1,36 @@ +#ifndef REBASE_HREFS_H_SEEN +#define REBASE_HREFS_H_SEEN + +#include +#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 rebase_href_attrs( + gchar const *old_abs_base, + gchar const *new_abs_base, + Inkscape::Util::List 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 : diff --git a/src/xml/repr-io.cpp b/src/xml/repr-io.cpp index a659ab61f..fa5e9b6ed 100644 --- a/src/xml/repr-io.cpp +++ b/src/xml/repr-io.cpp @@ -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 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 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 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 iter = attributes ; + for ( List 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) { diff --git a/src/xml/repr.h b/src/xml/repr.h index bbab619f0..549822e4e 100644 --- a/src/xml/repr.h +++ b/src/xml/repr.h @@ -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 */