< Summary

Information
Class: Ice.UtilInternal.StringUtil.OrdinalStringComparerImpl
Assembly: Ice
File(s): /home/runner/work/ice/ice/csharp/src/Ice/UtilInternal/StringUtil.cs
Tag: 71_18251537082
Line coverage
0%
Covered lines: 0
Uncovered lines: 1
Coverable lines: 1
Total lines: 712
Line coverage: 0%
Branch coverage
N/A
Covered branches: 0
Total branches: 0
Branch coverage: N/A
Method coverage
0%
Covered methods: 0
Total methods: 1
Method coverage: 0%

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
Compare(...)100%210%

File(s)

/home/runner/work/ice/ice/csharp/src/Ice/UtilInternal/StringUtil.cs

#LineLine coverage
 1// Copyright (c) ZeroC, Inc.
 2
 3using System.Diagnostics;
 4using System.Text;
 5
 6namespace Ice.UtilInternal;
 7
 8public sealed class StringUtil
 9{
 10    //
 11    // Return the index of the first character in str to
 12    // appear in match, starting from 0. Returns -1 if none is
 13    // found.
 14    //
 15    public static int findFirstOf(string str, string match) => findFirstOf(str, match, 0);
 16
 17    //
 18    // Return the index of the first character in str to
 19    // appear in match, starting from start. Returns -1 if none is
 20    // found.
 21    //
 22    public static int findFirstOf(string str, string match, int start)
 23    {
 24        int len = str.Length;
 25        for (int i = start; i < len; i++)
 26        {
 27            char ch = str[i];
 28            if (match.Contains(ch, StringComparison.Ordinal))
 29            {
 30                return i;
 31            }
 32        }
 33
 34        return -1;
 35    }
 36
 37    //
 38    // Return the index of the first character in str which does
 39    // not appear in match, starting from 0. Returns -1 if none is
 40    // found.
 41    //
 42    public static int findFirstNotOf(string str, string match) => findFirstNotOf(str, match, 0);
 43
 44    //
 45    // Return the index of the first character in str which does
 46    // not appear in match, starting from start. Returns -1 if none is
 47    // found.
 48    //
 49    public static int findFirstNotOf(string str, string match, int start)
 50    {
 51        int len = str.Length;
 52        for (int i = start; i < len; i++)
 53        {
 54            char ch = str[i];
 55            if (!match.Contains(ch, StringComparison.Ordinal))
 56            {
 57                return i;
 58            }
 59        }
 60
 61        return -1;
 62    }
 63
 64    private static void
 65    encodeChar(char c, StringBuilder sb, string special, Ice.ToStringMode toStringMode)
 66    {
 67        switch (c)
 68        {
 69            case '\\':
 70            {
 71                sb.Append("\\\\");
 72                break;
 73            }
 74            case '\'':
 75            {
 76                sb.Append("\\'");
 77                break;
 78            }
 79            case '"':
 80            {
 81                sb.Append("\\\"");
 82                break;
 83            }
 84            case '\a':
 85            {
 86                if (toStringMode == Ice.ToStringMode.Compat)
 87                {
 88                    // Octal escape for compatibility with 3.6 and earlier
 89                    sb.Append("\\007");
 90                }
 91                else
 92                {
 93                    sb.Append("\\a");
 94                }
 95                break;
 96            }
 97            case '\b':
 98            {
 99                sb.Append("\\b");
 100                break;
 101            }
 102            case '\f':
 103            {
 104                sb.Append("\\f");
 105                break;
 106            }
 107            case '\n':
 108            {
 109                sb.Append("\\n");
 110                break;
 111            }
 112            case '\r':
 113            {
 114                sb.Append("\\r");
 115                break;
 116            }
 117            case '\t':
 118            {
 119                sb.Append("\\t");
 120                break;
 121            }
 122            case '\v':
 123            {
 124                if (toStringMode == Ice.ToStringMode.Compat)
 125                {
 126                    // Octal escape for compatibility with 3.6 and earlier
 127                    sb.Append("\\013");
 128                }
 129                else
 130                {
 131                    sb.Append("\\v");
 132                }
 133                break;
 134            }
 135            default:
 136            {
 137                if (special != null && special.Contains(c, StringComparison.Ordinal))
 138                {
 139                    sb.Append('\\');
 140                    sb.Append(c);
 141                }
 142                else
 143                {
 144                    int i = (int)c;
 145                    if (i < 32 || i > 126)
 146                    {
 147                        if (toStringMode == Ice.ToStringMode.Compat)
 148                        {
 149                            //
 150                            // When ToStringMode=Compat, c is a UTF-8 byte
 151                            //
 152                            Debug.Assert(i < 256);
 153
 154                            sb.Append('\\');
 155                            string octal = System.Convert.ToString(i, 8);
 156                            //
 157                            // Add leading zeroes so that we avoid problems during
 158                            // decoding. For example, consider the encoded string
 159                            // \0013 (i.e., a character with value 1 followed by
 160                            // the character '3'). If the leading zeroes were omitted,
 161                            // the result would be incorrectly interpreted by the
 162                            // decoder as a single character with value 11.
 163                            //
 164                            for (int j = octal.Length; j < 3; j++)
 165                            {
 166                                sb.Append('0');
 167                            }
 168                            sb.Append(octal);
 169                        }
 170                        else if (i < 32 || i == 127 || toStringMode == Ice.ToStringMode.ASCII)
 171                        {
 172                            // append \\unnnn
 173                            sb.Append("\\u");
 174                            string hex = System.Convert.ToString(i, 16);
 175                            for (int j = hex.Length; j < 4; j++)
 176                            {
 177                                sb.Append('0');
 178                            }
 179                            sb.Append(hex);
 180                        }
 181                        else
 182                        {
 183                            // keep as is
 184                            sb.Append(c);
 185                        }
 186                    }
 187                    else
 188                    {
 189                        // printable ASCII character
 190                        sb.Append(c);
 191                    }
 192                }
 193                break;
 194            }
 195        }
 196    }
 197
 198    //
 199    // Add escape sequences (such as "\n", or "\007") to the input string
 200    //
 201    public static string escapeString(string s, string special, Ice.ToStringMode toStringMode)
 202    {
 203        if (special != null)
 204        {
 205            for (int i = 0; i < special.Length; ++i)
 206            {
 207                if (special[i] < 32 || special[i] > 126)
 208                {
 209                    throw new ArgumentException("special characters must be in ASCII range 32-126", nameof(special));
 210                }
 211            }
 212        }
 213
 214        if (toStringMode == Ice.ToStringMode.Compat)
 215        {
 216            // Encode UTF-8 bytes
 217
 218            var utf8 = new UTF8Encoding();
 219            byte[] bytes = utf8.GetBytes(s);
 220
 221            var result = new StringBuilder(bytes.Length);
 222            for (int i = 0; i < bytes.Length; i++)
 223            {
 224                encodeChar((char)bytes[i], result, special, toStringMode);
 225            }
 226
 227            return result.ToString();
 228        }
 229        else
 230        {
 231            var result = new StringBuilder(s.Length);
 232
 233            for (int i = 0; i < s.Length; i++)
 234            {
 235                char c = s[i];
 236                if (toStringMode == Ice.ToStringMode.Unicode || !char.IsSurrogate(c))
 237                {
 238                    encodeChar(c, result, special, toStringMode);
 239                }
 240                else
 241                {
 242                    Debug.Assert(toStringMode == Ice.ToStringMode.ASCII && char.IsSurrogate(c));
 243                    if (i + 1 == s.Length)
 244                    {
 245                        throw new System.ArgumentException("High surrogate without low surrogate");
 246                    }
 247                    else
 248                    {
 249                        i++;
 250                        int codePoint = char.ConvertToUtf32(c, s[i]);
 251                        // append \Unnnnnnnn
 252                        result.Append("\\U");
 253                        string hex = System.Convert.ToString(codePoint, 16);
 254                        for (int j = hex.Length; j < 8; j++)
 255                        {
 256                            result.Append('0');
 257                        }
 258                        result.Append(hex);
 259                    }
 260                }
 261            }
 262
 263            return result.ToString();
 264        }
 265    }
 266
 267    private static char
 268    checkChar(string s, int pos)
 269    {
 270        char c = s[pos];
 271        if (c < 32 || c == 127)
 272        {
 273            string msg;
 274            if (pos > 0)
 275            {
 276                msg = $"character after `{s[..pos]}'";
 277            }
 278            else
 279            {
 280                msg = "first character";
 281            }
 282            msg += " is not a printable ASCII character (ordinal " + (int)c + ")";
 283            throw new System.ArgumentException(msg);
 284        }
 285        return c;
 286    }
 287
 288    //
 289    // Decode the character or escape sequence starting at start and appends it to result;
 290    // returns the index of the first character following the decoded character
 291    // or escape sequence.
 292    //
 293    private static int
 294    decodeChar(string s, int start, int end, string special, StringBuilder result, UTF8Encoding utf8Encoding)
 295    {
 296        Debug.Assert(start >= 0);
 297        Debug.Assert(start < end);
 298        Debug.Assert(end <= s.Length);
 299
 300        if (s[start] != '\\')
 301        {
 302            result.Append(checkChar(s, start++));
 303        }
 304        else if (start + 1 == end)
 305        {
 306            ++start;
 307            result.Append('\\'); // trailing backslash
 308        }
 309        else
 310        {
 311            char c = s[++start];
 312
 313            switch (c)
 314            {
 315                case '\\':
 316                case '\'':
 317                case '"':
 318                case '?':
 319                {
 320                    ++start;
 321                    result.Append(c);
 322                    break;
 323                }
 324                case 'a':
 325                {
 326                    ++start;
 327                    result.Append('\a');
 328                    break;
 329                }
 330                case 'b':
 331                {
 332                    ++start;
 333                    result.Append('\b');
 334                    break;
 335                }
 336                case 'f':
 337                {
 338                    ++start;
 339                    result.Append('\f');
 340                    break;
 341                }
 342                case 'n':
 343                {
 344                    ++start;
 345                    result.Append('\n');
 346                    break;
 347                }
 348                case 'r':
 349                {
 350                    ++start;
 351                    result.Append('\r');
 352                    break;
 353                }
 354                case 't':
 355                {
 356                    ++start;
 357                    result.Append('\t');
 358                    break;
 359                }
 360                case 'v':
 361                {
 362                    ++start;
 363                    result.Append('\v');
 364                    break;
 365                }
 366                case 'u':
 367                case 'U':
 368                {
 369                    int codePoint = 0;
 370                    bool inBMP = c == 'u';
 371                    int size = inBMP ? 4 : 8;
 372                    ++start;
 373                    while (size > 0 && start < end)
 374                    {
 375                        c = s[start++];
 376                        int charVal;
 377                        if (c >= '0' && c <= '9')
 378                        {
 379                            charVal = c - '0';
 380                        }
 381                        else if (c >= 'a' && c <= 'f')
 382                        {
 383                            charVal = 10 + (c - 'a');
 384                        }
 385                        else if (c >= 'A' && c <= 'F')
 386                        {
 387                            charVal = 10 + (c - 'A');
 388                        }
 389                        else
 390                        {
 391                            break; // while
 392                        }
 393                        codePoint = (codePoint * 16) + charVal;
 394                        --size;
 395                    }
 396                    if (size > 0)
 397                    {
 398                        throw new System.ArgumentException("Invalid universal character name: too few hex digits");
 399                    }
 400                    if (codePoint >= 0xD800 && codePoint <= 0xDFFF)
 401                    {
 402                        throw new System.ArgumentException("A universal character name cannot designate a surrogate");
 403                    }
 404                    if (inBMP || codePoint <= 0xFFFF)
 405                    {
 406                        result.Append((char)codePoint);
 407                    }
 408                    else
 409                    {
 410                        result.Append(char.ConvertFromUtf32(codePoint));
 411                    }
 412                    break;
 413                }
 414
 415                case '0':
 416                case '1':
 417                case '2':
 418                case '3':
 419                case '4':
 420                case '5':
 421                case '6':
 422                case '7':
 423                case 'x':
 424                {
 425                    // UTF-8 byte sequence encoded with octal escapes
 426
 427                    byte[] arr = new byte[end - start];
 428                    int i = 0;
 429                    bool more = true;
 430                    while (more)
 431                    {
 432                        int val = 0;
 433                        if (c == 'x')
 434                        {
 435                            int size = 2;
 436                            ++start;
 437                            while (size > 0 && start < end)
 438                            {
 439                                c = s[start++];
 440                                int charVal;
 441                                if (c >= '0' && c <= '9')
 442                                {
 443                                    charVal = c - '0';
 444                                }
 445                                else if (c >= 'a' && c <= 'f')
 446                                {
 447                                    charVal = 10 + (c - 'a');
 448                                }
 449                                else if (c >= 'A' && c <= 'F')
 450                                {
 451                                    charVal = 10 + (c - 'A');
 452                                }
 453                                else
 454                                {
 455                                    --start; // move back
 456                                    break; // while
 457                                }
 458                                val = (val * 16) + charVal;
 459                                --size;
 460                            }
 461                            if (size == 2)
 462                            {
 463                                throw new System.ArgumentException("Invalid \\x escape sequence: no hex digit");
 464                            }
 465                        }
 466                        else
 467                        {
 468                            for (int j = 0; j < 3 && start < end; ++j)
 469                            {
 470                                int charVal = s[start++] - '0';
 471                                if (charVal < 0 || charVal > 7)
 472                                {
 473                                    --start; // move back
 474                                    Debug.Assert(j != 0); // must be at least one digit
 475                                    break; // for
 476                                }
 477                                val = (val * 8) + charVal;
 478                            }
 479                            if (val > 255)
 480                            {
 481                                string msg =
 482                                    "octal value \\" +
 483                                    System.Convert.ToString(val, 8) +
 484                                    " (" + val + ") is out of range";
 485                                throw new System.ArgumentException(msg);
 486                            }
 487                        }
 488
 489                        arr[i++] = (byte)val;
 490
 491                        more = false;
 492
 493                        if ((start + 1 < end) && s[start] == '\\')
 494                        {
 495                            c = s[start + 1];
 496                            if (c == 'x' || (c >= '0' && c <= '9'))
 497                            {
 498                                start++;
 499                                more = true;
 500                            }
 501                        }
 502                    }
 503
 504                    result.Append(utf8Encoding.GetString(arr, 0, i)); // May raise ArgumentException.
 505                    break;
 506                }
 507                default:
 508                {
 509                    if (string.IsNullOrEmpty(special) || !special.Contains(c, StringComparison.Ordinal))
 510                    {
 511                        result.Append('\\'); // not in special, so we keep the backslash
 512                    }
 513                    result.Append(checkChar(s, start++));
 514                    break;
 515                }
 516            }
 517        }
 518        return start;
 519    }
 520
 521    //
 522    // Remove escape sequences added by escapeString. Throws System.ArgumentException
 523    // for an invalid input string.
 524    //
 525    public static string unescapeString(string s, int start, int end, string special)
 526    {
 527        Debug.Assert(start >= 0 && start <= end && end <= s.Length);
 528
 529        if (special != null)
 530        {
 531            for (int i = 0; i < special.Length; ++i)
 532            {
 533                if (special[i] < 32 || special[i] > 126)
 534                {
 535                    throw new ArgumentException("special characters must be in ASCII range 32-126", nameof(special));
 536                }
 537            }
 538        }
 539
 540        // Optimization for strings without escapes
 541        if (start == end || s.IndexOf('\\', start, end - start) == -1)
 542        {
 543            int p = start;
 544            while (p < end)
 545            {
 546                checkChar(s, p++);
 547            }
 548            return s[start..end];
 549        }
 550        else
 551        {
 552            var sb = new StringBuilder(end - start);
 553            var utf8Encoding = new UTF8Encoding(false, true);
 554            while (start < end)
 555            {
 556                start = decodeChar(s, start, end, special, sb, utf8Encoding);
 557            }
 558            return sb.ToString();
 559        }
 560    }
 561
 562    //
 563    // Split string helper; returns null for unmatched quotes
 564    //
 565    public static string[] splitString(string str, string delim)
 566    {
 567        var l = new List<string>();
 568        char[] arr = new char[str.Length];
 569        int pos = 0;
 570
 571        int n = 0;
 572        char quoteChar = '\0';
 573        while (pos < str.Length)
 574        {
 575            if (quoteChar == '\0' && (str[pos] == '"' || str[pos] == '\''))
 576            {
 577                quoteChar = str[pos++];
 578                continue; // Skip the quote.
 579            }
 580            else if (quoteChar == '\0' && str[pos] == '\\' && pos + 1 < str.Length &&
 581                    (str[pos + 1] == '\'' || str[pos + 1] == '"'))
 582            {
 583                ++pos; // Skip the backslash
 584            }
 585            else if (quoteChar != '\0' && str[pos] == '\\' && pos + 1 < str.Length && str[pos + 1] == quoteChar)
 586            {
 587                ++pos; // Skip the backslash
 588            }
 589            else if (quoteChar != '\0' && str[pos] == quoteChar)
 590            {
 591                ++pos;
 592                quoteChar = '\0';
 593                continue; // Skip the quote.
 594            }
 595            else if (delim.Contains(str[pos], StringComparison.Ordinal))
 596            {
 597                if (quoteChar == '\0')
 598                {
 599                    ++pos;
 600                    if (n > 0)
 601                    {
 602                        l.Add(new string(arr, 0, n));
 603                        n = 0;
 604                    }
 605                    continue;
 606                }
 607            }
 608
 609            if (pos < str.Length)
 610            {
 611                arr[n++] = str[pos++];
 612            }
 613        }
 614
 615        if (n > 0)
 616        {
 617            l.Add(new string(arr, 0, n));
 618        }
 619        if (quoteChar != '\0')
 620        {
 621            return null; // Unmatched quote.
 622        }
 623        return l.ToArray();
 624    }
 625
 626    public static int checkQuote(string s) => checkQuote(s, 0);
 627
 628    //
 629    // If a single or double quotation mark is found at the start position,
 630    // then the position of the matching closing quote is returned. If no
 631    // quotation mark is found at the start position, then 0 is returned.
 632    // If no matching closing quote is found, then -1 is returned.
 633    //
 634    public static int checkQuote(string s, int start)
 635    {
 636        char quoteChar = s[start];
 637        if (quoteChar == '"' || quoteChar == '\'')
 638        {
 639            start++;
 640            int len = s.Length;
 641            int pos;
 642            while (start < len && (pos = s.IndexOf(quoteChar, start)) != -1)
 643            {
 644                if (s[pos - 1] != '\\')
 645                {
 646                    return pos;
 647                }
 648                start = pos + 1;
 649            }
 650            return -1; // Unmatched quote
 651        }
 652        return 0; // Not quoted
 653    }
 654
 655    public static bool match(string s, string pat, bool emptyMatch)
 656    {
 657        Debug.Assert(s.Length > 0);
 658        Debug.Assert(pat.Length > 0);
 659
 660        //
 661        // If pattern does not contain a wildcard just compare strings.
 662        //
 663        int beginIndex = pat.IndexOf('*', StringComparison.Ordinal);
 664        if (beginIndex < 0)
 665        {
 666            return s.Equals(pat, StringComparison.Ordinal);
 667        }
 668
 669        //
 670        // Make sure start of the strings match
 671        //
 672        if (beginIndex > s.Length ||
 673            !s[..beginIndex].Equals(pat[..beginIndex], StringComparison.Ordinal))
 674        {
 675            return false;
 676        }
 677
 678        //
 679        // Make sure there is something present in the middle to match the
 680        // wildcard. If emptyMatch is true, allow a match of "".
 681        //
 682        int endLength = pat.Length - beginIndex - 1;
 683        if (endLength > s.Length)
 684        {
 685            return false;
 686        }
 687        int endIndex = s.Length - endLength;
 688        if (endIndex < beginIndex || (!emptyMatch && endIndex == beginIndex))
 689        {
 690            return false;
 691        }
 692
 693        //
 694        // Make sure end of the strings match
 695        //
 696        if (!s[endIndex..].Equals(
 697               pat.Substring(beginIndex + 1, pat.Length - beginIndex - 1),
 698               StringComparison.Ordinal))
 699        {
 700            return false;
 701        }
 702
 703        return true;
 704    }
 705
 706    private class OrdinalStringComparerImpl : IComparer<string>
 707    {
 0708        public int Compare(string l, string r) => string.CompareOrdinal(l, r);
 709    }
 710
 711    public static IComparer<string> OrdinalStringComparer = new OrdinalStringComparerImpl();
 712}

Methods/Properties

Compare(string, string)