INSTINCT Code Coverage Report


Directory: src/
File: util/StringUtil.hpp
Date: 2025-06-02 15:19:59
Exec Total Coverage
Lines: 90 99 90.9%
Functions: 21 22 95.5%
Branches: 47 72 65.3%

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 2359281 static inline void ltrim(std::string& s)
29 {
30
4/6
✓ Branch 1 taken 2359255 times.
✓ Branch 2 taken 66 times.
✗ Branch 4 not taken.
✓ Branch 5 taken 2359281 times.
✗ Branch 6 not taken.
✓ Branch 7 taken 2359347 times.
2359281 if (!s.empty() && s[0] == '\n')
31 {
32 s.erase(0, 1);
33 }
34
2/4
✓ Branch 1 taken 2359658 times.
✗ Branch 2 not taken.
✓ Branch 7 taken 2359344 times.
✗ Branch 8 not taken.
18089997 s.erase(s.begin(), std::ranges::find_if(s, [](int ch) { return !std::isspace(ch); }));
35 2359344 }
36
37 /// @brief Trim from end (in place)
38 /// @param[in, out] s The string to trim
39 2361651 static inline void rtrim(std::string& s)
40 {
41
4/6
✓ Branch 1 taken 1953055 times.
✓ Branch 2 taken 408621 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 1952978 times.
✗ Branch 7 not taken.
✓ Branch 8 taken 2361599 times.
2361651 if (!s.empty() && s[s.length() - 1] == '\n')
42 {
43 s.erase(s.length() - 1);
44 }
45
2/4
✓ Branch 4 taken 2361171 times.
✗ Branch 5 not taken.
✓ Branch 8 taken 2361156 times.
✗ Branch 9 not taken.
4722581 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 2992530 return !std::isspace(ch);
47 })
48 2361171 .base(),
49 2361599 s.end());
50 2361156 }
51
52 /// @brief Trim from both ends (in place)
53 /// @param[in, out] s The string to trim
54 2359288 static inline void trim(std::string& s)
55 {
56 2359288 ltrim(s);
57 2359396 rtrim(s);
58 2358876 }
59
60 /// @brief Trim from start (in place)
61 /// @param[in, out] sv The string view to trim
62 25236786 static inline void ltrim(std::string_view& sv)
63 {
64 25236786 sv.remove_prefix(std::min(sv.find_first_not_of(' '), sv.size()));
65 25236785 }
66
67 /// @brief Trim from end (in place)
68 /// @param[in, out] sv The string view to trim
69 25236786 static inline void rtrim(std::string_view& sv)
70 {
71 25236786 sv.remove_suffix(std::min(sv.size() - sv.find_last_not_of(' ') - 1, sv.size()));
72 25236787 }
73
74 /// @brief Trim from both ends (in place)
75 /// @param[in, out] sv The string view to trim
76 25236786 static inline void trim(std::string_view& sv)
77 {
78 25236786 ltrim(sv);
79 25236786 rtrim(sv);
80 25236788 }
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 47 static inline std::string rtrim_copy(std::string s)
95 {
96 47 rtrim(s);
97 47 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 2351895 static inline std::string trim_copy(std::string s)
104 {
105 2351895 trim(s);
106 2351470 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 25236787 static inline std::string_view trim_copy(std::string_view sv)
131 {
132 25236787 trim(sv);
133 25236788 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 184937 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 184988 times.
184937 if (from.empty())
152 {
153 return false;
154 }
155
1/2
✓ Branch 5 taken 185002 times.
✗ Branch 6 not taken.
184988 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 3345778 [cs](char ch1, char ch2) { return cs == RespectCase
158
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 3345778 times.
3345778 ? ch1 == ch2
159 3345778 : std::toupper(ch1) == std::toupper(ch2); });
160
161
2/2
✓ Branch 2 taken 140863 times.
✓ Branch 3 taken 44162 times.
185002 if (it == str.end())
162 {
163 140863 return false;
164 }
165 44162 auto start_pos = static_cast<size_t>(it - str.begin());
166
1/2
✓ Branch 2 taken 44162 times.
✗ Branch 3 not taken.
44162 str.replace(start_pos, from.length(), to);
167 44162 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 140809 static inline void replaceAll(std::string& str, const std::string& from, const std::string& to, CaseSensitivity cs)
176 {
177
2/2
✓ Branch 1 taken 44162 times.
✓ Branch 2 taken 140849 times.
184971 while (replace(str, from, to, cs)) {}
178 140849 }
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 162753 static inline void replaceAll(std::string& str, const std::string& from, const std::string& to)
185 {
186 162753 std::string::size_type n = 0;
187
2/2
✓ Branch 1 taken 1145 times.
✓ Branch 2 taken 162755 times.
163898 while ((n = str.find(from, n)) != std::string::npos)
188 {
189 1145 str.replace(n, from.size(), to);
190 1145 n += to.size();
191 }
192 162755 }
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 140791 static inline std::string replaceAll_copy(std::string str, const std::string& from, const std::string& to, CaseSensitivity cs)
201 {
202 140791 replaceAll(str, from, to, cs);
203 140860 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 159749 static inline std::string replaceAll_copy(std::string str, const std::string& from, const std::string& to)
212 {
213 159749 replaceAll(str, from, to);
214 159750 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 509993 static inline std::vector<std::string> split(const std::string& str, const std::string& delimiter)
222 {
223 509993 size_t pos_start = 0;
224 509993 size_t pos_end = 0;
225 509993 size_t delim_len = delimiter.length();
226 509985 std::vector<std::string> res;
227
228
2/2
✓ Branch 1 taken 27234455 times.
✓ Branch 2 taken 509986 times.
27744432 while ((pos_end = str.find(delimiter, pos_start)) != std::string::npos)
229 {
230
2/4
✓ Branch 1 taken 27234463 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 27234441 times.
✗ Branch 5 not taken.
27234455 res.push_back(str.substr(pos_start, pos_end - pos_start));
231 27234447 pos_start = pos_end + delim_len;
232 }
233
2/4
✓ Branch 1 taken 509988 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 510001 times.
✗ Branch 5 not taken.
509986 res.push_back(str.substr(pos_start));
234 509997 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 6621 static inline std::vector<std::string> split_wo_empty(const std::string& str, const std::string& delimiter)
251 {
252 6621 size_t pos_start = 0;
253 6621 size_t pos_end = 0;
254 6621 size_t delim_len = delimiter.length();
255 6622 std::vector<std::string> res;
256
257
2/2
✓ Branch 1 taken 39734 times.
✓ Branch 2 taken 6619 times.
46353 while ((pos_end = str.find(delimiter, pos_start)) != std::string::npos)
258 {
259
2/2
✓ Branch 0 taken 33137 times.
✓ Branch 1 taken 6597 times.
39734 if (pos_start != pos_end)
260 {
261
2/4
✓ Branch 1 taken 33134 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 33140 times.
✗ Branch 5 not taken.
33137 res.push_back(str.substr(pos_start, pos_end - pos_start));
262 }
263 39734 pos_start = pos_end + delim_len;
264
6/6
✓ Branch 1 taken 39776 times.
✓ Branch 2 taken 27 times.
✓ Branch 4 taken 67 times.
✓ Branch 5 taken 39704 times.
✓ Branch 6 taken 67 times.
✓ Branch 7 taken 39731 times.
39801 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 6597 times.
✓ Branch 2 taken 27 times.
6619 if (pos_start != str.size())
270 {
271
2/4
✓ Branch 1 taken 6599 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 6598 times.
✗ Branch 5 not taken.
6597 res.push_back(str.substr(pos_start));
272 }
273 6624 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 6622 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 6617 times.
✗ Branch 2 not taken.
6622 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 1232415 double stod(const String& str, double default_value, std::size_t* pos = nullptr) noexcept
396 {
397 try
398 {
399
2/2
✓ Branch 1 taken 1231872 times.
✓ Branch 2 taken 550 times.
1232415 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