Manual pages: mcmcdiffmceditmcview

root/lib/strutil/strutil.c

/* [previous][next][first][last][top][bottom][index][help]  */

DEFINITIONS

This source file includes following definitions.
  1. str_test_not_convert
  2. _str_convert
  3. str_test_encoding_class
  4. str_choose_str_functions
  5. str_crt_conv_to
  6. str_crt_conv_from
  7. str_close_conv
  8. str_convert
  9. str_nconvert
  10. str_conv_gerror_message
  11. str_vfs_convert_from
  12. str_vfs_convert_to
  13. str_printf
  14. str_insert_replace_char
  15. str_translate_char
  16. str_detect_termencoding
  17. str_isutf8
  18. str_init_strings
  19. str_uninit_strings
  20. str_term_form
  21. str_fit_to_term
  22. str_term_trim
  23. str_term_substring
  24. str_get_next_char
  25. str_cget_next_char
  26. str_next_char
  27. str_cnext_char
  28. str_get_prev_char
  29. str_cget_prev_char
  30. str_prev_char
  31. str_cprev_char
  32. str_get_next_char_safe
  33. str_cget_next_char_safe
  34. str_next_char_safe
  35. str_cnext_char_safe
  36. str_get_prev_char_safe
  37. str_cget_prev_char_safe
  38. str_prev_char_safe
  39. str_cprev_char_safe
  40. str_next_noncomb_char
  41. str_cnext_noncomb_char
  42. str_prev_noncomb_char
  43. str_cprev_noncomb_char
  44. str_is_valid_char
  45. str_term_width1
  46. str_term_width2
  47. str_term_char_width
  48. str_offset_to_pos
  49. str_length
  50. str_length_char
  51. str_length2
  52. str_length_noncomb
  53. str_column_to_pos
  54. str_isspace
  55. str_ispunct
  56. str_isalnum
  57. str_isdigit
  58. str_toupper
  59. str_tolower
  60. str_isprint
  61. str_iscombiningmark
  62. str_trunc
  63. str_create_search_needle
  64. str_release_search_needle
  65. str_search_first
  66. str_search_last
  67. str_is_valid_string
  68. str_compare
  69. str_ncompare
  70. str_casecmp
  71. str_ncasecmp
  72. str_prefix
  73. str_caseprefix
  74. str_fix_string
  75. str_create_key
  76. str_create_key_for_filename
  77. str_key_collate
  78. str_release_key
  79. str_msg_term_size
  80. strrstr_skip_count
  81. parse_integer
  82. str_rstrip_eol

   1 /*
   2    Common strings utilities
   3 
   4    Copyright (C) 2007-2025
   5    Free Software Foundation, Inc.
   6 
   7    Written by:
   8    Rostislav Benes, 2007
   9 
  10    This file is part of the Midnight Commander.
  11 
  12    The Midnight Commander is free software: you can redistribute it
  13    and/or modify it under the terms of the GNU General Public License as
  14    published by the Free Software Foundation, either version 3 of the License,
  15    or (at your option) any later version.
  16 
  17    The Midnight Commander is distributed in the hope that it will be useful,
  18    but WITHOUT ANY WARRANTY; without even the implied warranty of
  19    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  20    GNU General Public License for more details.
  21 
  22    You should have received a copy of the GNU General Public License
  23    along with this program.  If not, see <https://www.gnu.org/licenses/>.
  24  */
  25 
  26 #include <config.h>
  27 
  28 #include <stdlib.h>
  29 #include <langinfo.h>
  30 #include <string.h>
  31 #include <errno.h>
  32 
  33 #include "lib/global.h"
  34 #include "lib/util.h"  // MC_PTR_FREE
  35 #include "lib/strutil.h"
  36 
  37 /*** global variables ****************************************************************************/
  38 
  39 GIConv str_cnv_to_term;
  40 GIConv str_cnv_from_term;
  41 GIConv str_cnv_not_convert = INVALID_CONV;
  42 
  43 /*** file scope macro definitions ****************************************************************/
  44 
  45 /*** file scope type declarations ****************************************************************/
  46 
  47 /*** forward declarations (file scope functions) *************************************************/
  48 
  49 /*** file scope variables ************************************************************************/
  50 
  51 /* names, that are used for utf-8 */
  52 static const char *const str_utf8_encodings[] = {
  53     "utf-8",
  54     "utf8",
  55     NULL,
  56 };
  57 
  58 /* standard 8bit encodings, no wide or multibytes characters */
  59 static const char *const str_8bit_encodings[] = {
  60     "cp-1251",
  61     "cp1251",
  62 
  63     // solaris
  64     "ansi-1251",
  65     "ansi1251",
  66 
  67     "cp-1250",
  68     "cp1250",
  69     "cp-866",
  70     "cp866",
  71 
  72     // glibc
  73     "ibm-866",
  74     "ibm866",
  75 
  76     "cp-850",
  77     "cp850",
  78     "cp-852",
  79     "cp852",
  80     "iso-8859",
  81     "iso8859",
  82     "koi8",
  83 
  84     NULL,
  85 };
  86 
  87 /* terminal encoding */
  88 static char *codeset = NULL;
  89 static char *term_encoding = NULL;
  90 /* function for encoding specific operations */
  91 static struct str_class used_class;
  92 
  93 /* --------------------------------------------------------------------------------------------- */
  94 /*** file scope functions ************************************************************************/
  95 /* --------------------------------------------------------------------------------------------- */
  96 
  97 /* if enc is same encoding like on terminal */
  98 static int
  99 str_test_not_convert (const char *enc)
     /* [previous][next][first][last][top][bottom][index][help]  */
 100 {
 101     return g_ascii_strcasecmp (enc, codeset) == 0;
 102 }
 103 
 104 /* --------------------------------------------------------------------------------------------- */
 105 
 106 static estr_t
 107 _str_convert (GIConv coder, const char *string, int size, GString *buffer)
     /* [previous][next][first][last][top][bottom][index][help]  */
 108 {
 109     estr_t state = ESTR_SUCCESS;
 110     gssize left;
 111     gsize bytes_read = 0;
 112     gsize bytes_written = 0;
 113 
 114     errno = 0;  // FIXME: is it really needed?
 115 
 116     if (coder == INVALID_CONV)
 117         return ESTR_FAILURE;
 118 
 119     if (string == NULL || buffer == NULL)
 120         return ESTR_FAILURE;
 121 
 122     /*
 123        if (! used_class.is_valid_string (string))
 124        {
 125        return ESTR_FAILURE;
 126        }
 127      */
 128     if (size < 0)
 129         size = strlen (string);
 130     else
 131     {
 132         left = strlen (string);
 133         if (left < size)
 134             size = left;
 135     }
 136 
 137     left = size;
 138     g_iconv (coder, NULL, NULL, NULL, NULL);
 139 
 140     while (left != 0)
 141     {
 142         gchar *tmp_buff;
 143         GError *mcerror = NULL;
 144 
 145         tmp_buff = g_convert_with_iconv ((const gchar *) string, left, coder, &bytes_read,
 146                                          &bytes_written, &mcerror);
 147         if (mcerror != NULL)
 148         {
 149             int code = mcerror->code;
 150 
 151             g_error_free (mcerror);
 152             mcerror = NULL;
 153 
 154             switch (code)
 155             {
 156             case G_CONVERT_ERROR_NO_CONVERSION:
 157                 // Conversion between the requested character sets is not supported.
 158                 g_free (tmp_buff);
 159                 mc_g_string_append_c_len (buffer, '?', strlen (string));
 160                 return ESTR_FAILURE;
 161 
 162             case G_CONVERT_ERROR_ILLEGAL_SEQUENCE:
 163                 // Invalid byte sequence in conversion input.
 164                 if ((tmp_buff == NULL) && (bytes_read != 0))
 165                     // recode valid byte sequence
 166                     tmp_buff = g_convert_with_iconv ((const gchar *) string, bytes_read, coder,
 167                                                      NULL, NULL, NULL);
 168 
 169                 if (tmp_buff != NULL)
 170                 {
 171                     g_string_append (buffer, tmp_buff);
 172                     g_free (tmp_buff);
 173                 }
 174 
 175                 if ((int) bytes_read >= left)
 176                     return ESTR_PROBLEM;
 177 
 178                 string += bytes_read + 1;
 179                 size -= (bytes_read + 1);
 180                 left -= (bytes_read + 1);
 181                 g_string_append_c (buffer, *(string - 1));
 182                 state = ESTR_PROBLEM;
 183                 break;
 184 
 185             case G_CONVERT_ERROR_PARTIAL_INPUT:
 186                 // Partial character sequence at end of input.
 187                 g_string_append (buffer, tmp_buff);
 188                 g_free (tmp_buff);
 189                 if ((int) bytes_read < left)
 190                     mc_g_string_append_c_len (buffer, '?', left - bytes_read);
 191                 return ESTR_PROBLEM;
 192 
 193             case G_CONVERT_ERROR_BAD_URI:            // Don't know how handle this error :(
 194             case G_CONVERT_ERROR_NOT_ABSOLUTE_PATH:  // Don't know how handle this error :(
 195             case G_CONVERT_ERROR_FAILED:             // Conversion failed for some reason.
 196             default:
 197                 g_free (tmp_buff);
 198                 return ESTR_FAILURE;
 199             }
 200         }
 201         else if (tmp_buff == NULL)
 202         {
 203             g_string_append (buffer, string);
 204             return ESTR_PROBLEM;
 205         }
 206         else if (*tmp_buff == '\0')
 207         {
 208             g_free (tmp_buff);
 209             g_string_append (buffer, string);
 210             return state;
 211         }
 212         else
 213         {
 214             g_string_append (buffer, tmp_buff);
 215             g_free (tmp_buff);
 216             string += bytes_read;
 217             left -= bytes_read;
 218         }
 219     }
 220 
 221     return state;
 222 }
 223 
 224 /* --------------------------------------------------------------------------------------------- */
 225 
 226 static int
 227 str_test_encoding_class (const char *encoding, const char *const *table)
     /* [previous][next][first][last][top][bottom][index][help]  */
 228 {
 229     int result = 0;
 230 
 231     if (encoding != NULL)
 232     {
 233         int t;
 234 
 235         for (t = 0; table[t] != NULL; t++)
 236             if (g_ascii_strncasecmp (encoding, table[t], strlen (table[t])) == 0)
 237                 result++;
 238     }
 239 
 240     return result;
 241 }
 242 
 243 /* --------------------------------------------------------------------------------------------- */
 244 
 245 static void
 246 str_choose_str_functions (void)
     /* [previous][next][first][last][top][bottom][index][help]  */
 247 {
 248     if (str_test_encoding_class (codeset, str_utf8_encodings))
 249         used_class = str_utf8_init ();
 250     else if (str_test_encoding_class (codeset, str_8bit_encodings))
 251         used_class = str_8bit_init ();
 252     else
 253         used_class = str_ascii_init ();
 254 }
 255 
 256 /* --------------------------------------------------------------------------------------------- */
 257 /*** public functions ****************************************************************************/
 258 /* --------------------------------------------------------------------------------------------- */
 259 
 260 GIConv
 261 str_crt_conv_to (const char *to_enc)
     /* [previous][next][first][last][top][bottom][index][help]  */
 262 {
 263     return (!str_test_not_convert (to_enc)) ? g_iconv_open (to_enc, codeset) : str_cnv_not_convert;
 264 }
 265 
 266 /* --------------------------------------------------------------------------------------------- */
 267 
 268 GIConv
 269 str_crt_conv_from (const char *from_enc)
     /* [previous][next][first][last][top][bottom][index][help]  */
 270 {
 271     return (!str_test_not_convert (from_enc)) ? g_iconv_open (codeset, from_enc)
 272                                               : str_cnv_not_convert;
 273 }
 274 
 275 /* --------------------------------------------------------------------------------------------- */
 276 
 277 void
 278 str_close_conv (GIConv conv)
     /* [previous][next][first][last][top][bottom][index][help]  */
 279 {
 280     if (conv != INVALID_CONV && conv != str_cnv_not_convert)
 281         g_iconv_close (conv);
 282 }
 283 
 284 /* --------------------------------------------------------------------------------------------- */
 285 
 286 estr_t
 287 str_convert (GIConv coder, const char *string, GString *buffer)
     /* [previous][next][first][last][top][bottom][index][help]  */
 288 {
 289     return _str_convert (coder, string, -1, buffer);
 290 }
 291 
 292 /* --------------------------------------------------------------------------------------------- */
 293 
 294 estr_t
 295 str_nconvert (GIConv coder, const char *string, int size, GString *buffer)
     /* [previous][next][first][last][top][bottom][index][help]  */
 296 {
 297     return _str_convert (coder, string, size, buffer);
 298 }
 299 
 300 /* --------------------------------------------------------------------------------------------- */
 301 
 302 gchar *
 303 str_conv_gerror_message (GError *mcerror, const char *def_msg)
     /* [previous][next][first][last][top][bottom][index][help]  */
 304 {
 305     return used_class.conv_gerror_message (mcerror, def_msg);
 306 }
 307 
 308 /* --------------------------------------------------------------------------------------------- */
 309 
 310 estr_t
 311 str_vfs_convert_from (GIConv coder, const char *string, GString *buffer)
     /* [previous][next][first][last][top][bottom][index][help]  */
 312 {
 313     estr_t result = ESTR_SUCCESS;
 314 
 315     if (coder == str_cnv_not_convert)
 316         g_string_append (buffer, string != NULL ? string : "");
 317     else
 318         result = _str_convert (coder, string, -1, buffer);
 319 
 320     return result;
 321 }
 322 
 323 /* --------------------------------------------------------------------------------------------- */
 324 
 325 estr_t
 326 str_vfs_convert_to (GIConv coder, const char *string, int size, GString *buffer)
     /* [previous][next][first][last][top][bottom][index][help]  */
 327 {
 328     return used_class.vfs_convert_to (coder, string, size, buffer);
 329 }
 330 
 331 /* --------------------------------------------------------------------------------------------- */
 332 
 333 void
 334 str_printf (GString *buffer, const char *format, ...)
     /* [previous][next][first][last][top][bottom][index][help]  */
 335 {
 336     va_list ap;
 337     va_start (ap, format);
 338 
 339     g_string_append_vprintf (buffer, format, ap);
 340     va_end (ap);
 341 }
 342 
 343 /* --------------------------------------------------------------------------------------------- */
 344 
 345 void
 346 str_insert_replace_char (GString *buffer)
     /* [previous][next][first][last][top][bottom][index][help]  */
 347 {
 348     used_class.insert_replace_char (buffer);
 349 }
 350 
 351 /* --------------------------------------------------------------------------------------------- */
 352 
 353 estr_t
 354 str_translate_char (GIConv conv, const char *keys, const ssize_t ch_size, char *output,
     /* [previous][next][first][last][top][bottom][index][help]  */
 355                     const size_t out_size)
 356 {
 357     size_t left;
 358     size_t cnv;
 359     size_t osize = out_size;
 360 
 361     g_iconv (conv, NULL, NULL, NULL, NULL);
 362 
 363     left = ch_size < 0 ? strlen (keys) : (size_t) ch_size;
 364 
 365     cnv = g_iconv (conv, (gchar **) &keys, &left, &output, &osize);
 366     if (cnv == (size_t) (-1))
 367         return (errno == EINVAL) ? ESTR_PROBLEM : ESTR_FAILURE;
 368 
 369     output[0] = '\0';
 370     return ESTR_SUCCESS;
 371 }
 372 
 373 /* --------------------------------------------------------------------------------------------- */
 374 
 375 const char *
 376 str_detect_termencoding (void)
     /* [previous][next][first][last][top][bottom][index][help]  */
 377 {
 378     if (term_encoding == NULL)
 379     {
 380         /* On Linux, nl_langinfo (CODESET) returns upper case UTF-8 whether the LANG is set
 381            to utf-8 or UTF-8.
 382            On Mac OS X, it returns the same case as the LANG input.
 383            So let transform result of nl_langinfo (CODESET) to upper case  unconditionally. */
 384         term_encoding = g_ascii_strup (nl_langinfo (CODESET), -1);
 385     }
 386 
 387     return term_encoding;
 388 }
 389 
 390 /* --------------------------------------------------------------------------------------------- */
 391 
 392 gboolean
 393 str_isutf8 (const char *codeset_name)
     /* [previous][next][first][last][top][bottom][index][help]  */
 394 {
 395     return (str_test_encoding_class (codeset_name, str_utf8_encodings) != 0);
 396 }
 397 
 398 /* --------------------------------------------------------------------------------------------- */
 399 
 400 void
 401 str_init_strings (const char *termenc)
     /* [previous][next][first][last][top][bottom][index][help]  */
 402 {
 403     codeset = termenc != NULL ? g_ascii_strup (termenc, -1) : g_strdup (str_detect_termencoding ());
 404 
 405     str_cnv_not_convert = g_iconv_open (codeset, codeset);
 406     if (str_cnv_not_convert == INVALID_CONV)
 407     {
 408         if (termenc != NULL)
 409         {
 410             g_free (codeset);
 411             codeset = g_strdup (str_detect_termencoding ());
 412             str_cnv_not_convert = g_iconv_open (codeset, codeset);
 413         }
 414 
 415         if (str_cnv_not_convert == INVALID_CONV)
 416         {
 417             g_free (codeset);
 418             codeset = g_strdup (DEFAULT_CHARSET);
 419             str_cnv_not_convert = g_iconv_open (codeset, codeset);
 420         }
 421     }
 422 
 423     str_cnv_to_term = str_cnv_not_convert;
 424     str_cnv_from_term = str_cnv_not_convert;
 425 
 426     str_choose_str_functions ();
 427 }
 428 
 429 /* --------------------------------------------------------------------------------------------- */
 430 
 431 void
 432 str_uninit_strings (void)
     /* [previous][next][first][last][top][bottom][index][help]  */
 433 {
 434     if (str_cnv_not_convert != INVALID_CONV)
 435         g_iconv_close (str_cnv_not_convert);
 436     // NULL-ize pointers to avoid double free in unit tests
 437     MC_PTR_FREE (term_encoding);
 438     MC_PTR_FREE (codeset);
 439 }
 440 
 441 /* --------------------------------------------------------------------------------------------- */
 442 
 443 const char *
 444 str_term_form (const char *text)
     /* [previous][next][first][last][top][bottom][index][help]  */
 445 {
 446     return used_class.term_form (text);
 447 }
 448 
 449 /* --------------------------------------------------------------------------------------------- */
 450 
 451 const char *
 452 str_fit_to_term (const char *text, int width, align_crt_t just_mode)
     /* [previous][next][first][last][top][bottom][index][help]  */
 453 {
 454     return used_class.fit_to_term (text, width, just_mode);
 455 }
 456 
 457 /* --------------------------------------------------------------------------------------------- */
 458 
 459 const char *
 460 str_term_trim (const char *text, const ssize_t width)
     /* [previous][next][first][last][top][bottom][index][help]  */
 461 {
 462     return used_class.term_trim (text, width);
 463 }
 464 
 465 /* --------------------------------------------------------------------------------------------- */
 466 
 467 const char *
 468 str_term_substring (const char *text, int start, int width)
     /* [previous][next][first][last][top][bottom][index][help]  */
 469 {
 470     return used_class.term_substring (text, start, width);
 471 }
 472 
 473 /* --------------------------------------------------------------------------------------------- */
 474 
 475 char *
 476 str_get_next_char (char *text)
     /* [previous][next][first][last][top][bottom][index][help]  */
 477 {
 478 
 479     used_class.cnext_char ((const char **) &text);
 480     return text;
 481 }
 482 
 483 /* --------------------------------------------------------------------------------------------- */
 484 
 485 const char *
 486 str_cget_next_char (const char *text)
     /* [previous][next][first][last][top][bottom][index][help]  */
 487 {
 488     used_class.cnext_char (&text);
 489     return text;
 490 }
 491 
 492 /* --------------------------------------------------------------------------------------------- */
 493 
 494 void
 495 str_next_char (char **text)
     /* [previous][next][first][last][top][bottom][index][help]  */
 496 {
 497     used_class.cnext_char ((const char **) text);
 498 }
 499 
 500 /* --------------------------------------------------------------------------------------------- */
 501 
 502 void
 503 str_cnext_char (const char **text)
     /* [previous][next][first][last][top][bottom][index][help]  */
 504 {
 505     used_class.cnext_char (text);
 506 }
 507 
 508 /* --------------------------------------------------------------------------------------------- */
 509 
 510 char *
 511 str_get_prev_char (char *text)
     /* [previous][next][first][last][top][bottom][index][help]  */
 512 {
 513     used_class.cprev_char ((const char **) &text);
 514     return text;
 515 }
 516 
 517 /* --------------------------------------------------------------------------------------------- */
 518 
 519 const char *
 520 str_cget_prev_char (const char *text)
     /* [previous][next][first][last][top][bottom][index][help]  */
 521 {
 522     used_class.cprev_char (&text);
 523     return text;
 524 }
 525 
 526 /* --------------------------------------------------------------------------------------------- */
 527 
 528 void
 529 str_prev_char (char **text)
     /* [previous][next][first][last][top][bottom][index][help]  */
 530 {
 531     used_class.cprev_char ((const char **) text);
 532 }
 533 
 534 /* --------------------------------------------------------------------------------------------- */
 535 
 536 void
 537 str_cprev_char (const char **text)
     /* [previous][next][first][last][top][bottom][index][help]  */
 538 {
 539     used_class.cprev_char (text);
 540 }
 541 
 542 /* --------------------------------------------------------------------------------------------- */
 543 
 544 char *
 545 str_get_next_char_safe (char *text)
     /* [previous][next][first][last][top][bottom][index][help]  */
 546 {
 547     used_class.cnext_char_safe ((const char **) &text);
 548     return text;
 549 }
 550 
 551 /* --------------------------------------------------------------------------------------------- */
 552 
 553 const char *
 554 str_cget_next_char_safe (const char *text)
     /* [previous][next][first][last][top][bottom][index][help]  */
 555 {
 556     used_class.cnext_char_safe (&text);
 557     return text;
 558 }
 559 
 560 /* --------------------------------------------------------------------------------------------- */
 561 
 562 void
 563 str_next_char_safe (char **text)
     /* [previous][next][first][last][top][bottom][index][help]  */
 564 {
 565     used_class.cnext_char_safe ((const char **) text);
 566 }
 567 
 568 /* --------------------------------------------------------------------------------------------- */
 569 
 570 void
 571 str_cnext_char_safe (const char **text)
     /* [previous][next][first][last][top][bottom][index][help]  */
 572 {
 573     used_class.cnext_char_safe (text);
 574 }
 575 
 576 /* --------------------------------------------------------------------------------------------- */
 577 
 578 char *
 579 str_get_prev_char_safe (char *text)
     /* [previous][next][first][last][top][bottom][index][help]  */
 580 {
 581     used_class.cprev_char_safe ((const char **) &text);
 582     return text;
 583 }
 584 
 585 /* --------------------------------------------------------------------------------------------- */
 586 
 587 const char *
 588 str_cget_prev_char_safe (const char *text)
     /* [previous][next][first][last][top][bottom][index][help]  */
 589 {
 590     used_class.cprev_char_safe (&text);
 591     return text;
 592 }
 593 
 594 /* --------------------------------------------------------------------------------------------- */
 595 
 596 void
 597 str_prev_char_safe (char **text)
     /* [previous][next][first][last][top][bottom][index][help]  */
 598 {
 599     used_class.cprev_char_safe ((const char **) text);
 600 }
 601 
 602 /* --------------------------------------------------------------------------------------------- */
 603 
 604 void
 605 str_cprev_char_safe (const char **text)
     /* [previous][next][first][last][top][bottom][index][help]  */
 606 {
 607     used_class.cprev_char_safe (text);
 608 }
 609 
 610 /* --------------------------------------------------------------------------------------------- */
 611 
 612 int
 613 str_next_noncomb_char (char **text)
     /* [previous][next][first][last][top][bottom][index][help]  */
 614 {
 615     return used_class.cnext_noncomb_char ((const char **) text);
 616 }
 617 
 618 /* --------------------------------------------------------------------------------------------- */
 619 
 620 int
 621 str_cnext_noncomb_char (const char **text)
     /* [previous][next][first][last][top][bottom][index][help]  */
 622 {
 623     return used_class.cnext_noncomb_char (text);
 624 }
 625 
 626 /* --------------------------------------------------------------------------------------------- */
 627 
 628 int
 629 str_prev_noncomb_char (char **text, const char *begin)
     /* [previous][next][first][last][top][bottom][index][help]  */
 630 {
 631     return used_class.cprev_noncomb_char ((const char **) text, begin);
 632 }
 633 
 634 /* --------------------------------------------------------------------------------------------- */
 635 
 636 int
 637 str_cprev_noncomb_char (const char **text, const char *begin)
     /* [previous][next][first][last][top][bottom][index][help]  */
 638 {
 639     return used_class.cprev_noncomb_char (text, begin);
 640 }
 641 
 642 /* --------------------------------------------------------------------------------------------- */
 643 
 644 int
 645 str_is_valid_char (const char *ch, size_t size)
     /* [previous][next][first][last][top][bottom][index][help]  */
 646 {
 647     return used_class.is_valid_char (ch, size);
 648 }
 649 
 650 /* --------------------------------------------------------------------------------------------- */
 651 
 652 int
 653 str_term_width1 (const char *text)
     /* [previous][next][first][last][top][bottom][index][help]  */
 654 {
 655     // Reduce bitwidth size_t -> int
 656     // str_term_width1() is widely used to caclulate widget coordinates and sizes
 657     // that are of type int (see WRect structure).
 658     // It's very unlikely that used_class.term_width1() will return a value greater than MAX_INT.
 659     return (int) used_class.term_width1 (text);
 660 }
 661 
 662 /* --------------------------------------------------------------------------------------------- */
 663 
 664 int
 665 str_term_width2 (const char *text, const ssize_t width)
     /* [previous][next][first][last][top][bottom][index][help]  */
 666 {
 667     // Reduce bitwidth size_t -> int
 668     // See comment in str_term_width1().
 669     return (int) used_class.term_width2 (text, width);
 670 }
 671 
 672 /* --------------------------------------------------------------------------------------------- */
 673 
 674 int
 675 str_term_char_width (const char *text)
     /* [previous][next][first][last][top][bottom][index][help]  */
 676 {
 677     return used_class.term_char_width (text);
 678 }
 679 
 680 /* --------------------------------------------------------------------------------------------- */
 681 
 682 int
 683 str_offset_to_pos (const char *text, size_t length)
     /* [previous][next][first][last][top][bottom][index][help]  */
 684 {
 685     return used_class.offset_to_pos (text, length);
 686 }
 687 
 688 /* --------------------------------------------------------------------------------------------- */
 689 
 690 int
 691 str_length (const char *text)
     /* [previous][next][first][last][top][bottom][index][help]  */
 692 {
 693     return used_class.length (text);
 694 }
 695 
 696 /* --------------------------------------------------------------------------------------------- */
 697 
 698 int
 699 str_length_char (const char *text)
     /* [previous][next][first][last][top][bottom][index][help]  */
 700 {
 701     return str_cget_next_char_safe (text) - text;
 702 }
 703 
 704 /* --------------------------------------------------------------------------------------------- */
 705 
 706 int
 707 str_length2 (const char *text, int size)
     /* [previous][next][first][last][top][bottom][index][help]  */
 708 {
 709     return used_class.length2 (text, size);
 710 }
 711 
 712 /* --------------------------------------------------------------------------------------------- */
 713 
 714 int
 715 str_length_noncomb (const char *text)
     /* [previous][next][first][last][top][bottom][index][help]  */
 716 {
 717     return used_class.length_noncomb (text);
 718 }
 719 
 720 /* --------------------------------------------------------------------------------------------- */
 721 
 722 int
 723 str_column_to_pos (const char *text, size_t pos)
     /* [previous][next][first][last][top][bottom][index][help]  */
 724 {
 725     return used_class.column_to_pos (text, pos);
 726 }
 727 
 728 /* --------------------------------------------------------------------------------------------- */
 729 
 730 gboolean
 731 str_isspace (const char *ch)
     /* [previous][next][first][last][top][bottom][index][help]  */
 732 {
 733     return used_class.char_isspace (ch);
 734 }
 735 
 736 /* --------------------------------------------------------------------------------------------- */
 737 
 738 gboolean
 739 str_ispunct (const char *ch)
     /* [previous][next][first][last][top][bottom][index][help]  */
 740 {
 741     return used_class.char_ispunct (ch);
 742 }
 743 
 744 /* --------------------------------------------------------------------------------------------- */
 745 
 746 gboolean
 747 str_isalnum (const char *ch)
     /* [previous][next][first][last][top][bottom][index][help]  */
 748 {
 749     return used_class.char_isalnum (ch);
 750 }
 751 
 752 /* --------------------------------------------------------------------------------------------- */
 753 
 754 gboolean
 755 str_isdigit (const char *ch)
     /* [previous][next][first][last][top][bottom][index][help]  */
 756 {
 757     return used_class.char_isdigit (ch);
 758 }
 759 
 760 /* --------------------------------------------------------------------------------------------- */
 761 
 762 gboolean
 763 str_toupper (const char *ch, char **out, size_t *remain)
     /* [previous][next][first][last][top][bottom][index][help]  */
 764 {
 765     return used_class.char_toupper (ch, out, remain);
 766 }
 767 
 768 /* --------------------------------------------------------------------------------------------- */
 769 
 770 gboolean
 771 str_tolower (const char *ch, char **out, size_t *remain)
     /* [previous][next][first][last][top][bottom][index][help]  */
 772 {
 773     return used_class.char_tolower (ch, out, remain);
 774 }
 775 
 776 /* --------------------------------------------------------------------------------------------- */
 777 
 778 gboolean
 779 str_isprint (const char *ch)
     /* [previous][next][first][last][top][bottom][index][help]  */
 780 {
 781     return used_class.char_isprint (ch);
 782 }
 783 
 784 /* --------------------------------------------------------------------------------------------- */
 785 
 786 gboolean
 787 str_iscombiningmark (const char *ch)
     /* [previous][next][first][last][top][bottom][index][help]  */
 788 {
 789     return used_class.char_iscombiningmark (ch);
 790 }
 791 
 792 /* --------------------------------------------------------------------------------------------- */
 793 
 794 const char *
 795 str_trunc (const char *text, const ssize_t width)
     /* [previous][next][first][last][top][bottom][index][help]  */
 796 {
 797     return used_class.trunc (text, width);
 798 }
 799 
 800 /* --------------------------------------------------------------------------------------------- */
 801 
 802 char *
 803 str_create_search_needle (const char *needle, gboolean case_sen)
     /* [previous][next][first][last][top][bottom][index][help]  */
 804 {
 805     return used_class.create_search_needle (needle, case_sen);
 806 }
 807 
 808 /* --------------------------------------------------------------------------------------------- */
 809 
 810 void
 811 str_release_search_needle (char *needle, gboolean case_sen)
     /* [previous][next][first][last][top][bottom][index][help]  */
 812 {
 813     used_class.release_search_needle (needle, case_sen);
 814 }
 815 
 816 /* --------------------------------------------------------------------------------------------- */
 817 
 818 const char *
 819 str_search_first (const char *text, const char *search, gboolean case_sen)
     /* [previous][next][first][last][top][bottom][index][help]  */
 820 {
 821     return used_class.search_first (text, search, case_sen);
 822 }
 823 
 824 /* --------------------------------------------------------------------------------------------- */
 825 
 826 const char *
 827 str_search_last (const char *text, const char *search, gboolean case_sen)
     /* [previous][next][first][last][top][bottom][index][help]  */
 828 {
 829     return used_class.search_last (text, search, case_sen);
 830 }
 831 
 832 /* --------------------------------------------------------------------------------------------- */
 833 
 834 gboolean
 835 str_is_valid_string (const char *text)
     /* [previous][next][first][last][top][bottom][index][help]  */
 836 {
 837     return used_class.is_valid_string (text);
 838 }
 839 
 840 /* --------------------------------------------------------------------------------------------- */
 841 
 842 int
 843 str_compare (const char *t1, const char *t2)
     /* [previous][next][first][last][top][bottom][index][help]  */
 844 {
 845     return used_class.compare (t1, t2);
 846 }
 847 
 848 /* --------------------------------------------------------------------------------------------- */
 849 
 850 int
 851 str_ncompare (const char *t1, const char *t2)
     /* [previous][next][first][last][top][bottom][index][help]  */
 852 {
 853     return used_class.ncompare (t1, t2);
 854 }
 855 
 856 /* --------------------------------------------------------------------------------------------- */
 857 
 858 int
 859 str_casecmp (const char *t1, const char *t2)
     /* [previous][next][first][last][top][bottom][index][help]  */
 860 {
 861     return used_class.casecmp (t1, t2);
 862 }
 863 
 864 /* --------------------------------------------------------------------------------------------- */
 865 
 866 int
 867 str_ncasecmp (const char *t1, const char *t2)
     /* [previous][next][first][last][top][bottom][index][help]  */
 868 {
 869     return used_class.ncasecmp (t1, t2);
 870 }
 871 
 872 /* --------------------------------------------------------------------------------------------- */
 873 
 874 int
 875 str_prefix (const char *text, const char *prefix)
     /* [previous][next][first][last][top][bottom][index][help]  */
 876 {
 877     return used_class.prefix (text, prefix);
 878 }
 879 
 880 /* --------------------------------------------------------------------------------------------- */
 881 
 882 int
 883 str_caseprefix (const char *text, const char *prefix)
     /* [previous][next][first][last][top][bottom][index][help]  */
 884 {
 885     return used_class.caseprefix (text, prefix);
 886 }
 887 
 888 /* --------------------------------------------------------------------------------------------- */
 889 
 890 void
 891 str_fix_string (char *text)
     /* [previous][next][first][last][top][bottom][index][help]  */
 892 {
 893     used_class.fix_string (text);
 894 }
 895 
 896 /* --------------------------------------------------------------------------------------------- */
 897 
 898 char *
 899 str_create_key (const char *text, gboolean case_sen)
     /* [previous][next][first][last][top][bottom][index][help]  */
 900 {
 901     return used_class.create_key (text, case_sen);
 902 }
 903 
 904 /* --------------------------------------------------------------------------------------------- */
 905 
 906 char *
 907 str_create_key_for_filename (const char *text, gboolean case_sen)
     /* [previous][next][first][last][top][bottom][index][help]  */
 908 {
 909     return used_class.create_key_for_filename (text, case_sen);
 910 }
 911 
 912 /* --------------------------------------------------------------------------------------------- */
 913 
 914 int
 915 str_key_collate (const char *t1, const char *t2, gboolean case_sen)
     /* [previous][next][first][last][top][bottom][index][help]  */
 916 {
 917     return used_class.key_collate (t1, t2, case_sen);
 918 }
 919 
 920 /* --------------------------------------------------------------------------------------------- */
 921 
 922 void
 923 str_release_key (char *key, gboolean case_sen)
     /* [previous][next][first][last][top][bottom][index][help]  */
 924 {
 925     used_class.release_key (key, case_sen);
 926 }
 927 
 928 /* --------------------------------------------------------------------------------------------- */
 929 
 930 void
 931 str_msg_term_size (const char *text, int *lines, int *columns)
     /* [previous][next][first][last][top][bottom][index][help]  */
 932 {
 933     char *p, *tmp;
 934     char *q;
 935     char c = '\0';
 936 
 937     *lines = 1;
 938     *columns = 0;
 939 
 940     tmp = g_strdup (text);
 941     p = tmp;
 942 
 943     while (TRUE)
 944     {
 945         int width;
 946 
 947         q = strchr (p, '\n');
 948         if (q != NULL)
 949         {
 950             c = q[0];
 951             q[0] = '\0';
 952         }
 953 
 954         width = str_term_width1 (p);
 955         if (width > *columns)
 956             *columns = width;
 957 
 958         if (q == NULL)
 959             break;
 960 
 961         q[0] = c;
 962         p = q + 1;
 963         (*lines)++;
 964     }
 965 
 966     g_free (tmp);
 967 }
 968 
 969 /* --------------------------------------------------------------------------------------------- */
 970 
 971 char *
 972 strrstr_skip_count (const char *haystack, const char *needle, size_t skip_count)
     /* [previous][next][first][last][top][bottom][index][help]  */
 973 {
 974     char *semi;
 975     ssize_t len;
 976 
 977     len = strlen (haystack);
 978 
 979     do
 980     {
 981         semi = g_strrstr_len (haystack, len, needle);
 982         if (semi == NULL)
 983             return NULL;
 984         len = semi - haystack - 1;
 985     }
 986     while (skip_count-- != 0);
 987 
 988     return semi;
 989 }
 990 
 991 /* --------------------------------------------------------------------------------------------- */
 992 /* Interpret string as a non-negative decimal integer, optionally multiplied by various values.
 993  *
 994  * @param str input value
 995  * @param invalid set to TRUE if "str" does not represent a number in this format
 996  *
 997  * @return non-negative integer representation of "str", 0 in case of error.
 998  */
 999 
