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