QGIS API Documentation  3.14.0-Pi (9f7028fd23)
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  QStringList srcElems = srcPath.split( '/', QString::SkipEmptyParts );
136  QStringList projElems = projPath.split( '/', QString::SkipEmptyParts );
137 
138 #if defined(Q_OS_WIN)
139  if ( uncPath )
140  {
141  projElems.insert( 0, "" );
142  projElems.insert( 0, "" );
143  }
144 #endif
145 
146  // remove project file element
147  projElems.removeLast();
148 
149  // append source path elements
150  projElems << srcElems;
151  projElems.removeAll( QStringLiteral( "." ) );
152 
153  // resolve ..
154  int pos;
155  while ( ( pos = projElems.indexOf( QStringLiteral( ".." ) ) ) > 0 )
156  {
157  // remove preceding element and ..
158  projElems.removeAt( pos - 1 );
159  projElems.removeAt( pos - 1 );
160  }
161 
162 #if !defined(Q_OS_WIN)
163  // make path absolute
164  projElems.prepend( QString() );
165 #endif
166 
167  return vsiPrefix + projElems.join( QStringLiteral( "/" ) );
168 }
169 
170 QString QgsPathResolver::setPathPreprocessor( const std::function<QString( const QString & )> &processor )
171 {
172  QString id = QUuid::createUuid().toString();
173  sCustomResolvers()->emplace_back( std::make_pair( id, processor ) );
174  return id;
175 }
176 
178 {
179  const size_t prevCount = sCustomResolvers()->size();
180  sCustomResolvers()->erase( std::remove_if( sCustomResolvers()->begin(), sCustomResolvers()->end(), [id]( std::pair< QString, std::function< QString( const QString & ) > > &a )
181  {
182  return a.first == id;
183  } ), sCustomResolvers()->end() );
184  return prevCount != sCustomResolvers()->size();
185 }
186 
187 QString QgsPathResolver::writePath( const QString &src ) const
188 {
189  if ( src.isEmpty() )
190  {
191  return src;
192  }
193 
194  QString localizedPath = QgsApplication::localizedDataPathRegistry()->localizedPath( src );
195  if ( !localizedPath.isEmpty() )
196  return QStringLiteral( "localized:" ) + localizedPath;
197 
198  if ( src.startsWith( QgsApplication::pkgDataPath() + QStringLiteral( "/resources" ) ) )
199  {
200  // replace inbuilt data folder path with "inbuilt:" prefix
201  return QStringLiteral( "inbuilt:" ) + src.mid( QgsApplication::pkgDataPath().length() + 10 );
202  }
203 
204  if ( mBaseFileName.isEmpty() )
205  {
206  return src;
207  }
208 
209  // Get projPath even if project has not been created yet
210  QFileInfo pfi( QFileInfo( mBaseFileName ).path() );
211  QString projPath = pfi.canonicalFilePath();
212 
213  // If project directory doesn't exit, fallback to absoluteFilePath : symbolic
214  // links won't be handled correctly, but that's OK as the path is "virtual".
215  if ( projPath.isEmpty() )
216  projPath = pfi.absoluteFilePath();
217 
218  if ( projPath.isEmpty() )
219  {
220  return src;
221  }
222 
223  // Check if it is a publicSource uri and clean it
224  QUrl url { src };
225  QString srcPath { src };
226  QString urlQuery;
227 
228  if ( url.isLocalFile( ) )
229  {
230  srcPath = url.path();
231  urlQuery = url.query();
232  }
233 
234  QFileInfo srcFileInfo( srcPath );
235  if ( srcFileInfo.exists() )
236  srcPath = srcFileInfo.canonicalFilePath();
237 
238  // if this is a VSIFILE, remove the VSI prefix and append to final result
239  QString vsiPrefix = qgsVsiPrefix( src );
240  if ( ! vsiPrefix.isEmpty() )
241  {
242  srcPath.remove( 0, vsiPrefix.size() );
243  }
244 
245 #if defined( Q_OS_WIN )
246  const Qt::CaseSensitivity cs = Qt::CaseInsensitive;
247 
248  srcPath.replace( '\\', '/' );
249 
250  if ( srcPath.startsWith( "//" ) )
251  {
252  // keep UNC prefix
253  srcPath = "\\\\" + srcPath.mid( 2 );
254  }
255 
256  projPath.replace( '\\', '/' );
257  if ( projPath.startsWith( "//" ) )
258  {
259  // keep UNC prefix
260  projPath = "\\\\" + projPath.mid( 2 );
261  }
262 #else
263  const Qt::CaseSensitivity cs = Qt::CaseSensitive;
264 #endif
265 
266  QStringList projElems = projPath.split( '/', QString::SkipEmptyParts );
267  QStringList srcElems = srcPath.split( '/', QString::SkipEmptyParts );
268 
269  projElems.removeAll( QStringLiteral( "." ) );
270  srcElems.removeAll( QStringLiteral( "." ) );
271 
272  // remove common part
273  int n = 0;
274  while ( !srcElems.isEmpty() &&
275  !projElems.isEmpty() &&
276  srcElems[0].compare( projElems[0], cs ) == 0 )
277  {
278  srcElems.removeFirst();
279  projElems.removeFirst();
280  n++;
281  }
282 
283  if ( n == 0 )
284  {
285  // no common parts; might not even be a file
286  return src;
287  }
288 
289  if ( !projElems.isEmpty() )
290  {
291  // go up to the common directory
292  for ( int i = 0; i < projElems.size(); i++ )
293  {
294  srcElems.insert( 0, QStringLiteral( ".." ) );
295  }
296  }
297  else
298  {
299  // let it start with . nevertheless,
300  // so relative path always start with either ./ or ../
301  srcElems.insert( 0, QStringLiteral( "." ) );
302  }
303 
304  // Append url query if any
305  QString returnPath { vsiPrefix + srcElems.join( QStringLiteral( "/" ) ) };
306  if ( ! urlQuery.isEmpty() )
307  {
308  returnPath.append( '?' );
309  returnPath.append( urlQuery );
310  }
311  return returnPath;
312 }
QgsPathResolver::removePathPreprocessor
static bool removePathPreprocessor(const QString &id)
Removes the custom pre-processor function with matching id.
Definition: qgspathresolver.cpp:177
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:187
QgsApplication::pkgDataPath
static QString pkgDataPath()
Returns the common root path of all application data directories.
Definition: qgsapplication.cpp:557
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:2239
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:170
QgsPathResolver
Definition: qgspathresolver.h:31