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

xlnt-community / xlnt / 080e57d6-34b8-458f-9999-80b099f835d5

01 Feb 2025 11:08PM UTC coverage: 83.45% (-0.03%) from 83.482%
080e57d6-34b8-458f-9999-80b099f835d5

Pull #55

circleci

doomlaur
Reduced code duplication, added further feature test macros, and added other test improvements based on feedback.
Pull Request #55: Add support for C++20 and C++23, and experimental support for C++26

44 of 59 new or added lines in 4 files covered. (74.58%)

14 existing lines in 1 file now uncovered.

11179 of 13396 relevant lines covered (83.45%)

1269595.74 hits per line

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

85.71
./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,474✔
77
{
78
    return '/';
286,474✔
79
}
80

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

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

91
#endif
92

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

99
    while (separator_index != std::string::npos)
293,884✔
100
    {
101
        auto part = path.substr(previous_index, separator_index - previous_index);
197,726✔
102
        split.push_back(part);
98,863✔
103

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

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

114
    return split;
96,158✔
115
}
116

117
bool file_exists(const std::string &path)
4✔
118
{
119
    struct stat info;
120
    return stat(path.c_str(), &info) == 0 && (info.st_mode & S_IFREG);
4✔
121
}
122

123
bool directory_exists(const std::string path)
3✔
124
{
125
    struct stat info;
126
    return stat(path.c_str(), &info) == 0 && (info.st_mode & S_IFDIR);
3✔
127
}
128

129
} // namespace
130

131
namespace xlnt {
132

133
char path::system_separator()
286,474✔
134
{
135
    return ::system_separator();
286,474✔
136
}
137

138
path::path()
97,215✔
139
{
140
}
97,215✔
141

142
path::path(const std::string &path_string)
297,186✔
143
{
144
    std::remove_copy(path_string.begin(), path_string.end(), std::back_inserter(internal_), '\"');
297,186✔
145
}
297,186✔
146

UNCOV
147
path::path(const std::string &path_string, char sep)
×
UNCOV
148
    : internal_(path_string)
×
149
{
UNCOV
150
    char curr_sep = guess_separator();
×
UNCOV
151
    if (curr_sep != sep)
×
152
    {
UNCOV
153
        for (char &c : internal_) // simple find and replace
×
154
        {
UNCOV
155
            if (c == curr_sep)
×
156
            {
UNCOV
157
                c = sep;
×
158
            }
159
        }
160
    }
161
}
×
162

163
#if XLNT_HAS_FEATURE(U8_STRING_VIEW)
164
path::path(std::u8string_view path_string)
165
    : path(detail::to_string_copy(path_string))
166
{
167

168
}
169

170
path::path(std::u8string_view path_string, char sep)
171
    : path(detail::to_string_copy(path_string), sep)
172
{
173

174
}
175
#endif
176

177

178
// general attributes
179

180
bool path::is_relative() const
2,725✔
181
{
182
    return !is_absolute();
2,725✔
183
}
184

185
bool path::is_absolute() const
5,382✔
186
{
187
    return ::is_absolute(internal_);
5,382✔
188
}
189

190
bool path::is_root() const
2,973✔
191
{
192
    return ::is_root(internal_);
2,973✔
193
}
194

195
path path::parent() const
2,973✔
196
{
197
    if (is_root()) return *this;
2,973✔
198

199
    auto split_path = split();
5,550✔
200

201
    split_path.pop_back();
2,775✔
202

203
    if (split_path.empty())
2,775✔
204
    {
205
        return path("");
164✔
206
    }
207

208
    path result;
5,222✔
209

210
    for (const auto &component : split_path)
5,715✔
211
    {
212
        result = result.append(component);
3,104✔
213
    }
214

215
    return result;
2,611✔
216
}
217

218
std::string path::filename() const
1,714✔
219
{
220
    auto split_path = split();
3,428✔
221
    return split_path.empty() ? "" : split_path.back();
3,428✔
222
}
223

224
std::string path::extension() const
160✔
225
{
226
    auto base = filename();
320✔
227
    auto last_dot = base.find_last_of('.');
160✔
228

229
    return last_dot == std::string::npos ? "" : base.substr(last_dot + 1);
320✔
230
}
231

232
std::pair<std::string, std::string> path::split_extension() const
8✔
233
{
234
    auto base = filename();
16✔
235
    auto last_dot = base.find_last_of('.');
8✔
236

237
    return {base.substr(0, last_dot), base.substr(last_dot + 1)};
16✔
238
}
239

240
// conversion
241

242
const std::string &path::string() const
337,186✔
243
{
244
    return internal_;
337,186✔
245
}
246

247
#ifdef _MSC_VER
248
std::wstring path::wstring() const
249
{
250
    const std::string &path_str = string();
251
    const int allocated_size = MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, path_str.c_str(), static_cast<int>(path_str.length()), nullptr, 0);
252

253
    if (allocated_size > 0)
254
    {
255
        std::wstring path_converted(allocated_size, L'\0');
256
        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);
257
        assert(allocated_size == actual_size); // unless a serious error happened, this MUST always be true!
258
        return path_converted;
259
    }
