QGIS API Documentation  3.21.0-Master (5b68dc587e)
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 }
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:200
Q_GLOBAL_STATIC(QReadWriteLock, sDefinitionCacheLock)
std::vector< std::pair< QString, std::function< QString(const QString &) > > > CustomResolvers