• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

xlnt-community / xlnt / 1a253839-e95c-4bd5-8d2b-67f853d51e30

04 Feb 2025 09:48PM UTC coverage: 81.87% (-0.1%) from 81.978%
1a253839-e95c-4bd5-8d2b-67f853d51e30

Pull #55

circleci

doomlaur
Fixed Unicode file paths on Windows for checking whether the file exists and for reading the contents of a file
Pull Request #55: Add support for C++20 and C++23, and experimental support for C++26

108 of 150 new or added lines in 8 files covered. (72.0%)

4 existing lines in 2 files now uncovered.

11488 of 14032 relevant lines covered (81.87%)

1191666.72 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

82.86
./source/utils/path.cpp
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
char system_separator()
286,785✔
77
{
78
    return '/';
286,785✔
79
}
80

81
bool is_root(const std::string &part)
2,994✔
82
{
83
    return part == "/";
2,994✔
84
}
85

86
bool is_absolute(const std::string &part)
5,428✔
87
{
88
    return !part.empty() && part[0] == '/';
5,428✔
89
}
90

91
#endif
92

93
std::vector<std::string> split_path(const std::string &path, char delim)
96,269✔
94
{
95
    std::vector<std::string> split;
96,269✔
96
    std::string::size_type previous_index = 0;
96,269✔
97
    auto separator_index = path.find(delim);
96,269✔
98

99
    while (separator_index != std::string::npos)
195,264✔
100
    {
101
        auto part = path.substr(previous_index, separator_index - previous_index);
98,995✔
102
        split.push_back(part);
98,995✔
103

104
        previous_index = separator_index + 1;
98,995✔
105
        separator_index = path.find(delim, previous_index);
98,995✔
106
    }
98,995✔
107

108
    // Don't add trailing slash
109
    if (previous_index < path.size())
96,269✔
110
    {
111
        split.push_back(path.substr(previous_index));
95,125✔
112
    }
113

114
    return split;
96,269✔
115
}
×
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
bool file_exists(const std::string &path)
5✔
131
{
132
    struct stat info;
133
    return stat(path.c_str(), &info) == 0 && (info.st_mode & S_IFREG);
5✔
134
}
135

136
bool directory_exists(const std::string &path)
3✔
137
{
138
    struct stat info;
139
    return stat(path.c_str(), &info) == 0 && (info.st_mode & S_IFDIR);
3✔
140
}
141
#endif
142

143
} // namespace
144