260
    else
261
    {
262
        return {};
263
    }
264
}
265
#endif
266

267
std::vector<std::string> path::split() const
96,158✔
268
{
269
    return split_path(internal_, guess_separator());
96,158✔
270
}
271

272
path path::resolve(const path &base_path) const
1,568✔
273
{
274
    if (is_absolute())
1,568✔
275
    {
276
        return *this;
627✔
277
    }
278

279
    path copy(base_path.internal_);
1,882✔
280

281
    for (const auto &part : split())
2,906✔
282
    {
283
        if (part == "..")
1,967✔
284
        {
285
            copy = copy.parent();
2✔
286
            continue;
2✔
287
        }
288

289
        copy = copy.append(part);
1,963✔
290
    }
291

292
    return copy;
941✔
293
}
294

295
// filesystem attributes
296

297
bool path::exists() const
4✔
298
{
299
    return is_file() || is_directory();
4✔
300
}
301

302
bool path::is_directory() const
3✔
303
{
304
    return directory_exists(string());
3✔
305
}
306

307
bool path::is_file() const
4✔
308
{
309
    return file_exists(string());
4✔
310
}
311

312
// filesystem
313

UNCOV
314
std::string path::read_contents() const
×
315
{
UNCOV
316
    std::ifstream f(string());
×
UNCOV
317
    std::ostringstream ss;
×
318
    ss << f.rdbuf();
×
319

320
    return ss.str();
×
321
}
322

323
// append
324

325
path path::append(const std::string &to_append) const
185,950✔
326
{
327
    path copy(internal_);
185,950✔
328

329
    if (!internal_.empty() && internal_.back() != guess_separator())
185,950✔
330
    {
331
        copy.internal_.push_back(guess_separator());
94,633✔
332
    }
333

334
    copy.internal_.append(to_append);
185,950✔
335

336
    return copy;
185,950✔
337
}
338

339
#if XLNT_HAS_FEATURE(U8_STRING_VIEW)
340
path path::append(std::u8string_view to_append) const
341
{
342
    return append(detail::to_string_copy(to_append));
343
}
344
#endif
345

346
path path::append(const path &to_append) const
44,780✔
347
{
348
    path copy(internal_);
44,780✔
349

350
    for (const auto &component : to_append.split())
133,230✔
351
    {
352
        copy = copy.append(component);
88,450✔
353
    }
354

355
    return copy;
44,780✔
356
}
357

358
char path::guess_separator() const
286,474✔
359
{
360
    if (system_separator() == '/' || internal_.empty() || internal_.front() == '/') return '/';
286,474✔
UNCOV
361
    if (is_absolute()) return internal_.at(2);
×
UNCOV
362
    return internal_.find('\\') != std::string::npos ? '\\' : '/';
×
363
}
364

365
path path::relative_to(const path &base_path) const
2,725✔
366
{
367
    if (is_relative()) return *this;
2,725✔
368

369
    auto base_split = base_path.split();
2,974✔
370
    auto this_split = split();
2,974✔
371
    auto index = std::size_t(0);
1,487✔
372

373
    while (index < base_split.size() && index < this_split.size() && base_split[index] == this_split[index])
5,633✔
374
    {
375
        index++;
2,073✔
376
    }
377

378
    auto result = path();
2,974✔
379

380
    for (auto i = index; i < this_split.size(); i++)
4,144✔
381
    {
382
        result = result.append(this_split[i]);
2,657✔
383
    }
384

385
    return result;
1,487✔
386
}
387

388
bool path::operator==(const path &other) const
146,076✔
389
{
390
    return internal_ == other.internal_;
146,076✔
391
}
392

UNCOV
393
bool path::operator!=(const path &other) const
×
394
{
UNCOV
395
    return !operator==(other);
×
396
}
397

398
} // 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