sources/mystring.cpp

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

mercurial