QGIS API Documentation  3.20.0-Odense (decaadbb31)
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 }
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.
QString qgsVsiPrefix(const QString &path)
Definition: qgis.cpp:200
Q_GLOBAL_STATIC(QReadWriteLock, sDefinitionCacheLock)
std::vector< std::pair< QString, std::function< QString(const QString &) > > > CustomResolvers