QGIS API Documentation  3.26.3-Buenos Aires (65e4edfdad)
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 
27 typedef std::vector< std::pair< QString, std::function< QString( const QString & ) > > > CustomResolvers;
28 Q_GLOBAL_STATIC( CustomResolvers, sCustomResolvers )
29 Q_GLOBAL_STATIC( CustomResolvers, sCustomWriters )
30 
31 QgsPathResolver::QgsPathResolver( const QString &baseFileName, const QString &attachmentDir )
32  : mBaseFileName( baseFileName ), mAttachmentDir( attachmentDir )
33 {
34 }
35 
36 
37 QString 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 
191 QString 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 
208 QString 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 
215 bool 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 
225 QString 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 }
QgsPathResolver::removePathPreprocessor
static bool removePathPreprocessor(const QString &id)
Removes the custom pre-processor function with matching id.
Definition: qgspathresolver.cpp:198
qgslocalizeddatapathregistry.h
qgspathresolver.h
qgis.h
QgsLocalizedDataPathRegistry::localizedPath
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.
Definition: qgslocalizeddatapathregistry.cpp:40
Q_GLOBAL_STATIC
Q_GLOBAL_STATIC(QReadWriteLock, sDefinitionCacheLock)
CustomResolvers
std::vector< std::pair< QString, std::function< QString(const QString &) > > > CustomResolvers
Definition: qgspathresolver.cpp:27
qgsapplication.h
QgsPathResolver::writePath
QString writePath(const QString &filename) const
Prepare a filename to save it to the project file.
Definition: qgspathresolver.cpp:225
QgsPathResolver::setPathWriter
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...
Definition: qgspathresolver.cpp:208
QgsPathResolver::removePathWriter
static bool removePathWriter(const QString &id)
Removes the custom writer function with matching id.
Definition: qgspathresolver.cpp:215
QgsApplication::pkgDataPath
static QString pkgDataPath()
Returns the common root path of all application data directories.
Definition: qgsapplication.cpp:645
qgsVsiPrefix
QString qgsVsiPrefix(const QString &path)
Definition: qgis.cpp:192
QgsLocalizedDataPathRegistry::globalPath
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.
Definition: qgslocalizeddatapathregistry.cpp:29
QgsPathResolver::readPath
QString readPath(const QString &filename) const
Turn filename read from the project file to an absolute path.
Definition: qgspathresolver.cpp:37
QgsApplication::localizedDataPathRegistry
static QgsLocalizedDataPathRegistry * localizedDataPathRegistry()
Returns the registry of data repositories These are used as paths for basemaps, logos,...
Definition: qgsapplication.cpp:2510
QgsPathResolver::setPathPreprocessor
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 ...
Definition: qgspathresolver.cpp:191
QgsPathResolver
Resolves relative paths into absolute paths and vice versa. Used for writing.
Definition: qgspathresolver.h:31