Branch data TLA Line data Source code
1 : : // Copyright (c) 2014-2022 Thomas Fussell
2 : : // Copyright (c) 2024-2025 xlnt-community
3 : : //
4 : : // Permission is hereby granted, free of charge, to any person obtaining a copy
5 : : // of this software and associated documentation files (the "Software"), to deal
6 : : // in the Software without restriction, including without limitation the rights
7 : : // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8 : : // copies of the Software, and to permit persons to whom the Software is
9 : : // furnished to do so, subject to the following conditions:
10 : : //
11 : : // The above copyright notice and this permission notice shall be included in
12 : : // all copies or substantial portions of the Software.
13 : : //
14 : : // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15 : : // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16 : : // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17 : : // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18 : : // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19 : : // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
20 : : // THE SOFTWARE
21 : : //
22 : : // @license: http://www.opensource.org/licenses/mit-license.php
23 : : // @author: see AUTHORS file
24 : :
25 : : #include <algorithm>
26 : : #include <cassert>
27 : : #include <fstream>
28 : : #include <iterator>
29 : : #include <sstream>
30 : : #include <sys/stat.h>
31 : :
32 : : #ifdef __APPLE__
33 : : #include <mach-o/dyld.h>
34 : : #elif defined(__linux)
35 : : #include <linux/limits.h>
36 : : #include <sys/types.h>
37 : : #include <unistd.h>
38 : : #endif
39 : :
40 : : #include <xlnt/utils/path.hpp>
41 : : #include <detail/external/include_windows.hpp>
42 : : #include <detail/utils/string_helpers.hpp>
43 : :
44 : : namespace {
45 : :
46 : : #ifdef WIN32
47 : :
48 : : char system_separator()
49 : : {
50 : : return '\\';
51 : : }
52 : :
53 : : bool is_drive_letter(char letter)
54 : : {
55 : : return letter >= 'A' && letter <= 'Z';
56 : : }
57 : :
58 : : bool is_root(const std::string &part)
59 : : {
60 : : if (part.size() == 1 && part[0] == '/') return true;
61 : : if (part.size() != 3) return false;
62 : :
63 : : return is_drive_letter(part[0]) && part[1] == ':' && (part[2] == '\\' || part[2] == '/');
64 : : }
65 : :
66 : : bool is_absolute(const std::string &part)
67 : : {
68 : : if (!part.empty() && part[0] == '/') return true;
69 : : if (part.size() < 3) return false;
70 : :
71 : : return is_root(part.substr(0, 3));
72 : : }
73 : :
74 : : #else
75 : :
76 :CBC 39674 : char system_separator()
77 : : {
78 : 39674 : return '/';
79 : : }
80 : :
81 : 2315 : bool is_root(const std::string &part)
82 : : {
83 : 2315 : return part == "/";
84 : : }
85 : :
86 : 4647 : bool is_absolute(const std::string &part)
87 : : {
88 [ + + + + ]: 4647 : return !part.empty() && part[0] == '/';
89 : : }
90 : :
91 : : #endif
92 : :
93 : 13824 : std::vector<std::string> split_path(const std::string &path, char delim)
94 : : {
95 : 13824 : std::vector<std::string> split;
96 : 13824 : std::string::size_type previous_index = 0;
97 : 13824 : auto separator_index = path.find(delim);
98 : :
99 [ + + ]: 30332 : while (separator_index != std::string::npos)
100 : : {
101 [ + ]: 16508 : auto part = path.substr(previous_index, separator_index - previous_index);
102 [ + ]: 16508 : split.push_back(part);
103 : :
104 : 16508 : previous_index = separator_index + 1;
105 : 16508 : separator_index = path.find(delim, previous_index);
106 : 16508 : }
107 : :
108 : : // Don't add trailing slash
109 [ + + ]: 13824 : if (previous_index < path.size())
110 : : {
111 [ + + ]: 12773 : split.push_back(path.substr(previous_index));
112 : : }
113 : :
114 : 13824 : return split;
115 :UBC 0 : }
116 : :
117 : : #ifdef _MSC_VER
118 : : bool file_exists(const std::wstring &path)
119 : : {
120 : : struct _stat info;
121 : : return _wstat(path.c_str(), &info) == 0 && (info.st_mode & S_IFREG);
122 : : }
123 : :
124 : : bool directory_exists(const std::wstring &path)
125 : : {
126 : : struct _stat info;
127 : : return _wstat(path.c_str(), &info) == 0 && (info.st_mode & S_IFDIR);
128 : : }
129 : : #else
130 :CBC 5 : bool file_exists(const std::string &path)
131 : : {
132 : : struct stat info;
133 [ + + + - ]: 5 : return stat(path.c_str(), &info) == 0 && (info.st_mode & S_IFREG);
134 : : }
135 : :
136 : 2 : bool directory_exists(const std::string &path)
137 : : {
138 : : struct stat info;
139 [ - + - - ]: 2 : return stat(path.c_str(), &info) == 0 && (info.st_mode & S_IFDIR);
140 : : }
141 : : #endif
142 : :
143 : : } // namespace
144 : :
145 : : namespace xlnt {
146 : :
147 : 39674 : char path::system_separator()
148 : : {
149 : 39674 : return ::system_separator();
150 : : }
151 : :
152 : 15229 : path::path()
153 : : {
154 : 15229 : }
155 : :
156 : 49956 : path::path(const std::string &path_string)
157 : : {
158 [ + + ]: 49956 : std::remove_copy(path_string.begin(), path_string.end(), std::back_inserter(internal_), '\"');
159 : 49956 : }
160 : :
161 :UBC 0 : path::path(const std::string &path_string, char sep)
162 : 0 : : internal_(path_string)
163 : : {
164 [ # ]: 0 : char curr_sep = guess_separator();
165 [ # # ]: 0 : if (curr_sep != sep)
166 : : {
167 [ # # ]: 0 : for (char &c : internal_) // simple find and replace
168 : : {
169 [ # # ]: 0 : if (c == curr_sep)
170 : : {
171 : 0 : c = sep;
172 : : }
173 : : }
174 : : }
175 : 0 : }
176 : :
177 : : #if XLNT_HAS_FEATURE(U8_STRING_VIEW)
178 :CBC 7 : path::path(std::u8string_view path_string)
179 [ + + ]: 7 : : path(detail::to_string_copy(path_string))
180 : : {
181 : :
182 : 7 : }
183 : :
184 :UBC 0 : path::path(std::u8string_view path_string, char sep)
185 [ # # ]: 0 : : path(detail::to_string_copy(path_string), sep)
186 : : {
187 : :
188 : 0 : }
189 : : #endif
190 : :
191 : :
192 : : // general attributes
193 : :
194 :CBC 2530 : bool path::is_relative() const
195 : : {
196 : 2530 : return !is_absolute();
197 : : }
198 : :
199 : 4647 : bool path::is_absolute() const
200 : : {
201 : 4647 : return ::is_absolute(internal_);
202 : : }
203 : :
204 : 2315 : bool path::is_root() const
205 : : {
206 : 2315 : return ::is_root(internal_);
207 : : }
208 : :
209 : 2315 : path path::parent() const
210 : : {
211 [ + + + + ]: 2315 : if (is_root()) return *this;
212 : :
213 [ + ]: 2174 : auto split_path = split();
214 : :
215 : 2174 : split_path.pop_back();
216 : :
217 [ + + ]: 2174 : if (split_path.empty())
218 : : {
219 [ + + ]: 306 : return path("");
220 : : }
221 : :
222 : 2021 : path result;
223 : :
224 [ + + ]: 4481 : for (const auto &component : split_path)
225 : : {
226 [ + ]: 2460 : result = result.append(component);
227 : : }
228 : :
229 : 2021 : return result;
230 : 2174 : }
231 : :
232 : 1471 : std::string path::filename() const
233 : : {
234 [ + ]: 1471 : auto split_path = split();
235 [ - + - + : 2942 : return split_path.empty() ? "" : split_path.back();
- + - - ]
236 : 1471 : }
237 : :
238 : 160 : std::string path::extension() const
239 : : {
240 [ + ]: 160 : auto base = filename();
241 : 160 : auto last_dot = base.find_last_of('.');
242 : :
243 [ - + - + : 320 : return last_dot == std::string::npos ? "" : base.substr(last_dot + 1);
- + - - ]
244 : 160 : }
245 : :
246 : 8 : std::pair<std::string, std::string> path::split_extension() const
247 : : {
248 [ + ]: 8 : auto base = filename();
249 : 8 : auto last_dot = base.find_last_of('.');
250 : :
251 [ + + ]: 16 : return {base.substr(0, last_dot), base.substr(last_dot + 1)};
252 : 8 : }
253 : :
254 : : // conversion
255 : :
256 : 48644 : const std::string &path::string() const
257 : : {
258 : 48644 : return internal_;
259 : : }
260 : :
261 : : #ifdef _MSC_VER
262 : : std::wstring path::wstring() const
263 : : {
264 : : const std::string &path_str = string();
265 : : const int allocated_size = MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, path_str.c_str(), static_cast<int>(path_str.length()), nullptr, 0);
266 : :
267 : : if (allocated_size > 0)
268 : : {
269 : : std::wstring path_converted(allocated_size, L'\0');
270 : : const int actual_size = MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, path_str.c_str(), static_cast<int>(path_str.length()), &path_converted.at(0), allocated_size);
271 : : assert(allocated_size == actual_size); // unless a serious error happened, this MUST always be true!
272 : : return path_converted;
273 : : }
274 : : else
275 : : {
276 : : return {};
277 : : }
278 : : }
279 : : #endif
280 : :
281 : 13824 : std::vector<std::string> path::split() const
282 : : {
283 : 13824 : return split_path(internal_, guess_separator());
284 : : }
285 : :
286 : 1222 : path path::resolve(const path &base_path) const
287 : : {
288 [ + + ]: 1222 : if (is_absolute())
289 : : {
290 [ + ]: 313 : return *this;
291 : : }
292 : :
293 [ + ]: 909 : path copy(base_path.internal_);
294 : :
295 [ + + + ]: 2810 : for (const auto &part : split())
296 : : {
297 [ + + + ]: 1901 : if (part == "..")
298 : : {
299 [ + ]: 2 : copy = copy.parent();
300 : 2 : continue;
301 : : }
302 : :
303 [ + ]: 1899 : copy = copy.append(part);
304 : 909 : }
305 : :
306 : 909 : return copy;
307 : 909 : }
308 : :
309 : : // filesystem attributes
310 : :
311 : 5 : bool path::exists() const
312 : : {
313 [ + + - + ]: 5 : return is_file() || is_directory();
314 : : }
315 : :
316 : 2 : bool path::is_directory() const
317 : : {
318 : : #ifdef _MSC_VER
319 : : return directory_exists(wstring());
320 : : #else
321 : 2 : return directory_exists(string());
322 : : #endif
323 : : }
324 : :
325 : 5 : bool path::is_file() const
326 : : {
327 : : #ifdef _MSC_VER
328 : : return file_exists(wstring());
329 : : #else
330 : 5 : return file_exists(string());
331 : : #endif
332 : : }
333 : :
334 : : // filesystem
335 : :
336 :UBC 0 : std::string path::read_contents() const
337 : : {
338 : : #ifdef _MSC_VER
339 : : std::ifstream f(wstring());
340 : : #else
341 [ # ]: 0 : std::ifstream f(string());
342 : : #endif
343 [ # ]: 0 : std::ostringstream ss;
344 [ # ]: 0 : ss << f.rdbuf();
345 : :
346 [ # ]: 0 : return ss.str();
347 : 0 : }
348 : :
349 : : // append
350 : :
351 :CBC 22084 : path path::append(const std::string &to_append) const
352 : : {
353 : 22084 : path copy(internal_);
354 : :
355 [ + + + + : 22084 : if (!internal_.empty() && internal_.back() != guess_separator())
+ + + ]
356 : : {
357 [ + + ]: 12423 : copy.internal_.push_back(guess_separator());
358 : : }
359 : :
360 [ + ]: 22084 : copy.internal_.append(to_append);
361 : :
362 : 22084 : return copy;
363 :UBC 0 : }
364 : :
365 : : #if XLNT_HAS_FEATURE(U8_STRING_VIEW)
366 :CBC 1 : path path::append(std::u8string_view to_append) const
367 : : {
368 [ + + ]: 1 : return append(detail::to_string_copy(to_append));
369 : : }
370 : : #endif
371 : :
372 : 3886 : path path::append(const path &to_append) const
373 : : {
374 : 3886 : path copy(internal_);
375 : :
376 [ + + + ]: 10794 : for (const auto &component : to_append.split())
377 : : {
378 [ + ]: 6908 : copy = copy.append(component);
379 : 3886 : }
380 : :
381 : 3886 : return copy;
382 :UBC 0 : }
383 : :
384 :CBC 39674 : char path::guess_separator() const
385 : : {
386 [ - + - - : 39674 : if (system_separator() == '/' || internal_.empty() || internal_.front() == '/') return '/';
- - + - ]
387 [ # # ]:UBC 0 : if (is_absolute()) return internal_.at(2);
388 [ # # ]: 0 : return internal_.find('\\') != std::string::npos ? '\\' : '/';
389 : : }
390 : :
391 :CBC 2530 : path path::relative_to(const path &base_path) const
392 : : {
393 [ + + + + ]: 2530 : if (is_relative()) return *this;
394 : :
395 [ + ]: 1433 : auto base_split = base_path.split();
396 [ + ]: 1433 : auto this_split = split();
397 : 1433 : auto index = std::size_t(0);
398 : :
399 [ + + + - : 3434 : while (index < base_split.size() && index < this_split.size() && base_split[index] == this_split[index])
+ + + + ]
400 : : {
401 : 2001 : index++;
402 : : }
403 : :
404 : 1433 : auto result = path();
405 : :
406 [ + + ]: 3993 : for (auto i = index; i < this_split.size(); i++)
407 : : {
408 [ + ]: 2560 : result = result.append(this_split[i]);
409 : : }
410 : :
411 : 1433 : return result;
412 : 1433 : }
413 : :
414 : 22787 : bool path::operator==(const path &other) const
415 : : {
416 : 22787 : return internal_ == other.internal_;
417 : : }
418 : :
419 :UBC 0 : bool path::operator!=(const path &other) const
420 : : {
421 : 0 : return !operator==(other);
422 : : }
423 : :
424 : : } // namespace xlnt
|