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