1000 uintmax_t
1001 parse_integer (const char *str, gboolean *invalid)
     /* [previous][next][first][last][top][bottom][index][help]  */
1002 {
1003     uintmax_t n;
1004     char *suffix;
1005     strtol_error_t e;
1006 
1007     e = xstrtoumax (str, &suffix, 10, &n, "bcEGkKMPTwYZ0");
1008     if (e == LONGINT_INVALID_SUFFIX_CHAR && *suffix == 'x')
1009     {
1010         uintmax_t multiplier;
1011 
1012         multiplier = parse_integer (suffix + 1, invalid);
1013         if (multiplier != 0 && n * multiplier / multiplier != n)
1014         {
1015             *invalid = TRUE;
1016             return 0;
1017         }
1018 
1019         n *= multiplier;
1020     }
1021     else if (e != LONGINT_OK)
1022     {
1023         *invalid = TRUE;
1024         n = 0;
1025     }
1026 
1027     return n;
1028 }
1029 
1030 /* --------------------------------------------------------------------------------------------- */
1031 
1032 /**
1033  * Strips single right (trailing) EOL (\\r, \\n, or \\r\\n), NULL-safe
1034  */
1035 void
1036 str_rstrip_eol (char *s)
     /* [previous][next][first][last][top][bottom][index][help]  */
1037 {
1038     if (s == NULL || *s == '\0')
1039         return;
1040 
1041     const size_t len = strlen (s);
1042     if (len >= 2 && s[len - 2] == '\r' && s[len - 1] == '\n')  // removes \r\n
1043         s[len - 2] = '\0';
1044     else if (len >= 1 && (s[len - 1] == '\n' || s[len - 1] == '\r'))  // removes \n or \r
1045         s[len - 1] = '\0';
1046 }
1047 
1048 /* --------------------------------------------------------------------------------------------- */

/* [previous][next][first][last][top][bottom][index][help]  */