INSTINCT Code Coverage Report


Directory: src/
File: util/StringUtil.hpp
Date: 2025-02-07 16:54:41
Exec Total Coverage
Lines: 90 99 90.9%
Functions: 21 22 95.5%
Branches: 46 72 63.9%

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 2377078 static inline void ltrim(std::string& s)
29 {
30
3/6
✓ Branch 1 taken 2377079 times.
✗ Branch 2 not taken.
✗ Branch 4 not taken.
✓ Branch 5 taken 2377095 times.
✗ Branch 6 not taken.
✓ Branch 7 taken 2377093 times.
2377078 if (!s.empty() && s[0] == '\n')
31 {
32 s.erase(0, 1);
33 }
34
2/4
✓ Branch 1 taken 2377588 times.
✗ Branch 2 not taken.
✓ Branch 7 taken 2377289 times.
✗ Branch 8 not taken.
18248335 s.erase(s.begin(), std::ranges::find_if(s, [](int ch) { return !std::isspace(ch); }));
35 2377289 }
36
37 /// @brief Trim from end (in place)
38 /// @param[in, out] s The string to trim
39 2379534 static inline void rtrim(std::string& s)
40 {
41
4/6
✓ Branch 1 taken 1965863 times.
✓ Branch 2 taken 413696 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 1965945 times.
✗ Branch 7 not taken.
✓ Branch 8 taken 2379641 times.
2379534 if (!s.empty() && s[s.length() - 1] == '\n')
42 {
43 s.erase(s.length() - 1);
44 }
45
2/4
✓ Branch 4 taken 2379251 times.
✗ Branch 5 not taken.
✓ Branch 8 taken 2379146 times.
✗ Branch 9 not taken.
4758671 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 3005990 return !std::isspace(ch);
47 })
48 2379251 .base(),
49 2379641 s.end());
50 2379146 }
51
52 /// @brief Trim from both ends (in place)
53 /// @param[in, out] s The string to trim
54 2377096 static inline void trim(std::string& s)
55 {
56 2377096 ltrim(s);
57 2377280 rtrim(s);
58 2376807 }
59
60 /// @brief Trim from start (in place)
61 /// @param[in, out] sv The string view to trim
62 25236825 static inline void ltrim(std::string_view& sv)
63 {
64 25236825 sv.remove_prefix(std::min(sv.find_first_not_of(' '), sv.size()));
65 25236826 }
66
67 /// @brief Trim from end (in place)
68 /// @param[in, out] sv The string view to trim
69 25236825 static inline void rtrim(std::string_view& sv)
70 {
71 25236825 sv.remove_suffix(std::min(sv.size() - sv.find_last_not_of(' ') - 1, sv.size()));
72 25236828 }
73
74 /// @brief Trim from both ends (in place)
75 /// @param[in, out] sv The string view to trim
76 25236826 static inline void trim(std::string_view& sv)
77 {
78 25236826 ltrim(sv);
79 25236826 rtrim(sv);
80 25236828 }
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 48 static inline std::string rtrim_copy(std::string s)
95 {
96 48 rtrim(s);
97 48 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 2369641 static inline std::string trim_copy(std::string s)
104 {
105 2369641 trim(s);
106 2369370 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 25236826 static inline std::string_view trim_copy(std::string_view sv)
131 {
132 25236826 trim(sv);
133 25236828 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 186744 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 186758 times.
186744 if (from.empty())
152 {
153 return false;
154 }
155
1/2
✓ Branch 5 taken 186787 times.
✗ Branch 6 not taken.
186758 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 3375636 [cs](char ch1, char ch2) { return cs == RespectCase
158
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 3375636 times.
3375636 ? ch1 == ch2
159 3375636 : std::toupper(ch1) == std::toupper(ch2); });
160
161
2/2
✓ Branch 2 taken 141725 times.
✓ Branch 3 taken 45100 times.
186787 if (it == str.end())
162 {
163 141725 return false;
164 }
165 45100 auto start_pos = static_cast<size_t>(it - str.begin());
166
1/2
✓ Branch 2 taken 45100 times.
✗ Branch 3 not taken.
45100 str.replace(start_pos, from.length(), to);
167 45100 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 141633 static inline void replaceAll(std::string& str, const std::string& from, const std::string& to, CaseSensitivity cs)
176 {
177
2/2
✓ Branch 1 taken 45100 times.
✓ Branch 2 taken 141728 times.
186733 while (replace(str, from, to, cs)) {}
178 141728 }
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 102140 static inline void replaceAll(std::string& str, const std::string& from, const std::string& to)
185 {
186 102140 std::string::size_type n = 0;
187
2/2
✓ Branch 1 taken 1126 times.
✓ Branch 2 taken 102119 times.
103266 while ((n = str.find(from, n)) != std::string::npos)
188 {
189 1126 str.replace(n, from.size(), to);
190 1126 n += to.size();
191 }
192 102119 }
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 141637 static inline std::string replaceAll_copy(std::string str, const std::string& from, const std::string& to, CaseSensitivity cs)
201 {
202 141637 replaceAll(str, from, to, cs);
203 141742 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 99211 static inline std::string replaceAll_copy(std::string str, const std::string& from, const std::string& to)
212 {
213 99211 replaceAll(str, from, to);
214 99179 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 509997 static inline std::vector<std::string> split(const std::string& str, const std::string& delimiter)
222 {
223 509997 size_t pos_start = 0;
224 509997 size_t pos_end = 0;
225 509997 size_t delim_len = delimiter.length();
226 509999 std::vector<std::string> res;
227
228
2/2
✓ Branch 1 taken 27234479 times.
✓ Branch 2 taken 510003 times.
27744478 while ((pos_end = str.find(delimiter, pos_start)) != std::string::npos)
229 {
230
2/4
✓ Branch 1 taken 27234481 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 27234471 times.
✗ Branch 5 not taken.
27234479 res.push_back(str.substr(pos_start, pos_end - pos_start));
231 27234479 pos_start = pos_end + delim_len;
232 }
233
2/4
✓ Branch 1 taken 509997 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 510004 times.
✗ Branch 5 not taken.
510003 res.push_back(str.substr(pos_start));
234 510006 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 6657 static inline std::vector<std::string> split_wo_empty(const std::string& str, const std::string& delimiter)
251 {
252 6657 size_t pos_start = 0;
253 6657 size_t pos_end = 0;
254 6657 size_t delim_len = delimiter.length();
255 6657 std::vector<std::string> res;
256
257
2/2
✓ Branch 1 taken 39926 times.
✓ Branch 2 taken 6657 times.
46586 while ((pos_end = str.find(delimiter, pos_start)) != std::string::npos)
258 {
259
2/2
✓ Branch 0 taken 33300 times.
✓ Branch 1 taken 6626 times.
39926 if (pos_start != pos_end)
260 {
261
2/4
✓ Branch 1 taken 33301 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 33300 times.
✗ Branch 5 not taken.
33300 res.push_back(str.substr(pos_start, pos_end - pos_start));
262 }
263 39924 pos_start = pos_end + delim_len;
264
6/6
✓ Branch 1 taken 39965 times.
✓ Branch 2 taken 27 times.
✓ Branch 4 taken 67 times.
✓ Branch 5 taken 39902 times.
✓ Branch 6 taken 67 times.
✓ Branch 7 taken 39929 times.
39991 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 6630 times.
✓ Branch 2 taken 27 times.
6657 if (pos_start != str.size())
270 {
271
2/4
✓ Branch 1 taken 6631 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 6629 times.
✗ Branch 5 not taken.
6630 res.push_back(str.substr(pos_start));
272 }
273 6657 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 6654 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 6653 times.
✗ Branch 2 not taken.
6654 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 int64_t stoll(const String& str, int64_t default_value, std::size_t* pos = nullptr, int base = 10) noexcept
338 {
339 try
340 {
341 return std::stoll(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 /// @return Value corresponding to the content of str
355 template<StdString String>
356 float stof(const String& str, float default_value, std::size_t* pos = nullptr) noexcept
357 {
358 try
359 {
360 return std::stof(str, pos);
361 }
362 catch (...) // NOLINT(bugprone-empty-catch)
363 {}
364
365 return default_value;
366 }
367
368 /// @brief Interprets a value in the string str
369 /// @tparam String std::string or std::wstring and also allowing convertible types like const char*
370 /// @param str the string to convert
371 /// @param default_value default value to take if an invalid argument is given
372 /// @param pos address of an integer to store the number of characters processed
373 /// @return Value corresponding to the content of str
374 template<StdString String>
375 1232652 double stod(const String& str, double default_value, std::size_t* pos = nullptr) noexcept
376 {
377 try
378 {
379
2/2
✓ Branch 1 taken 1232104 times.
✓ Branch 2 taken 550 times.
1232652 return std::stod(str, pos);
380 }
381 550 catch (...) // NOLINT(bugprone-empty-catch)
382 {}
383
384 550 return default_value;
385 }
386
387 /// @brief Interprets a value in the string str
388 /// @tparam String std::string or std::wstring and also allowing convertible types like const char*
389 /// @param str the string to convert
390 /// @param default_value default value to take if an invalid argument is given
391 /// @param pos address of an integer to store the number of characters processed
392 /// @return Value corresponding to the content of str
393 template<StdString String>
394 long double stold(const String& str, long double default_value, std::size_t* pos = nullptr) noexcept
395 {
396 try
397 {
398 return std::stold(str, pos);
399 }
400 catch (...) // NOLINT(bugprone-empty-catch)
401 {}
402
403 return default_value;
404 }
405
406 } // namespace NAV::str
407