INSTINCT Code Coverage Report


Directory: src/
File: util/StringUtil.hpp
Date: 2025-11-25 23:34:18
Exec Total Coverage
Lines: 90 99 90.9%
Functions: 21 22 95.5%
Branches: 48 72 66.7%

Line Branch Exec Source
1 // This file is part of INSTINCT, the INS Toolkit for Integrated
2 // Navigation Concepts and Training by the Institute of Navigation of
3 // the University of Stuttgart, Germany.
4 //
5 // This Source Code Form is subject to the terms of the Mozilla Public
6 // License, v. 2.0. If a copy of the MPL was not distributed with this
7 // file, You can obtain one at https://mozilla.org/MPL/2.0/.
8
9 /// @file StringUtil.hpp
10 /// @brief Utility functions for working with std::strings
11 /// @author T. Topp (topp@ins.uni-stuttgart.de)
12 /// @date 2020-09-16
13
14 #pragma once
15
16 #include <algorithm>
17 #include <cctype>
18 #include <cstdint>
19 #include <locale>
20 #include <vector>
21 #include <string>
22 #include <string_view>
23
24 namespace NAV::str
25 {
26 /// @brief Trim from start (in place)
27 /// @param[in, out] s The string to trim
28 2844825 static inline void ltrim(std::string& s)
29 {
30
4/6
✓ Branch 1 taken 2844797 times.
✓ Branch 2 taken 79 times.
✗ Branch 4 not taken.
✓ Branch 5 taken 2844855 times.
✗ Branch 6 not taken.
✓ Branch 7 taken 2844934 times.
2844825 if (!s.empty() && s[0] == '\n')
31 {
32 s.erase(0, 1);
33 }
34
2/4
✓ Branch 1 taken 2845222 times.
✗ Branch 2 not taken.
✓ Branch 7 taken 2844958 times.
✗ Branch 8 not taken.
21149674 s.erase(s.begin(), std::ranges::find_if(s, [](int ch) { return !std::isspace(ch); }));
35 2844958 }
36
37 /// @brief Trim from end (in place)
38 /// @param[in, out] s The string to trim
39 2847240 static inline void rtrim(std::string& s)
40 {
41
4/6
✓ Branch 1 taken 2437588 times.
✓ Branch 2 taken 409707 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 2437493 times.
✗ Branch 7 not taken.
✓ Branch 8 taken 2847200 times.
2847240 if (!s.empty() && s[s.length() - 1] == '\n')
42 {
43 s.erase(s.length() - 1);
44 }
45
2/4
✓ Branch 4 taken 2846814 times.
✗ Branch 5 not taken.
✓ Branch 8 taken 2846718 times.
✗ Branch 9 not taken.
5693683 s.erase(std::find_if(s.rbegin(), s.rend(), [](int ch) { // NOLINT(boost-use-ranges,modernize-use-ranges) // ranges::find_last_if is C++23 and not supported yet
46 3497863 return !std::isspace(ch);
47 })
48 2846814 .base(),
49 2847200 s.end());
50 2846718 }
51
52 /// @brief Trim from both ends (in place)
53 /// @param[in, out] s The string to trim
54 2844842 static inline void trim(std::string& s)
55 {
56 2844842 ltrim(s);
57 2844937 rtrim(s);
58 2844343 }
59
60 /// @brief Trim from start (in place)
61 /// @param[in, out] sv The string view to trim
62 26003241 static inline void ltrim(std::string_view& sv)
63 {
64 26003241 sv.remove_prefix(std::min(sv.find_first_not_of(' '), sv.size()));
65 26003239 }
66
67 /// @brief Trim from end (in place)
68 /// @param[in, out] sv The string view to trim
69 26003240 static inline void rtrim(std::string_view& sv)
70 {
71 26003240 sv.remove_suffix(std::min(sv.size() - sv.find_last_not_of(' ') - 1, sv.size()));
72 26003237 }
73
74 /// @brief Trim from both ends (in place)
75 /// @param[in, out] sv The string view to trim
76 26003241 static inline void trim(std::string_view& sv)
77 {
78 26003241 ltrim(sv);
79 26003239 rtrim(sv);
80 26003238 }
81
82 /// @brief Trim from start (copying)
83 /// @param[in] s The string to trim
84 /// @return The trimmed string
85 static inline std::string ltrim_copy(std::string s)
86 {
87 ltrim(s);
88 return s;
89 }
90
91 /// @brief Trim from end (copying)
92 /// @param[in] s The string to trim
93 /// @return The trimmed string
94 49 static inline std::string rtrim_copy(std::string s)
95 {
96 49 rtrim(s);
97 49 return s;
98 }
99
100 /// @brief Trim from both ends (copying)
101 /// @param[in] s The string to trim
102 /// @return The trimmed string
103 2836267 static inline std::string trim_copy(std::string s)
104 {
105 2836267 trim(s);
106 2835791 return s;
107 }
108
109 /// @brief Trim from start (copying)
110 /// @param[in] sv The string view to trim
111 /// @return The trimmed string
112 static inline std::string_view ltrim_copy(std::string_view sv)
113 {
114 ltrim(sv);
115 return sv;
116 }
117
118 /// @brief Trim from end (copying)
119 /// @param[in] sv The string view to trim
120 /// @return The trimmed string
121 static inline std::string_view rtrim_copy(std::string_view sv)
122 {
123 rtrim(sv);
124 return sv;
125 }
126
127 /// @brief Trim from both ends (copying)
128 /// @param[in] sv The string view to trim
129 /// @return The trimmed string
130 26003241 static inline std::string_view trim_copy(std::string_view sv)
131 {
132 26003241 trim(sv);
133 26003238 return sv;
134 }
135
136 /// @brief Enum for case sensitive tasks
137 enum CaseSensitivity : uint8_t
138 {
139 RespectCase, ///< Respect the case
140 IgnoreCase, ///< Ignore case
141 };
142
143 /// @brief Replaces the first occurrence of a search pattern with another sequence
144 /// @param[in, out] str String to search in and return value
145 /// @param[in] from String pattern to search for
146 /// @param[in] to Replacement string
147 /// @param[in] cs Case sensitivity
148 /// @return True if something was replaced
149 188272 static inline bool replace(std::string& str, const std::string& from, const std::string& to, CaseSensitivity cs = RespectCase)
150 {
151
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 188301 times.
188272 if (from.empty())
152 {
153 return false;
154 }
155
1/2
✓ Branch 5 taken 188328 times.
✗ Branch 6 not taken.
188301 auto it = std::search(str.begin(), str.end(), // NOLINT(boost-use-ranges,modernize-use-ranges) // ranges::search is C++23 and not supported yet
156 from.begin(), from.end(),
157 3399665 [cs](char ch1, char ch2) { return cs == RespectCase
158
2/2
✓ Branch 0 taken 164 times.
✓ Branch 1 taken 3399501 times.
3399665 ? ch1 == ch2
159 3399665 : std::toupper(ch1) == std::toupper(ch2); });
160
161
2/2
✓ Branch 2 taken 142534 times.
✓ Branch 3 taken 45822 times.
188328 if (it == str.end())
162 {
163 142534 return false;
164 }
165 45822 auto start_pos = static_cast<size_t>(it - str.begin());
166
1/2
✓ Branch 2 taken 45818 times.
✗ Branch 3 not taken.
45824 str.replace(start_pos, from.length(), to);
167 45818 return true;
168 }
169
170 /// @brief Replaces all occurrence of a search pattern with another sequence
171 /// @param[in, out] str String to search in and return value
172 /// @param[in] from String pattern to search for
173 /// @param[in] to Replacement string
174 /// @param[in] cs Case sensitivity
175 142475 static inline void replaceAll(std::string& str, const std::string& from, const std::string& to, CaseSensitivity cs)
176 {
177
2/2
✓ Branch 1 taken 45819 times.
✓ Branch 2 taken 142501 times.
188294 while (replace(str, from, to, cs)) {}
178 142501 }
179
180 /// @brief Replaces all occurrence of a search pattern with another sequence
181 /// @param[in, out] str String to search in and return value
182 /// @param[in] from String pattern to search for
183 /// @param[in] to Replacement string
184 137835 static inline void replaceAll(std::string& str, const std::string& from, const std::string& to)
185 {
186 137835 std::string::size_type n = 0;
187
2/2
✓ Branch 1 taken 1247 times.
✓ Branch 2 taken 137830 times.
139082 while ((n = str.find(from, n)) != std::string::npos)
188 {
189 1247 str.replace(n, from.size(), to);
190 1247 n += to.size();
191 }
192 137830 }
193
194 /// @brief Replaces all occurrence of a search pattern with another sequence
195 /// @param[in, out] str String to search in and return value
196 /// @param[in] from String pattern to search for
197 /// @param[in] to Replacement string
198 /// @param[in] cs Case sensitivity
199 /// @return The string with the replacements
200 142459 static inline std::string replaceAll_copy(std::string str, const std::string& from, const std::string& to, CaseSensitivity cs)
201 {
202 142459 replaceAll(str, from, to, cs);
203 142520 return str;
204 }
205
206 /// @brief Replaces all occurrence of a search pattern with another sequence
207 /// @param[in, out] str String to search in and return value
208 /// @param[in] from String pattern to search for
209 /// @param[in] to Replacement string
210 /// @return The string with the replacements
211 134577 static inline std::string replaceAll_copy(std::string str, const std::string& from, const std::string& to)
212 {
213 134577 replaceAll(str, from, to);
214 134577 return str;
215 }
216
217 /// @brief Splits a string into parts at a delimiter
218 /// @param[in] str String to split
219 /// @param[in] delimiter Sequence of characters to split at
220 /// @return List with splitted parts
221 511196 static inline std::vector<std::string> split(const std::string& str, const std::string& delimiter)
222 {
223 511196 size_t pos_start = 0;
224 511196 size_t pos_end = 0;
225 511196 size_t delim_len = delimiter.length();
226 511195 std::vector<std::string> res;
227
228
2/2
✓ Branch 1 taken 27236854 times.
✓ Branch 2 taken 511193 times.
27748058 while ((pos_end = str.find(delimiter, pos_start)) != std::string::npos)
229 {
230
2/4
✓ Branch 1 taken 27236875 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 27236854 times.
✗ Branch 5 not taken.
27236854 res.push_back(str.substr(pos_start, pos_end - pos_start));
231 27236863 pos_start = pos_end + delim_len;
232 }
233
2/4
✓ Branch 1 taken 511201 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 511205 times.
✗ Branch 5 not taken.
511193 res.push_back(str.substr(pos_start));
234 511201 return res;
235 }
236
237 /// @brief Splits a string into parts at a delimiter
238 /// @param[in] str String to split
239 /// @param[in] delimiter Character to split at
240 /// @return List with splitted parts
241 static inline std::vector<std::string> split(const std::string& str, char delimiter)
242 {
243 return split(str, std::string(1, delimiter));
244 }
245
246 /// @brief Splits a string into parts at a delimiter and removes empty entries
247 /// @param[in] str String to split
248 /// @param[in] delimiter Sequence of characters to split at
249 /// @return List with splitted parts
250 6682 static inline std::vector<std::string> split_wo_empty(const std::string& str, const std::string& delimiter)
251 {
252 6682 size_t pos_start = 0;
253 6682 size_t pos_end = 0;
254 6682 size_t delim_len = delimiter.length();
255 6682 std::vector<std::string> res;
256
257
2/2
✓ Branch 1 taken 40079 times.
✓ Branch 2 taken 6680 times.
46759 while ((pos_end = str.find(delimiter, pos_start)) != std::string::npos)
258 {
259
2/2
✓ Branch 0 taken 33426 times.
✓ Branch 1 taken 6653 times.
40079 if (pos_start != pos_end)
260 {
261
2/4
✓ Branch 1 taken 33428 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 33427 times.
✗ Branch 5 not taken.
33426 res.push_back(str.substr(pos_start, pos_end - pos_start));
262 }
263 40079 pos_start = pos_end + delim_len;
264
6/6
✓ Branch 1 taken 40119 times.
✓ Branch 2 taken 26 times.
✓ Branch 4 taken 67 times.
✓ Branch 5 taken 40051 times.
✓ Branch 6 taken 67 times.
✓ Branch 7 taken 40077 times.
40146 while (pos_start < str.size() && str.find(delimiter, pos_start) == pos_start)
265 {
266 67 pos_start += delim_len;
267 }
268 }
269
2/2
✓ Branch 1 taken 6656 times.
✓ Branch 2 taken 27 times.
6680 if (pos_start != str.size())
270 {
271
2/4
✓ Branch 1 taken 6656 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 6656 times.
✗ Branch 5 not taken.
6656 res.push_back(str.substr(pos_start));
272 }
273 6683 return res;
274 }
275
276 /// @brief Splits a string into parts at a delimiter and removes empty entries
277 /// @param[in] str String to split
278 /// @param[in] delimiter Character to split at
279 /// @return List with splitted parts
280 static inline std::vector<std::string> split_wo_empty(const std::string& str, char delimiter)
281 {
282 return split_wo_empty(str, std::string(1, delimiter));
283 }
284
285 /// @brief Concept limiting the type to std::string and std::wstring, but also allowing convertible types like const char*
286 template<typename T>
287 concept StdString = std::convertible_to<T, std::string> || std::convertible_to<T, std::wstring>;
288
289 /// @brief Interprets a value in the string str
290 /// @tparam String std::string or std::wstring and also allowing convertible types like const char*
291 /// @param str the string to convert
292 /// @param default_value default value to take if an invalid argument is given
293 /// @param pos address of an integer to store the number of characters processed
294 /// @param base the number base
295 /// @return Value corresponding to the content of str
296 template<StdString String>
297 6679 int stoi(const String& str, int default_value, std::size_t* pos = nullptr, int base = 10) noexcept
298 {
299 try
300 {
301
1/2
✓ Branch 1 taken 6679 times.
✗ Branch 2 not taken.
6679 return std::stoi(str, pos, base);
302 }
303 catch (...) // NOLINT(bugprone-empty-catch)
304 {}
305
306 return default_value;
307 }
308
309 /// @brief Interprets a value in the string str
310 /// @tparam String std::string or std::wstring and also allowing convertible types like const char*
311 /// @param str the string to convert
312 /// @param default_value default value to take if an invalid argument is given
313 /// @param pos address of an integer to store the number of characters processed
314 /// @param base the number base
315 /// @return Value corresponding to the content of str
316 template<StdString String>
317 int64_t stol(const String& str, int64_t default_value, std::size_t* pos = nullptr, int base = 10) noexcept
318 {
319 try
320 {
321 return std::stol(str, pos, base);
322 }
323 catch (...) // NOLINT(bugprone-empty-catch)
324 {}
325
326 return default_value;
327 }
328
329 /// @brief Interprets a value in the string str
330 /// @tparam String std::string or std::wstring and also allowing convertible types like const char*
331 /// @param str the string to convert
332 /// @param default_value default value to take if an invalid argument is given
333 /// @param pos address of an integer to store the number of characters processed
334 /// @param base the number base
335 /// @return Value corresponding to the content of str
336 template<StdString String>
337 uint64_t stoul(const String& str, uint64_t default_value, std::size_t* pos = nullptr, int base = 10) noexcept
338 {
339 try
340 {
341 return std::stoul(str, pos, base);
342 }
343 catch (...) // NOLINT(bugprone-empty-catch)
344 {}
345
346 return default_value;
347 }
348
349 /// @brief Interprets a value in the string str
350 /// @tparam String std::string or std::wstring and also allowing convertible types like const char*
351 /// @param str the string to convert
352 /// @param default_value default value to take if an invalid argument is given
353 /// @param pos address of an integer to store the number of characters processed
354 /// @param base the number base
355 /// @return Value corresponding to the content of str
356 template<StdString String>
357 int64_t stoll(const String& str, int64_t default_value, std::size_t* pos = nullptr, int base = 10) noexcept
358 {
359 try
360 {
361 return std::stoll(str, pos, base);
362 }
363 catch (...) // NOLINT(bugprone-empty-catch)
364 {}
365
366 return default_value;
367 }
368
369 /// @brief Interprets a value in the string str
370 /// @tparam String std::string or std::wstring and also allowing convertible types like const char*
371 /// @param str the string to convert
372 /// @param default_value default value to take if an invalid argument is given
373 /// @param pos address of an integer to store the number of characters processed
374 /// @return Value corresponding to the content of str
375 template<StdString String>
376 float stof(const String& str, float default_value, std::size_t* pos = nullptr) noexcept
377 {
378 try
379 {
380 return std::stof(str, pos);
381 }
382 catch (...) // NOLINT(bugprone-empty-catch)
383 {}
384
385 return default_value;
386 }
387
388 /// @brief Interprets a value in the string str
389 /// @tparam String std::string or std::wstring and also allowing convertible types like const char*
390 /// @param str the string to convert
391 /// @param default_value default value to take if an invalid argument is given
392 /// @param pos address of an integer to store the number of characters processed
393 /// @return Value corresponding to the content of str
394 template<StdString String>
395 1267463 double stod(const String& str, double default_value, std::size_t* pos = nullptr) noexcept
396 {
397 try
398 {
399
2/2
✓ Branch 1 taken 1266916 times.
✓ Branch 2 taken 550 times.
1267463 return std::stod(str, pos);
400 }
401 550 catch (...) // NOLINT(bugprone-empty-catch)
402 {}
403
404 550 return default_value;
405 }
406
407 /// @brief Interprets a value in the string str
408 /// @tparam String std::string or std::wstring and also allowing convertible types like const char*
409 /// @param str the string to convert
410 /// @param default_value default value to take if an invalid argument is given
411 /// @param pos address of an integer to store the number of characters processed
412 /// @return Value corresponding to the content of str
413 template<StdString String>
414 long double stold(const String& str, long double default_value, std::size_t* pos = nullptr) noexcept
415 {
416 try
417 {
418 return std::stold(str, pos);
419 }
420 catch (...) // NOLINT(bugprone-empty-catch)
421 {}
422
423 return default_value;
424 }
425
426 } // namespace NAV::str
427