QGIS API Documentation 3.28.0-Firenze (ed3ad0430f)
qgspathresolver.cpp
Go to the documentation of this file.
1/***************************************************************************
2 qgspathresolver.cpp
3 --------------------------------------
4 Date : February 2017
5 Copyright : (C) 2017 by Martin Dobias
6 Email : wonder dot sk at gmail dot com
7 ***************************************************************************
8 * *
9 * This program is free software; you can redistribute it and/or modify *
10 * it under the terms of the GNU General Public License as published by *
11 * the Free Software Foundation; either version 2 of the License, or *
12 * (at your option) any later version. *
13 * *
14 ***************************************************************************/
15
16#include "qgspathresolver.h"
18
19#include "qgis.h"
20#include "qgsapplication.h"
21#include <QDir>
22#include <QFileInfo>
23#include <QUrl>
24#include <QUuid>
25
26
27typedef std::vector< std::pair< QString, std::function< QString( const QString & ) > > > CustomResolvers;
28Q_GLOBAL_STATIC( CustomResolvers, sCustomResolvers )
29Q_GLOBAL_STATIC( CustomResolvers, sCustomWriters )
30
31QgsPathResolver::QgsPathResolver( const QString &baseFileName, const QString &attachmentDir )
32 : mBaseFileName( baseFileName ), mAttachmentDir( attachmentDir )
33{
34}
35
36
37QString QgsPathResolver::readPath( const QString &f ) const
38{
39 QString filename = f;
40
41 const CustomResolvers customResolvers = *sCustomResolvers();
42 for ( const auto &resolver : customResolvers )
43 filename = resolver.second( filename );
44
45 if ( filename.isEmpty() )
46 return QString();
47
48 QString src = filename;
49 if ( src.startsWith( QLatin1String( "inbuilt:" ) ) )
50 {
51 // strip away "inbuilt:" prefix, replace with actual inbuilt data folder path
52 return QgsApplication::pkgDataPath() + QStringLiteral( "/resources" ) + src.mid( 8 );
53 }
54
55 if ( src.startsWith( QLatin1String( "localized:" ) ) )
56 {
57 QStringList parts = src.split( "|" );
58 // strip away "localized:" prefix, replace with actual inbuilt data folder path
59 parts[0] = QgsApplication::localizedDataPathRegistry()->globalPath( parts[0].mid( 10 ) ) ;
60 if ( !parts[0].isEmpty() )
61 {
62 return parts.join( "|" );
63 }
64 else
65 {
66 return QString();
67 }
68 }
69 if ( src.startsWith( QLatin1String( "attachment:" ) ) )
70 {
71 // resolve attachment w.r.t. temporary path where project archive is extracted
72 return QDir( mAttachmentDir ).absoluteFilePath( src.mid( 11 ) );
73 }
74
75 if ( mBaseFileName.isNull() )
76 {
77 return src;
78 }
79
80 // if this is a VSIFILE, remove the VSI prefix and append to final result
81 QString vsiPrefix = qgsVsiPrefix( src );
82 if ( ! vsiPrefix.isEmpty() )
83 {
84 // unfortunately qgsVsiPrefix returns prefix also for files like "/x/y/z.gz"
85 // so we need to check if we really have the prefix
86 if ( src.startsWith( QLatin1String( "/vsi" ), Qt::CaseInsensitive ) )
87 src.remove( 0, vsiPrefix.size() );
88 else
89 vsiPrefix.clear();
90 }
91
92 // relative path should always start with ./ or ../
93 if ( !src.startsWith( QLatin1String( "./" ) ) && !src.startsWith( QLatin1String( "../" ) ) )
94 {
95#if defined(Q_OS_WIN)
96 if ( src.startsWith( "\\\\" ) ||
97 src.startsWith( "//" ) ||
98 ( src[0].isLetter() && src[1] == ':' ) )
99 {
100 // UNC or absolute path
101 return vsiPrefix + src;
102 }
103#else
104 if ( src[0] == '/' )
105 {
106 // absolute path
107 return vsiPrefix + src;
108 }
109#endif
110
111 // so this one isn't absolute, but also doesn't start // with ./ or ../.
112 // That means that it was saved with an earlier version of "relative path support",
113 // where the source file had to exist and only the project directory was stripped
114 // from the filename.
115
116 const QFileInfo pfi( mBaseFileName );
117 const QString home = pfi.absolutePath();
118 if ( home.isEmpty() )
119 return vsiPrefix + src;
120
121 const QFileInfo fi( home + '/' + src );
122
123 if ( !fi.exists() )
124 {
125 return vsiPrefix + src;
126 }
127 else
128 {
129 return vsiPrefix + fi.canonicalFilePath();
130 }
131 }
132
133 QString srcPath = src;
134 QString projPath = mBaseFileName;
135
136 if ( projPath.isEmpty() )
137 {
138 return vsiPrefix + src;
139 }
140
141#if defined(Q_OS_WIN)
142 srcPath.replace( '\\', '/' );
143 projPath.replace( '\\', '/' );
144
145 bool uncPath = projPath.startsWith( "//" );
146#endif
147
148 // Make sure the path is absolute (see GH #33200)
149 projPath = QFileInfo( projPath ).absoluteFilePath();
150
151#if QT_VERSION < QT_VERSION_CHECK(5, 15, 0)
152 QStringList srcElems = srcPath.split( '/', QString::SkipEmptyParts );
153 QStringList projElems = projPath.split( '/', QString::SkipEmptyParts );
154#else
155 const QStringList srcElems = srcPath.split( '/', Qt::SkipEmptyParts );
156 QStringList projElems = projPath.split( '/', Qt::SkipEmptyParts );
157#endif
158
159#if defined(Q_OS_WIN)
160 if ( uncPath )
161 {
162 projElems.insert( 0, "" );
163 projElems.insert( 0, "" );
164 }
165#endif
166
167 // remove project file element
168 projElems.removeLast();
169
170 // append source path elements
171 projElems << srcElems;
172 projElems.removeAll( QStringLiteral( "." ) );
173
174 // resolve ..
175 int pos;
176 while ( ( pos = projElems.indexOf( QLatin1String( ".." ) ) ) > 0 )
177 {
178 // remove preceding element and ..
179 projElems.removeAt( pos - 1 );
180 projElems.removeAt( pos - 1 );
181 }
182
183#if !defined(Q_OS_WIN)
184 // make path absolute
185 projElems.prepend( QString() );
186#endif
187
188 return vsiPrefix + projElems.join( QLatin1Char( '/' ) );
189}
190
191QString QgsPathResolver::setPathPreprocessor( const std::function<QString( const QString & )> &processor )
192{
193 QString id = QUuid::createUuid().toString();
194 sCustomResolvers()->emplace_back( std::make_pair( id, processor ) );
195 return id;
196}
197
199{
200 const size_t prevCount = sCustomResolvers()->size();
201 sCustomResolvers()->erase( std::remove_if( sCustomResolvers()->begin(), sCustomResolvers()->end(), [id]( std::pair< QString, std::function< QString( const QString & ) > > &a )
202 {
203 return a.first == id;
204 } ), sCustomResolvers()->end() );
205 return prevCount != sCustomResolvers()->size();
206}
207
208QString QgsPathResolver::setPathWriter( const std::function<QString( const QString & )> &writer )
209{
210 QString id = QUuid::createUuid().toString();
211 sCustomWriters()->emplace_back( std::make_pair( id, writer ) );
212 return id;
213}
214
215bool QgsPathResolver::removePathWriter( const QString &id )
216{
217 const size_t prevCount = sCustomWriters->size();
218 sCustomWriters()->erase( std::remove_if( sCustomWriters->begin(), sCustomWriters->end(), [id]( std::pair< QString, std::function< QString( const QString & ) > > &a )
219 {
220 return a.first == id;
221 } ), sCustomWriters->end() );
222 return prevCount != sCustomWriters->size();
223}
224
225QString QgsPathResolver::writePath( const QString &s ) const
226{
227 QString src = s;
228 if ( src.isEmpty() )
229 {
230 return src;
231 }
232
233 const QString localizedPath = QgsApplication::localizedDataPathRegistry()->localizedPath( src );
234 if ( !localizedPath.isEmpty() )
235 return QStringLiteral( "localized:" ) + localizedPath;
236
237 const CustomResolvers customWriters = *sCustomWriters();
238 for ( const auto &writer : customWriters )
239 src = writer.second( src );
240
241 if ( src.startsWith( QgsApplication::pkgDataPath() + QStringLiteral( "/resources" ) ) )
242 {
243 // replace inbuilt data folder path with "inbuilt:" prefix
244 return QStringLiteral( "inbuilt:" ) + src.mid( QgsApplication::pkgDataPath().length() + 10 );
245 }
246
247 if ( !mAttachmentDir.isEmpty() && src.startsWith( mAttachmentDir ) )
248 {
249 // Replace attachment dir with "attachment:" prefix
250 return QStringLiteral( "attachment:" ) + QFileInfo( src ).fileName();
251 }
252
253 if ( mBaseFileName.isEmpty() )
254 {
255 return src;
256 }
257
258 // Get projPath even if project has not been created yet
259 const QFileInfo pfi( QFileInfo( mBaseFileName ).path() );
260 QString projPath = pfi.canonicalFilePath();
261
262 // If project directory doesn't exit, fallback to absoluteFilePath : symbolic
263 // links won't be handled correctly, but that's OK as the path is "virtual".
264 if ( projPath.isEmpty() )
265 projPath = pfi.absoluteFilePath();
266
267 if ( projPath.isEmpty() )
268 {
269 return src;
270 }
271
272 // Check if it is a publicSource uri and clean it
273 const QUrl url { src };
274 QString srcPath { src };
275 QString urlQuery;
276
277 if ( url.isLocalFile( ) )
278 {
279 srcPath = url.path();
280 urlQuery = url.query();
281 }
282
283 const QFileInfo srcFileInfo( srcPath );
284 if ( srcFileInfo.exists() )
285 srcPath = srcFileInfo.canonicalFilePath();
286
287 // if this is a VSIFILE, remove the VSI prefix and append to final result
288 const QString vsiPrefix = qgsVsiPrefix( src );
289 if ( ! vsiPrefix.isEmpty() )
290 {
291 srcPath.remove( 0, vsiPrefix.size() );
292 }
293
294#if defined( Q_OS_WIN )
295 const Qt::CaseSensitivity cs = Qt::CaseInsensitive;
296
297 srcPath.replace( '\\', '/' );
298
299 if ( srcPath.startsWith( "//" ) )
300 {
301 // keep UNC prefix
302 srcPath = "\\\\" + srcPath.mid( 2 );
303 }
304
305 projPath.replace( '\\', '/' );
306 if ( projPath.startsWith( "//" ) )
307 {
308 // keep UNC prefix
309 projPath = "\\\\" + projPath.mid( 2 );
310 }
311#else
312 const Qt::CaseSensitivity cs = Qt::CaseSensitive;
313#endif
314
315#if QT_VERSION < QT_VERSION_CHECK(5, 15, 0)
316 QStringList projElems = projPath.split( '/', QString::SkipEmptyParts );
317 QStringList srcElems = srcPath.split( '/', QString::SkipEmptyParts );
318#else
319 QStringList projElems = projPath.split( '/', Qt::SkipEmptyParts );
320 QStringList srcElems = srcPath.split( '/', Qt::SkipEmptyParts );
321#endif
322
323 projElems.removeAll( QStringLiteral( "." ) );
324 srcElems.removeAll( QStringLiteral( "." ) );
325
326 // remove common part
327 int n = 0;
328 while ( !srcElems.isEmpty() &&
329 !projElems.isEmpty() &&
330 srcElems[0].compare( projElems[0], cs ) == 0 )
331 {
332 srcElems.removeFirst();
333 projElems.removeFirst();
334 n++;
335 }
336
337 if ( n == 0 )
338 {
339 // no common parts; might not even be a file
340 return src;
341 }
342
343 if ( !projElems.isEmpty() )
344 {
345 // go up to the common directory
346 for ( int i = 0; i < projElems.size(); i++ )
347 {
348 srcElems.insert( 0, QStringLiteral( ".." ) );
349 }
350 }
351 else
352 {
353 // let it start with . nevertheless,
354 // so relative path always start with either ./ or ../
355 srcElems.insert( 0, QStringLiteral( "." ) );
356 }
357
358 // Append url query if any
359 QString returnPath { vsiPrefix + srcElems.join( QLatin1Char( '/' ) ) };
360 if ( ! urlQuery.isEmpty() )
361 {
362 returnPath.append( '?' );
363 returnPath.append( urlQuery );
364 }
365 return returnPath;
366}
static QString pkgDataPath()
Returns the common root path of all application data directories.
static QgsLocalizedDataPathRegistry * localizedDataPathRegistry()
Returns the registry of data repositories These are used as paths for basemaps, logos,...
QString globalPath(const QString &localizedPath) const
Returns the global path if the file has been found in one of the paths, an empty string otherwise.
QString localizedPath(const QString &globalPath) const
Returns the localized path if the file has been found in one of the path, an empty string otherwise.
Resolves relative paths into absolute paths and vice versa.
QString writePath(const QString &filename) const
Prepare a filename to save it to the project file.
static bool removePathPreprocessor(const QString &id)
Removes the custom pre-processor function with matching id.
static QString setPathPreprocessor(const std::function< QString(const QString &filename)> &processor)
Sets a path pre-processor function, which allows for manipulation of paths and data sources prior to ...
QString readPath(const QString &filename) const
Turn filename read from the project file to an absolute path.
static bool removePathWriter(const QString &id)
Removes the custom writer function with matching id.
static QString setPathWriter(const std::function< QString(const QString &filename)> &writer)
Sets a path writer function, which allows for manipulation of paths and data sources prior to writing...
QString qgsVsiPrefix(const QString &path)
Definition: qgis.cpp:192
Q_GLOBAL_STATIC(QReadWriteLock, sDefinitionCacheLock)
std::vector< std::pair< QString, std::function< QString(const QString &) > > > CustomResolvers