145
namespace xlnt {
146

147
char path::system_separator()
286,785✔
148
{
149
    return ::system_separator();
286,785✔
150
}
151

152
path::path()
97,334✔
153
{
154
}
97,334✔
155

156
path::path(const std::string &path_string)
297,621✔
157
{
158
    std::remove_copy(path_string.begin(), path_string.end(), std::back_inserter(internal_), '\"');
297,621✔
159
}
297,621✔
160

161
path::path(const std::string &path_string, char sep)
×
162
    : internal_(path_string)
×
163
{
164
    char curr_sep = guess_separator();
×
165
    if (curr_sep != sep)
×
166
    {
167
        for (char &c : internal_) // simple find and replace
×
168
        {
169
            if (c == curr_sep)
×
170
            {
171
                c = sep;
×
172
            }
173
        }
174
    }
175
}
×
176

177
#if XLNT_HAS_FEATURE(U8_STRING_VIEW)
178
path::path(std::u8string_view path_string)
5✔
179
    : path(detail::to_string_copy(path_string))
5✔
180
{
181

182
}
5✔
183

NEW
184
path::path(std::u8string_view path_string, char sep)
×
NEW
185
    : path(detail::to_string_copy(path_string), sep)
×
186
{
187

NEW
188
}
×
189
#endif
190

191

192
// general attributes
193

194
bool path::is_relative() const
2,750✔
195
{
196
    return !is_absolute();
2,750✔
197
}
198

199
bool path::is_absolute() const
5,428✔
200
{
201
    return ::is_absolute(internal_);
5,428✔
202
}
203

204
bool path::is_root() const
2,994✔
205
{
206
    return ::is_root(internal_);
2,994✔
207
}
208

209
path path::parent() const
2,994✔
210
{
211
    if (is_root()) return *this;
2,994✔
212

213
    auto split_path = split();
2,794✔
214

215
    split_path.pop_back();
2,794✔
216

217
    if (split_path.empty())
2,794✔
218
    {
219
        return path("");
330✔
220
    }
221

222
    path result;
2,629✔
223

224
    for (const auto &component : split_path)
5,753✔
225
    {
226
        result = result.append(component);
3,124✔
227
    }
228

229
    return result;
2,629✔
230
}
2,794✔
231

232
std::string path::filename() const
1,726✔
233
{
234
    auto split_path = split();
1,726✔
235
    return split_path.empty() ? "" : split_path.back();
3,452✔
236
}
1,726✔
237

238
std::string path::extension() const
160✔
239
{
240
    auto base = filename();
160✔
241
    auto last_dot = base.find_last_of('.');
160✔
242

243
    return last_dot == std::string::npos ? "" : base.substr(last_dot + 1);
320✔
244
}
160✔
245

246
std::pair<std::string, std::string> path::split_extension() const
8✔
247
{
248
    auto base = filename();
8✔
249
    auto last_dot = base.find_last_of('.');
8✔
250

251
    return {base.substr(0, last_dot), base.substr(last_dot + 1)};
16✔
252
}
8✔
253

254
// conversion
255

256
const std::string &path::string() const
337,009✔
257
{
258
    return internal_;
337,009✔
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
std::vector<std::string> path::split() const
96,269✔
282
{
283
    return split_path(internal_, guess_separator());
96,269✔
284
}
285

286
path path::resolve(const path &base_path) const
1,581✔
287
{
288
    if (is_absolute())
1,581✔
289
    {
290
        return *this;
633✔
291
    }
292

293
    path copy(base_path.internal_);
948✔
294

295
    for (const auto &part : split())
2,927✔
296
    {
297
        if (part == "..")
1,979✔
298
        {
299
            copy = copy.parent();
2✔
300
            continue;
2✔
301
        }
302

303
        copy = copy.append(part);
1,977✔
304
    }
948✔
305

306
    return copy;
948✔
307
}
948✔
308

309
// filesystem attributes
310

311
bool path::exists() const
5✔
312
{
313
    return is_file() || is_directory();
5✔
314
}
315

316
bool path::is_directory() const
3✔
317
{
318
#ifdef _MSC_VER
319
    return directory_exists(wstring());
320
#else
321
    return directory_exists(string());
3✔
322
#endif
323
}
324

325
bool path::is_file() const
5✔
326
{
327
#ifdef _MSC_VER
328
    return file_exists(wstring());
329
#else
330
    return file_exists(string());
5✔
331
#endif
332
}
333

334
// filesystem
335

336
std::string path::read_contents() const
×
337
{
338
#ifdef _MSC_VER
339
    std::ifstream f(wstring());
340
#else
341
    std::ifstream f(string());
×
342
#endif
343
    std::ostringstream ss;
×
344
    ss << f.rdbuf();
×
345

346
    return ss.str();
×
347
}
×
348

349
// append
350

351
path path::append(const std::string &to_append) const
186,118✔
352
{
353
    path copy(internal_);
186,118✔
354

355
    if (!internal_.empty() && internal_.back() != guess_separator())
186,118✔
356
    {
357
        copy.internal_.push_back(guess_separator());
94,729✔
358
    }
359

360
    copy.internal_.append(to_append);
186,118✔
361

362
    return copy;
186,118✔
363
}
×
364

365
#if XLNT_HAS_FEATURE(U8_STRING_VIEW)
366
path path::append(std::u8string_view to_append) const
1✔
367
{
368
    return append(detail::to_string_copy(to_append));
1✔
369
}
370
#endif
371

372
path path::append(const path &to_append) const
44,807✔
373
{
374
    path copy(internal_);
44,807✔
375

376
    for (const auto &component : to_append.split())
133,303✔
377
    {
378
        copy = copy.append(component);
88,496✔
379
    }
44,807✔
380

381
    return copy;
44,807✔
382
}
×
383

384
char path::guess_separator() const
286,785✔
385
{
386
    if (system_separator() == '/' || internal_.empty() || internal_.front() == '/') return '/';
286,785✔
387
    if (is_absolute()) return internal_.at(2);
×
388
    return internal_.find('\\') != std::string::npos ? '\\' : '/';
×
389
}
390

391
path path::relative_to(const path &base_path) const
2,750✔
392
{
393
    if (is_relative()) return *this;
2,750✔
394

395
    auto base_split = base_path.split();
1,503✔
396
    auto this_split = split();
1,503✔
397
    auto index = std::size_t(0);
1,503✔
398

399
    while (index < base_split.size() && index < this_split.size() && base_split[index] == this_split[index])
3,598✔
400
    {
401
        index++;
2,095✔
402
    }
403

404
    auto result = path();
1,503✔
405

406
    for (auto i = index; i < this_split.size(); i++)
4,189✔
407
    {
408
        result = result.append(this_split[i]);
2,686✔
409
    }
410

411
    return result;
1,503✔
412
}
1,503✔
413

414
bool path::operator==(const path &other) const
146,228✔
415
{
416
    return internal_ == other.internal_;
146,228✔
417
}
418

419
bool path::operator!=(const path &other) const
×
420
{
421
    return !operator==(other);
×
422
}
423

424
} // namespace xlnt
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc