QGIS API Documentation  3.8.0-Zanzibar (11aff65)
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 
20 #include <QFileInfo>
21 #include <QUrl>
22 
23 
24 QgsPathResolver::QgsPathResolver( const QString &baseFileName )
25  : mBaseFileName( baseFileName )
26 {
27 }
28 
29 
30 QString QgsPathResolver::readPath( const QString &filename ) const
31 {
32  if ( filename.isEmpty() )
33  return QString();
34 
35  QString src = filename;
36 
37  if ( mBaseFileName.isNull() )
38  {
39  return src;
40  }
41 
42  // if this is a VSIFILE, remove the VSI prefix and append to final result
43  QString vsiPrefix = qgsVsiPrefix( src );
44  if ( ! vsiPrefix.isEmpty() )
45  {
46  // unfortunately qgsVsiPrefix returns prefix also for files like "/x/y/z.gz"
47  // so we need to check if we really have the prefix
48  if ( src.startsWith( QLatin1String( "/vsi" ), Qt::CaseInsensitive ) )
49  src.remove( 0, vsiPrefix.size() );
50  else
51  vsiPrefix.clear();
52  }
53 
54  // relative path should always start with ./ or ../
55  if ( !src.startsWith( QLatin1String( "./" ) ) && !src.startsWith( QLatin1String( "../" ) ) )
56  {
57 #if defined(Q_OS_WIN)
58  if ( src.startsWith( "\\\\" ) ||
59  src.startsWith( "//" ) ||
60  ( src[0].isLetter() && src[1] == ':' ) )
61  {
62  // UNC or absolute path
63  return vsiPrefix + src;
64  }
65 #else
66  if ( src[0] == '/' )
67  {
68  // absolute path
69  return vsiPrefix + src;
70  }
71 #endif
72 
73  // so this one isn't absolute, but also doesn't start // with ./ or ../.
74  // That means that it was saved with an earlier version of "relative path support",
75  // where the source file had to exist and only the project directory was stripped
76  // from the filename.
77 
78  QFileInfo pfi( mBaseFileName );
79  QString home = pfi.absolutePath();
80  if ( home.isEmpty() )
81  return vsiPrefix + src;
82 
83  QFileInfo fi( home + '/' + src );
84 
85  if ( !fi.exists() )
86  {
87  return vsiPrefix + src;
88  }
89  else
90  {
91  return vsiPrefix + fi.canonicalFilePath();
92  }
93  }
94 
95  QString srcPath = src;
96  QString projPath = mBaseFileName;
97 
98  if ( projPath.isEmpty() )
99  {
100  return vsiPrefix + src;
101  }
102 
103 #if defined(Q_OS_WIN)
104  srcPath.replace( '\\', '/' );
105  projPath.replace( '\\', '/' );
106 
107  bool uncPath = projPath.startsWith( "//" );
108 #endif
109 
110  QStringList srcElems = srcPath.split( '/', QString::SkipEmptyParts );
111  QStringList projElems = projPath.split( '/', QString::SkipEmptyParts );
112 
113 #if defined(Q_OS_WIN)
114  if ( uncPath )
115  {
116  projElems.insert( 0, "" );
117  projElems.insert( 0, "" );
118  }
119 #endif
120 
121  // remove project file element
122  projElems.removeLast();
123 
124  // append source path elements
125  projElems << srcElems;
126  projElems.removeAll( QStringLiteral( "." ) );
127 
128  // resolve ..
129  int pos;
130  while ( ( pos = projElems.indexOf( QStringLiteral( ".." ) ) ) > 0 )
131  {
132  // remove preceding element and ..
133  projElems.removeAt( pos - 1 );
134  projElems.removeAt( pos - 1 );
135  }
136 
137 #if !defined(Q_OS_WIN)
138  // make path absolute
139  projElems.prepend( QString() );
140 #endif
141 
142  return vsiPrefix + projElems.join( QStringLiteral( "/" ) );
143 }
144 
145 
146 QString QgsPathResolver::writePath( const QString &src ) const
147 {
148  if ( mBaseFileName.isEmpty() || src.isEmpty() )
149  {
150  return src;
151  }
152 
153  // Get projPath even if project has not been created yet
154  QFileInfo pfi( QFileInfo( mBaseFileName ).path() );
155  QString projPath = pfi.canonicalFilePath();
156 
157  // If project directory doesn't exit, fallback to absoluteFilePath : symbolic
158  // links won't be handled correctly, but that's OK as the path is "virtual".
159  if ( projPath.isEmpty() )
160  projPath = pfi.absoluteFilePath();
161 
162  if ( projPath.isEmpty() )
163  {
164  return src;
165  }
166 
167  // Check if it is a publicSource uri and clean it
168  QUrl url { src };
169  QString srcPath { src };
170  QString urlQuery;
171 
172  if ( url.isLocalFile( ) )
173  {
174  srcPath = url.path();
175  urlQuery = url.query();
176  }
177 
178  QFileInfo srcFileInfo( srcPath );
179  if ( srcFileInfo.exists() )
180  srcPath = srcFileInfo.canonicalFilePath();
181 
182  // if this is a VSIFILE, remove the VSI prefix and append to final result
183  QString vsiPrefix = qgsVsiPrefix( src );
184  if ( ! vsiPrefix.isEmpty() )
185  {
186  srcPath.remove( 0, vsiPrefix.size() );
187  }
188 
189 #if defined( Q_OS_WIN )
190  const Qt::CaseSensitivity cs = Qt::CaseInsensitive;
191 
192  srcPath.replace( '\\', '/' );
193 
194  if ( srcPath.startsWith( "//" ) )
195  {
196  // keep UNC prefix
197  srcPath = "\\\\" + srcPath.mid( 2 );
198  }
199 
200  projPath.replace( '\\', '/' );
201  if ( projPath.startsWith( "//" ) )
202  {
203  // keep UNC prefix
204  projPath = "\\\\" + projPath.mid( 2 );
205  }
206 #else
207  const Qt::CaseSensitivity cs = Qt::CaseSensitive;
208 #endif
209 
210  QStringList projElems = projPath.split( '/', QString::SkipEmptyParts );
211  QStringList srcElems = srcPath.split( '/', QString::SkipEmptyParts );
212 
213  projElems.removeAll( QStringLiteral( "." ) );
214  srcElems.removeAll( QStringLiteral( "." ) );
215 
216  // remove common part
217  int n = 0;
218  while ( !srcElems.isEmpty() &&
219  !projElems.isEmpty() &&
220  srcElems[0].compare( projElems[0], cs ) == 0 )
221  {
222  srcElems.removeFirst();
223  projElems.removeFirst();
224  n++;
225  }
226 
227  if ( n == 0 )
228  {
229  // no common parts; might not even be a file
230  return src;
231  }
232 
233  if ( !projElems.isEmpty() )
234  {
235  // go up to the common directory
236  for ( int i = 0; i < projElems.size(); i++ )
237  {
238  srcElems.insert( 0, QStringLiteral( ".." ) );
239  }
240  }
241  else
242  {
243  // let it start with . nevertheless,
244  // so relative path always start with either ./ or ../
245  srcElems.insert( 0, QStringLiteral( "." ) );
246  }
247 
248  // Append url query if any
249  QString returnPath { vsiPrefix + srcElems.join( QStringLiteral( "/" ) ) };
250  if ( ! urlQuery.isEmpty() )
251  {
252  returnPath.append( '?' );
253  returnPath.append( urlQuery );
254  }
255  return returnPath;
256 }
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:226
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...