rebase-hrefs.cpp 6.13 KB
Newer Older
Max Gaukler's avatar
Max Gaukler committed
1
2
3
4
5
6
7
8
9
// SPDX-License-Identifier: GPL-2.0-or-later
/** @file
 * TODO: insert short description here
 *//*
 * Authors: see git history
 *
 * Copyright (C) 2018 Authors
 * Released under GNU GPL v2+, read the file 'COPYING' for more information.
 */
10
11
12
13

#include <glibmm/convert.h>
#include <glibmm/miscutils.h>
#include <glibmm/uriutils.h>
Thomas Holder's avatar
Thomas Holder committed
14
#include <glibmm/utility.h>
15

16
#include "../document.h"  /* Unfortunately there's a separate xml/document.h. */
17
#include "streq.h"
18

19
#include "io/dir-util.h"
20
21
22
#include "io/sys.h"

#include "object/sp-object.h"
23
#include "object/uri.h"
24

25
#include "xml/node.h"
26
#include "xml/rebase-hrefs.h"
27

28
using Inkscape::XML::AttributeRecord;
29
using Inkscape::XML::AttributeVector;
30
31

/**
32
 * Determine if a href needs rebasing.
33
 */
34
static bool href_needs_rebasing(char const *href)
35
{
36
37
38
39
    // RFC 3986 defines empty string relative URL as referring to the
    // containing document, rather than referring to the base URI.
    if (!href[0] || href[0] == '#') {
        return false;
40
41
    }

Thomas Holder's avatar
Thomas Holder committed
42
43
44
45
46
    // skip document-local queries
    if (href[0] == '?') {
        return false;
    }

47
48
49
50
51
    // skip absolute-path and network-path references
    if (href[0] == '/') {
        return false;
    }

52
    // Don't change non-file URIs (like data or http)
Thomas Holder's avatar
Thomas Holder committed
53
54
    auto scheme = Glib::make_unique_ptr_gfree(g_uri_parse_scheme(href));
    return !scheme || g_str_equal(scheme.get(), "file");
55
56
}

57
AttributeVector
58
59
Inkscape::XML::rebase_href_attrs(gchar const *const old_abs_base,
                                 gchar const *const new_abs_base,
60
                                 const AttributeVector &attributes)
61
62
63
{
    using Inkscape::Util::share_string;

64
    auto ret = attributes; // copy
65

66
    if (old_abs_base == new_abs_base) {
67
        return ret;
68
69
    }

70
71
    static GQuark const href_key = g_quark_from_static_string("xlink:href");
    static GQuark const absref_key = g_quark_from_static_string("sodipodi:absref");
72

73
74
75
76
77
78
79
    auto const find_record = [&ret](GQuark const key) {
        return find_if(ret.begin(), ret.end(), [key](auto const &attr) { return attr.key == key; });
    };

    auto href_it = find_record(href_key);
    if (href_it == ret.end() || !href_needs_rebasing(href_it->value.pointer())) {
        return ret;
80
81
    }

82
    auto uri = URI::from_href_and_basedir(href_it->value.pointer(), old_abs_base);
83
84
    auto abs_href = uri.toNativeFilename();

85
86
87
88
89
90
91
92
93
    auto absref_it = find_record(absref_key);
    if (absref_it != ret.end()) {
        if (g_file_test(abs_href.c_str(), G_FILE_TEST_EXISTS)) {
            if (!streq(abs_href.c_str(), absref_it->value.pointer())) {
                absref_it->value = share_string(abs_href.c_str());
            }
        } else if (g_file_test(absref_it->value.pointer(), G_FILE_TEST_EXISTS)) {
            uri = URI::from_native_filename(absref_it->value.pointer());
        }
94
95
    }

Thomas Holder's avatar
Thomas Holder committed
96
    std::string baseuri;
97
    if (new_abs_base && new_abs_base[0]) {
Thomas Holder's avatar
Thomas Holder committed
98
99
100
101
        baseuri = URI::from_dirname(new_abs_base).str();
    }

    auto new_href = uri.str(baseuri.c_str());
102
    href_it->value = share_string(new_href.c_str());
103

104
105
106
    return ret;
}

107
void Inkscape::XML::rebase_hrefs(SPDocument *const doc, gchar const *const new_base, bool const spns)
108
{
109
    using Inkscape::URI;
Dmitry Kirsanov's avatar
Dmitry Kirsanov committed
110

111
    std::string old_base_url_str = URI::from_dirname(doc->getDocumentBase()).str();
112
113
114
115
116
    std::string new_base_url_str;

    if (new_base) {
        new_base_url_str = URI::from_dirname(new_base).str();
    }
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137

    /* 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. */
138
    std::vector<SPObject *> images = doc->getResourceList("image");
Marc Jeanmougin's avatar
Marc Jeanmougin committed
139
140
    for (auto image : images) {
        Inkscape::XML::Node *ir = image->getRepr();
141

142
143
144
        auto href_cstr = ir->attribute("xlink:href");
        if (!href_cstr) {
            continue;
145
        }
146

Thomas Holder's avatar
Thomas Holder committed
147
        if (!href_needs_rebasing(href_cstr)) {
148
149
150
            continue;
        }

151
152
153
154
155
156
        // make absolute
        URI url;
        try {
            url = URI(href_cstr, old_base_url_str.c_str());
        } catch (...) {
            continue;
157
158
        }

159
160
161
        // skip non-file URLs
        if (!url.hasScheme("file")) {
            continue;
162
163
        }

164
165
166
167
168
169
170
        // if path doesn't exist, use sodipodi:absref
        if (!g_file_test(url.toNativeFilename().c_str(), G_FILE_TEST_EXISTS)) {
            auto spabsref = ir->attribute("sodipodi:absref");
            if (spabsref && g_file_test(spabsref, G_FILE_TEST_EXISTS)) {
                url = URI::from_native_filename(spabsref);
            }
        } else if (spns) {
171
            ir->setAttributeOrRemoveIfEmpty("sodipodi:absref", url.toNativeFilename());
172
        }
173

174
        if (!spns) {
175
            ir->removeAttribute("sodipodi:absref");
176
177
        }

178
        auto href_str = url.str(new_base_url_str.c_str());
179
180
        href_str = Inkscape::uri_to_iri(href_str.c_str());

181
        ir->setAttribute("xlink:href", href_str);
182
    }
183

184
    doc->setDocumentBase(new_base);
185
186
187
188
189
190
191
192
193
194
195
196
}


/*
  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:
*/
197
// vi: set autoindent shiftwidth=4 tabstop=8 filetype=cpp expandtab softtabstop=4 fileencoding=utf-8 textwidth=99 :