sources/mystring.cpp

branch
protocol5
changeset 195
be953e1621d9
parent 159
970d58a01e8b
parent 191
2e6cbacafdc7
child 197
819fdef70d68
equal deleted inserted replaced
176:060a13878ca0 195:be953e1621d9
1 /* 1 /*
2 Copyright 2014 - 2016 Teemu Piippo 2 Copyright 2014 - 2021 Teemu Piippo
3 All rights reserved. 3 All rights reserved.
4 4
5 Redistribution and use in source and binary forms, with or without 5 Redistribution and use in source and binary forms, with or without
6 modification, are permitted provided that the following conditions 6 modification, are permitted provided that the following conditions
7 are met: 7 are met:
29 */ 29 */
30 30
31 #include <cstring> 31 #include <cstring>
32 #include "main.h" 32 #include "main.h"
33 #include "mystring.h" 33 #include "mystring.h"
34 #include "md5.h"
35 34
36 BEGIN_ZFC_NAMESPACE 35 BEGIN_ZFC_NAMESPACE
37 36
38 /*! 37 /*!
39 * \brief Compares this string with another.
40 * \param other The string to compare with.
41 * \returns -1 if this string is lexicographically less than \c other,
42 * 0 if they are equal, or
43 * 1 if this string is lexicographically greater than \c other.
44 */
45 int String::compare (const String& other) const
46 {
47 return m_string.compare (other.stdString());
48 }
49
50 /*!
51 * \brief Removes all instances of an unwanted character from this string.
52 * \param unwanted Character to remove.
53 */
54 void String::strip (char unwanted)
55 {
56 for (int pos = 0; (pos = find (unwanted)) != -1;)
57 removeAt (pos--);
58 }
59
60 /*!
61 * \brief Removes all instances of multiple characters from this string.
62 * \param unwanted Characters to remove.
63 */
64 void String::strip (const List<char>& unwanted)
65 {
66 for (char character : unwanted)
67 strip(character);
68 }
69
70 /*!
71 * \returns an upper-case version of this string.
72 */
73 String String::toUpperCase() const
74 {
75 String result (m_string);
76
77 for (char &ch : result)
78 {
79 if (islower(ch))
80 ch -= 'a' - 'A';
81 }
82
83 return result;
84 }
85
86 /*!
87 * \returns a lower-case version of this string. 38 * \returns a lower-case version of this string.
88 */ 39 */
89 String String::toLowerCase() const 40 std::string to_lowercase(const std::string& string)
90 { 41 {
91 String result (m_string); 42 std::string result = string;
92 43
93 for (char &ch : result) 44 for (char& ch : result)
94 { 45 {
95 if (isupper(ch)) 46 if (isupper(ch))
96 ch += 'a' - 'A'; 47 ch += 'a' - 'A';
97 } 48 }
98 49
99 return result; 50 return result;
51 }
52
53 /*!
54 * \brief Joins the elements of this string list into one longer string.
55 * \param delimeter The delimeter to place between the element strings.
56 * \returns the catenated string.
57 */
58 std::string join_string_list(const std::vector<std::string>& strings, const std::string& delimeter)
59 {
60 std::string result;
61
62 for (const std::string &item : strings)
63 {
64 if (not result.empty())
65 result += delimeter;
66
67 result += item;
68 }
69
70 return result;
71 }
72
73 /*!
74 * \brief Modifies the given index so that if it is negative, it is translated into a positive index starting from the
75 * end of the string. For example, an index of -1 will be modified to point to the last character in the string,
76 * -2 to the second last, etc.
77 * \param index Index to translate.
78 */
79 inline void modifyIndex(const std::string& str, int& index)
80 {
81 if (index < 0)
82 index = str.length() - index;
83 }
84
85 /*!
86 * \param a Starting index of the range.
87 * \param b Ending index of the range.
88 * \returns a sub-string containing all characters from \c a to \c b, not including the character at \c b.
89 */
90 std::string mid(const std::string& str, int rangeBegin, int rangeEnd)
91 {
92 modifyIndex(str, rangeBegin);
93 modifyIndex(str, rangeEnd);
94 rangeBegin = max(rangeBegin, 0);
95 rangeEnd = min(rangeEnd, static_cast<signed>(str.length()));
96
97 if (rangeEnd <= rangeBegin)
98 return "";
99 else
100 return str.substr(rangeBegin, rangeEnd - rangeBegin);
101 }
102
103 /*!
104 * \param length Amount of characters to return.
105 * \returns the \c length right-most characters of the string.
106 */
107 std::string right(const std::string& str, int length)
108 {
109 if (length >= static_cast<signed>(str.length()))
110 return str;
111 else
112 return std::string{str.data() + str.length() - length};
113 }
114
115 /*!
116 * \brief Formats this string using \c vsnprintf, using the provided arguments.
117 * \param formatString Template string to use with formatting.
118 * \param args Variadic arguments to use with formatting.
119 */
120 std::string vsprintf(const char* formatString, va_list args)
121 {
122 std::string result;
123
124 // Copy the argument list so that we have something to provide to vsnprintf in case we have to call it again.
125 va_list argsCopy;
126 va_copy(argsCopy, args);
127
128 // First, attempt to format using a fixed-size buffer.
129 static char buffer[1024];
130 size_t length = vsnprintf(buffer, sizeof buffer, formatString, args);
131
132 if (length < sizeof buffer)
133 {
134 // vsnprintf succeeded in fitting the formatted string into the buffer, so we're done.
135 result = buffer;
136 }
137 else
138 {
139 // vsnprintf needs more space, so we have to allocate a new buffer and try again.
140 std::vector<char> newBuffer(length + 1);
141 vsnprintf(newBuffer.data(), length + 1, formatString, argsCopy);
142 result = newBuffer.data();
143 }
144
145 return result;
146 }
147
148 /*!
149 * \brief Formats this string using \c printf -like syntax.
150 * \param formatString Template string to use with formatting.
151 * \param ... Variadic arguments to use with formatting.
152 */
153 std::string __cdecl sprintf(const char* formatString, ...)
154 {
155 va_list args;
156 va_start (args, formatString);
157 std::string result = vsprintf(formatString, args);
158 va_end (args);
159 return result;
160 }
161
162 std::string remove_range(const std::string &string, int start, int end)
163 {
164 std::string result;
165 result.reserve(string.length() - (end - start));
166 std::copy(string.begin(), string.begin() + start, std::back_inserter(result));
167 std::copy(string.begin() + end, string.end(), std::back_inserter(result));
168 return result;
169 }
170
171
172 /*!
173 * \param other Sub-string to find from the beginning of this string.
174 * \returns whether or not this string begins with the provided sub-string.
175 */
176 bool starts_with(const std::string& str, const std::string& other)
177 {
178 if (str.length() < other.length())
179 return false;
180 else
181 return std::strncmp(str.data(), other.data(), other.length()) == 0;
182 }
183
184 /*!
185 * \brief Replaces all instances of \c text with \c replacement.
186 * \param text Text to replace away.
187 * \param replacement Text to replace \c text with.
188 */
189 void replace_all(std::string& str, const char* text, const char* replacement)
190 {
191 int position;
192 while ((position = str.find(text)) != -1)
193 {
194 str.replace(position, std::strlen(text), replacement);
195 }
100 } 196 }
101 197
102 /*! 198 /*!
103 * \brief Splits this string using the provided delimeter. 199 * \brief Splits this string using the provided delimeter.
104 * \param delimeter Delimeter to use for splitting. 200 * \param delimeter Delimeter to use for splitting.
105 * \returns a string list containing the split strings. 201 * \returns a string list containing the split strings.
106 */ 202 */
107 StringList String::split (char delimeter) const 203 std::vector<std::string> split(const std::string& string, const std::string& delimeter)
108 { 204 {
109 String delimeterString; 205 std::vector<std::string> result;
110 delimeterString += delimeter;
111 return split (delimeterString);
112 }
113
114 /*!
115 * \brief Splits this string using the provided delimeter.
116 * \param delimeter Delimeter to use for splitting.
117 * \returns a string list containing the split strings.
118 */
119 StringList String::split (const String& delimeter) const
120 {
121 StringList result;
122 int a = 0; 206 int a = 0;
123 int b; 207 int b;
124 208
125 // Find all separators and store the text left to them. 209 // Find all separators and store the text left to them.
126 while ((b = find (delimeter, a)) != -1) 210 while ((b = string.find(delimeter, a)) != -1)
127 { 211 {
128 String sub = mid (a, b); 212 std::string sub = mid(string, a, b);
129 213
130 if (sub.length() > 0) 214 if (sub.length() > 0)
131 result << sub; 215 result.push_back(sub);
132 216
133 a = b + delimeter.length(); 217 a = b + delimeter.length();
134 } 218 }
135 219
136 // Add the string at the right of the last separator 220 // Add the string at the right of the last separator
137 if (a < (int) length()) 221 if (a < static_cast<int>(string.length()))
138 result.append (mid (a, length())); 222 result.push_back(mid(string, a, string.length()));
139 223
140 return result; 224 return result;
141 }
142
143 /*!
144 * \brief Replaces all instances of \c text with \c replacement.
145 * \param text Text to replace away.
146 * \param replacement Text to replace \c text with.
147 */
148 void String::replace (const char* text, const char* replacement)
149 {
150 int position;
151
152 while ((position = find (text)) != -1)
153 m_string = m_string.replace (position, strlen (text), replacement);
154 }
155
156 /*!
157 * \param character Character to count.
158 * \returns the amount of \c character found in the string.
159 */
160 int String::count (char character) const
161 {
162 int result = 0;
163
164 for (char ch : *this)
165 {
166 if (ch == character)
167 result++;
168 }
169
170 return result;
171 }
172
173 /*!
174 * \param a Starting index of the range.
175 * \param b Ending index of the range.
176 * \returns a sub-string containing all characters from \c a to \c b, not including the character at \c b.
177 */
178 String String::mid (int rangeBegin, int rangeEnd) const
179 {
180 modifyIndex(rangeBegin);
181 modifyIndex(rangeEnd);
182 rangeBegin = max(rangeBegin, 0);
183 rangeEnd = min(rangeEnd, length());
184
185 if (rangeEnd <= rangeBegin)
186 return "";
187 else
188 return m_string.substr(rangeBegin, rangeEnd - rangeBegin);
189 }
190
191 /*!
192 * \param length Amount of characters to return.
193 * \returns the \c length right-most characters of the string.
194 */
195 String String::right(int length) const
196 {
197 if (length >= this->length())
198 return *this;
199 else
200 return String(chars() + this->length() - length);
201 }
202
203 /*!
204 * \brief Finds the first instance of a sub-string.
205 * \param subString Sub-string to search within this string.
206 * \param startingPosition Position to start looking for the sub-string from.
207 * \returns the position the first instance of sub-string found, or -1 if not found.
208 */
209 int String::find (const char* subString, int startingPosition) const
210 {
211 int position = m_string.find (subString, startingPosition);
212
213 if (position == int (std::string::npos))
214 return -1;
215 else
216 return position;
217 }
218
219 /*!
220 * \brief Finds the first instance of a character.
221 * \param character Character to search within this string.
222 * \param startingPosition Position to start looking for the character from.
223 * \returns the position of the first instance of the provided character found, or -1 if not found.
224 */
225 int String::find (char character, int startingPosition) const
226 {
227 int position = m_string.find (character, startingPosition);
228
229 if (position == int (std::string::npos))
230 return -1;
231 else
232 return position;
233 }
234
235 /*!
236 * \brief Finds the last instance of a sub-string.
237 * \param subString Sub-string to search within this string.
238 * \param startingPosition Position to start looking for the sub-string from.
239 * \returns the position the last instance of sub-string found, or -1 if not found.
240 */
241 int String::findLast (const char* subString, int startingPosition) const
242 {
243 modifyIndex(startingPosition);
244
245 for (; startingPosition > 0; startingPosition--)
246 {
247 if (strncmp (chars() + startingPosition, subString, strlen (subString)) == 0)
248 return startingPosition;
249 }
250
251 return -1;
252 } 225 }
253 226
254 /*! 227 /*!
255 * \brief Converts this string to an integer. 228 * \brief Converts this string to an integer.
256 * \param ok An pointer to a boolean to store whether or not the conversion was successful. 229 * \param ok An pointer to a boolean to store whether or not the conversion was successful.
257 * If \c ok is \c NULL, the success state is not stored. 230 * If \c ok is \c NULL, the success state is not stored.
258 * \param base The base to interpret this string with. 231 * \param base The base to interpret this string with.
259 * \returns the resulting integer. 232 * \returns the resulting integer.
260 */ 233 */
261 long String::toInt (bool* ok, int base) const 234 std::optional<long> to_int(const char* str, int base)
262 { 235 {
263 errno = 0; 236 errno = 0;
264 char* endPointer; 237 char* endPointer;
265 long result = strtol (chars(), &endPointer, base); 238 long result = strtol(str, &endPointer, base);
266 239 if (errno == 0 and *endPointer == '\0')
267 if (ok != nullptr) 240 {
268 *ok = (errno == 0 and *endPointer == '\0'); 241 return result;
269 242 }
270 return result; 243 else
271 } 244 {
272 245 return {};
273 /*! 246 }
274 * \brief Converts this string to a floating-point number.
275 * \param ok An pointer to a boolean to store whether or not the conversion was successful.
276 * If \c ok is \c NULL, the success state is not stored.
277 * \returns the resulting floating-point number.
278 */
279 float String::toFloat (bool* ok) const
280 {
281 return static_cast<float>(toDouble(ok));
282 }
283
284 /*!
285 * \brief Converts this string to a double-precision floating-point number.
286 * \param ok An pointer to a boolean to store whether or not the conversion was successful.
287 * If \c ok is \c NULL, the success state is not stored.
288 * \returns the resulting floating-point number.
289 */
290 double String::toDouble (bool* ok) const
291 {
292 errno = 0;
293 char* endptr;
294 double i = strtod (chars(), &endptr);
295
296 if (ok != nullptr)
297 *ok = (errno == 0 and *endptr == '\0');
298
299 return i;
300 }
301
302 /*!
303 * \brief Catenates this string with another string.
304 * \param text String to catenate to the end of this string.
305 * \returns the resulting string.
306 */
307 String String::operator+ (const String& text) const
308 {
309 String newString = *this;
310 newString.append (text);
311 return newString;
312 }
313
314 /*!
315 * \brief Catenates this string with another string.
316 * \param text String to catenate to the end of this string.
317 * \returns the resulting string.
318 */
319 String String::operator+ (const char* text) const
320 {
321 String newString = *this;
322 newString.append (text);
323 return newString;
324 }
325
326 /*!
327 * \returns whether or not this string represents a number.
328 */
329 bool String::isNumeric() const
330 {
331 char* endPointer;
332 strtol (chars(), &endPointer, 10);
333 return (endPointer != nullptr) and (*endPointer != '\0');
334 }
335
336 /*!
337 * \param other Sub-string to find from the end of this string.
338 * \return whether or not this string ends with the provided sub-string.
339 */
340 bool String::endsWith (const String& other) const
341 {
342 if (length() < other.length())
343 {
344 return false;
345 }
346 else
347 {
348 const int offset = length() - other.length();
349 return strncmp (chars() + offset, other.chars(), other.length()) == 0;
350 }
351 }
352
353 /*!
354 * \param other Sub-string to find from the beginning of this string.
355 * \returns whether or not this string begins with the provided sub-string.
356 */
357 bool String::startsWith (const String& other) const
358 {
359 if (length() < other.length())
360 return false;
361 else
362 return strncmp (chars(), other.chars(), other.length()) == 0;
363 }
364
365 /*!
366 * \brief Formats this string using \c printf -like syntax.
367 * \param formatString Template string to use with formatting.
368 * \param ... Variadic arguments to use with formatting.
369 */
370 void __cdecl String::sprintf (const char* formatString, ...)
371 {
372 va_list args;
373 va_start (args, formatString);
374 this->vsprintf (formatString, args);
375 va_end (args);
376 }
377
378 /*!
379 * \brief Formats this string using \c vsnprintf, using the provided arguments.
380 * \param formatString Template string to use with formatting.
381 * \param args Variadic arguments to use with formatting.
382 */
383 void String::vsprintf (const char* formatString, va_list args)
384 {
385 // Copy the argument list so that we have something to provide to vsnprintf in case we have to call it again.
386 va_list argsCopy;
387 va_copy(argsCopy, args);
388
389 // First, attempt to format using a fixed-size buffer.
390 static char buffer[1024];
391 size_t length = vsnprintf(buffer, sizeof buffer, formatString, args);
392
393 if (length < sizeof buffer)
394 {
395 // vsnprintf succeeded in fitting the formatted string into the buffer, so we're done.
396 m_string = buffer;
397 }
398 else
399 {
400 // vsnprintf needs more space, so we have to allocate a new buffer and try again.
401 Vector<char> newBuffer(length + 1);
402 vsnprintf(newBuffer.data(), length + 1, formatString, argsCopy);
403 m_string = newBuffer;
404 }
405 }
406
407 /*!
408 * \brief Joins the elements of this string list into one longer string.
409 * \param delimeter The delimeter to place between the element strings.
410 * \returns the catenated string.
411 */
412 String StringList::join (const String& delimeter)
413 {
414 String result;
415
416 for (const String &item : container())
417 {
418 if (result.isEmpty() == false)
419 result += delimeter;
420
421 result += item;
422 }
423
424 return result;
425 }
426
427 /*!
428 * \brief Tries to match this string against a mask pattern. In the pattern, '?' refers to one character, and '*' to
429 * any number of characters.
430 * \param pattern The masking pattern to use for matching.
431 * \returns whether or not this string matches the provided pattern.
432 */
433 bool String::maskAgainst (const String& pattern) const
434 {
435 // Elevate to uppercase for case-insensitive matching
436 String pattern_upper = pattern.toUpperCase();
437 String this_upper = toUpperCase();
438 const char* maskstring = pattern_upper.chars();
439 const char* mptr = &maskstring[0];
440
441 for (const char* sptr = this_upper.chars(); *sptr != '\0'; sptr++)
442 {
443 if (*mptr == '?')
444 {
445 if (*(sptr + 1) == '\0')
446 {
447 // ? demands that there's a character here and there wasn't.
448 // Therefore, mask matching fails
449 return false;
450 }
451 }
452 else if (*mptr == '*')
453 {
454 char end = *(++mptr);
455
456 // If '*' is the final character of the message, all of the remaining
457 // string matches against the '*'. We don't need to bother checking
458 // the string any further.
459 if (end == '\0')
460 return true;
461
462 // Skip to the end character
463 while (*sptr != end and *sptr != '\0')
464 sptr++;
465
466 // String ended while the mask still had stuff
467 if (*sptr == '\0')
468 return false;
469 }
470 else if (*sptr != *mptr)
471 return false;
472
473 mptr++;
474 }
475
476 return true;
477 }
478
479 /*!
480 * \brief Converts a short integer into a string.
481 * \param value The value to convert.
482 * \returns the resulting string.
483 */
484 String String::fromNumber (short int value)
485 {
486 char buffer[32];
487 ::sprintf (buffer, "%d", value);
488 return String (buffer);
489 }
490
491 /*!
492 * \brief Converts an integer into a string.
493 * \param value The value to convert.
494 * \returns the resulting string.
495 */
496 String String::fromNumber (int value)
497 {
498 char buffer[32];
499 ::sprintf (buffer, "%d", value);
500 return String (buffer);
501 }
502
503 /*!
504 * \brief Converts a long integer into a string.
505 * \param value The value to convert.
506 * \returns the resulting string.
507 */
508 String String::fromNumber (long int value)
509 {
510 char buffer[32];
511 ::sprintf (buffer, "%ld", value);
512 return String (buffer);
513 }
514
515 /*!
516 * \brief Converts an unsigned short integer into a string.
517 * \param value The value to convert.
518 * \returns the resulting string.
519 */
520 String String::fromNumber (unsigned short int value)
521 {
522 char buffer[32];
523 ::sprintf (buffer, "%u", value);
524 return String (buffer);
525 }
526
527 /*!
528 * \brief Converts an unsigned integer into a string.
529 * \param value The value to convert.
530 * \returns the resulting string.
531 */
532 String String::fromNumber (unsigned int value)
533 {
534 char buffer[32];
535 ::sprintf (buffer, "%u", value);
536 return String (buffer);
537 }
538
539 /*!
540 * \brief Converts an unsigned long integer into a string.
541 * \param value The value to convert.
542 * \returns the resulting string.
543 */
544 String String::fromNumber (unsigned long int value)
545 {
546 char buffer[32];
547 ::sprintf (buffer, "%lu", value);
548 return String (buffer);
549 }
550
551 /*!
552 * \brief Converts a double-precision floating point number into a string, using the "%f" format specifier.
553 * \param value The value to convert.
554 * \returns the resulting string.
555 */
556 String String::fromNumber (double value)
557 {
558 char buffer[64];
559 ::sprintf (buffer, "%f", value);
560 return String (buffer);
561 }
562
563 /*!
564 * \brief Constructs a string from a vector of bytes. The bytes do not have to be null-terminated.
565 * \param bytes Bytes to use for construction
566 * \returns the resulting string.
567 */
568 String String::fromBytes(const ByteArray& bytes)
569 {
570 return String(reinterpret_cast<const Vector<char>&>(bytes));
571 }
572
573 /*!
574 * \returns the MD5-checksum of this string.
575 */
576 String String::md5() const
577 {
578 char checksum[33];
579 CalculateMD5 (reinterpret_cast<const unsigned char*> (chars()), length(), checksum);
580 checksum[sizeof checksum - 1] = '\0';
581 return String (checksum);
582 } 247 }
583 248
584 /*! 249 /*!
585 * \brief Removes leading and trailing whitespace from this string. Alternatively a custom filter can be used to strip 250 * \brief Removes leading and trailing whitespace from this string. Alternatively a custom filter can be used to strip
586 * something else than whitespace. 251 * something else than whitespace.
587 * \param filter The filtering function to use. 252 * \param filter The filtering function to use.
588 */ 253 */
589 void String::normalize (int (*filter)(int)) 254 void normalize(std::string& string, int (*filter)(int))
590 { 255 {
591 int a = 0; 256 int a = 0;
592 int b = length() - 1; 257 int b = string.length() - 1;
593 258 while ((*filter)(string[a]) and a != b)
594 while ((*filter) (m_string[a]) and a != b) 259 {
595 ++a; 260 ++a;
596 261 }
597 while ((*filter) (m_string[b]) and a != b) 262 while ((*filter)(string[b]) and a != b)
263 {
598 --b; 264 --b;
599 265 }
600 if (a == b) 266 if (a == b)
601 m_string = ""; 267 {
602 else if (a != 0 or b != length() - 1) 268 string = "";
603 m_string = m_string.substr (a, b - a + 1); 269 }
604 } 270 else if (a != 0 or b != static_cast<signed>(string.length() - 1))
605 271 {
606 /*! 272 string = string.substr (a, b - a + 1);
607 * \returns a version of this string without leading or trailing whitespace. Alternatively a custom filter can be used 273 }
608 * to strip something else than whitespace.
609 */
610 String String::normalized (int (*filter)(int)) const
611 {
612 String result = *this;
613 result.normalize(filter);
614 return result;
615 } 274 }
616 275
617 END_ZFC_NAMESPACE 276 END_ZFC_NAMESPACE

mercurial