< Summary

Information
Class: Ice.UtilInternal.StringUtil
Assembly: Ice
File(s): /home/runner/work/ice/ice/csharp/src/Ice/UtilInternal/StringUtil.cs
Tag: 71_18251537082
Line coverage
83%
Covered lines: 222
Uncovered lines: 45
Coverable lines: 267
Total lines: 712
Line coverage: 83.1%
Branch coverage
86%
Covered branches: 236
Total branches: 272
Branch coverage: 86.7%
Method coverage
73%
Covered methods: 11
Total methods: 15
Method coverage: 73.3%

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
findFirstOf(...)100%11100%
findFirstOf(...)100%44100%
findFirstNotOf(...)100%210%
findFirstNotOf(...)100%44100%
encodeChar(...)83.33%78.082454.55%
escapeString(...)86.36%22.222292.31%
checkChar(...)83.33%6.07687.5%
decodeChar(...)83.59%165.0912886.87%
unescapeString(...)87.5%16.091692.86%
splitString(...)100%4242100%
checkQuote(...)100%210%
checkQuote(...)90%10.11090%
match(...)75%17.411682.35%
Compare(...)100%210%
.cctor()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    //
 115    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    {
 124        int len = str.Length;
 125        for (int i = start; i < len; i++)
 26        {
 127            char ch = str[i];
 128            if (match.Contains(ch, StringComparison.Ordinal))
 29            {
 130                return i;
 31            }
 32        }
 33
 134        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    //
 042    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    {
 151        int len = str.Length;
 152        for (int i = start; i < len; i++)
 53        {
 154            char ch = str[i];
 155            if (!match.Contains(ch, StringComparison.Ordinal))
 56            {
 157                return i;
 58            }
 59        }
 60
 161        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            {
 171                sb.Append("\\\\");
 172                break;
 73            }
 74            case '\'':
 75            {
 176                sb.Append("\\'");
 177                break;
 78            }
 79            case '"':
 80            {
 081                sb.Append("\\\"");
 082                break;
 83            }
 84            case '\a':
 85            {
 086                if (toStringMode == Ice.ToStringMode.Compat)
 87                {
 88                    // Octal escape for compatibility with 3.6 and earlier
 089                    sb.Append("\\007");
 90                }
 91                else
 92                {
 093                    sb.Append("\\a");
 94                }
 095                break;
 96            }
 97            case '\b':
 98            {
 099                sb.Append("\\b");
 0100                break;
 101            }
 102            case '\f':
 103            {
 0104                sb.Append("\\f");
 0105                break;
 106            }
 107            case '\n':
 108            {
 0109                sb.Append("\\n");
 0110                break;
 111            }
 112            case '\r':
 113            {
 0114                sb.Append("\\r");
 0115                break;
 116            }
 117            case '\t':
 118            {
 0119                sb.Append("\\t");
 0120                break;
 121            }
 122            case '\v':
 123            {
 0124                if (toStringMode == Ice.ToStringMode.Compat)
 125                {
 126                    // Octal escape for compatibility with 3.6 and earlier
 0127                    sb.Append("\\013");
 128                }
 129                else
 130                {
 0131                    sb.Append("\\v");
 132                }
 0133                break;
 134            }
 135            default:
 136            {
 1137                if (special != null && special.Contains(c, StringComparison.Ordinal))
 138                {
 1139                    sb.Append('\\');
 1140                    sb.Append(c);
 141                }
 142                else
 143                {
 1144                    int i = (int)c;
 1145                    if (i < 32 || i > 126)
 146                    {
 1147                        if (toStringMode == Ice.ToStringMode.Compat)
 148                        {
 149                            //
 150                            // When ToStringMode=Compat, c is a UTF-8 byte
 151                            //
 152                            Debug.Assert(i < 256);
 153
 1154                            sb.Append('\\');
 1155                            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                            //
 1164                            for (int j = octal.Length; j < 3; j++)
 165                            {
 1166                                sb.Append('0');
 167                            }
 1168                            sb.Append(octal);
 169                        }
 1170                        else if (i < 32 || i == 127 || toStringMode == Ice.ToStringMode.ASCII)
 171                        {
 172                            // append \\unnnn
 1173                            sb.Append("\\u");
 1174                            string hex = System.Convert.ToString(i, 16);
 1175                            for (int j = hex.Length; j < 4; j++)
 176                            {
 1177                                sb.Append('0');
 178                            }
 1179                            sb.Append(hex);
 180                        }
 181                        else
 182                        {
 183                            // keep as is
 1184                            sb.Append(c);
 185                        }
 186                    }
 187                    else
 188                    {
 189                        // printable ASCII character
 1190                        sb.Append(c);
 191                    }
 192                }
 193                break;
 194            }
 195        }
 1196    }
 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    {
 1203        if (special != null)
 204        {
 1205            for (int i = 0; i < special.Length; ++i)
 206            {
 1207                if (special[i] < 32 || special[i] > 126)
 208                {
 0209                    throw new ArgumentException("special characters must be in ASCII range 32-126", nameof(special));
 210                }
 211            }
 212        }
 213
 1214        if (toStringMode == Ice.ToStringMode.Compat)
 215        {
 216            // Encode UTF-8 bytes
 217
 1218            var utf8 = new UTF8Encoding();
 1219            byte[] bytes = utf8.GetBytes(s);
 220
 1221            var result = new StringBuilder(bytes.Length);
 1222            for (int i = 0; i < bytes.Length; i++)
 223            {
 1224                encodeChar((char)bytes[i], result, special, toStringMode);
 225            }
 226
 1227            return result.ToString();
 228        }
 229        else
 230        {
 1231            var result = new StringBuilder(s.Length);
 232
 1233            for (int i = 0; i < s.Length; i++)
 234            {
 1235                char c = s[i];
 1236                if (toStringMode == Ice.ToStringMode.Unicode || !char.IsSurrogate(c))
 237                {
 1238                    encodeChar(c, result, special, toStringMode);
 239                }
 240                else
 241                {
 242                    Debug.Assert(toStringMode == Ice.ToStringMode.ASCII && char.IsSurrogate(c));
 1243                    if (i + 1 == s.Length)
 244                    {
 0245                        throw new System.ArgumentException("High surrogate without low surrogate");
 246                    }
 247                    else
 248                    {
 1249                        i++;
 1250                        int codePoint = char.ConvertToUtf32(c, s[i]);
 251                        // append \Unnnnnnnn
 1252                        result.Append("\\U");
 1253                        string hex = System.Convert.ToString(codePoint, 16);
 1254                        for (int j = hex.Length; j < 8; j++)
 255                        {
 1256                            result.Append('0');
 257                        }
 1258                        result.Append(hex);
 259                    }
 260                }
 261            }
 262
 1263            return result.ToString();
 264        }
 265    }
 266
 267    private static char
 268    checkChar(string s, int pos)
 269    {
 1270        char c = s[pos];
 1271        if (c < 32 || c == 127)
 272        {
 273            string msg;
 1274            if (pos > 0)
 275            {
 1276                msg = $"character after `{s[..pos]}'";
 277            }
 278            else
 279            {
 0280                msg = "first character";
 281            }
 1282            msg += " is not a printable ASCII character (ordinal " + (int)c + ")";
 1283            throw new System.ArgumentException(msg);
 284        }
 1285        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
 1300        if (s[start] != '\\')
 301        {
 1302            result.Append(checkChar(s, start++));
 303        }
 1304        else if (start + 1 == end)
 305        {
 1306            ++start;
 1307            result.Append('\\'); // trailing backslash
 308        }
 309        else
 310        {
 1311            char c = s[++start];
 312
 313            switch (c)
 314            {
 315                case '\\':
 316                case '\'':
 317                case '"':
 318                case '?':
 319                {
 1320                    ++start;
 1321                    result.Append(c);
 1322                    break;
 323                }
 324                case 'a':
 325                {
 0326                    ++start;
 0327                    result.Append('\a');
 0328                    break;
 329                }
 330                case 'b':
 331                {
 1332                    ++start;
 1333                    result.Append('\b');
 1334                    break;
 335                }
 336                case 'f':
 337                {
 1338                    ++start;
 1339                    result.Append('\f');
 1340                    break;
 341                }
 342                case 'n':
 343                {
 1344                    ++start;
 1345                    result.Append('\n');
 1346                    break;
 347                }
 348                case 'r':
 349                {
 1350                    ++start;
 1351                    result.Append('\r');
 1352                    break;
 353                }
 354                case 't':
 355                {
 1356                    ++start;
 1357                    result.Append('\t');
 1358                    break;
 359                }
 360                case 'v':
 361                {
 0362                    ++start;
 0363                    result.Append('\v');
 0364                    break;
 365                }
 366                case 'u':
 367                case 'U':
 368                {
 1369                    int codePoint = 0;
 1370                    bool inBMP = c == 'u';
 1371                    int size = inBMP ? 4 : 8;
 1372                    ++start;
 1373                    while (size > 0 && start < end)
 374                    {
 1375                        c = s[start++];
 376                        int charVal;
 1377                        if (c >= '0' && c <= '9')
 378                        {
 1379                            charVal = c - '0';
 380                        }
 1381                        else if (c >= 'a' && c <= 'f')
 382                        {
 1383                            charVal = 10 + (c - 'a');
 384                        }
 0385                        else if (c >= 'A' && c <= 'F')
 386                        {
 0387                            charVal = 10 + (c - 'A');
 388                        }
 389                        else
 390                        {
 391                            break; // while
 392                        }
 1393                        codePoint = (codePoint * 16) + charVal;
 1394                        --size;
 395                    }
 1396                    if (size > 0)
 397                    {
 0398                        throw new System.ArgumentException("Invalid universal character name: too few hex digits");
 399                    }
 1400                    if (codePoint >= 0xD800 && codePoint <= 0xDFFF)
 401                    {
 1402                        throw new System.ArgumentException("A universal character name cannot designate a surrogate");
 403                    }
 1404                    if (inBMP || codePoint <= 0xFFFF)
 405                    {
 1406                        result.Append((char)codePoint);
 407                    }
 408                    else
 409                    {
 1410                        result.Append(char.ConvertFromUtf32(codePoint));
 411                    }
 1412                    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
 1427                    byte[] arr = new byte[end - start];
 1428                    int i = 0;
 1429                    bool more = true;
 1430                    while (more)
 431                    {
 1432                        int val = 0;
 1433                        if (c == 'x')
 434                        {
 1435                            int size = 2;
 1436                            ++start;
 1437                            while (size > 0 && start < end)
 438                            {
 1439                                c = s[start++];
 440                                int charVal;
 1441                                if (c >= '0' && c <= '9')
 442                                {
 1443                                    charVal = c - '0';
 444                                }
 1445                                else if (c >= 'a' && c <= 'f')
 446                                {
 0447                                    charVal = 10 + (c - 'a');
 448                                }
 1449                                else if (c >= 'A' && c <= 'F')
 450                                {
 0451                                    charVal = 10 + (c - 'A');
 452                                }
 453                                else
 454                                {
 1455                                    --start; // move back
 1456                                    break; // while
 457                                }
 1458                                val = (val * 16) + charVal;
 1459                                --size;
 460                            }
 1461                            if (size == 2)
 462                            {
 0463                                throw new System.ArgumentException("Invalid \\x escape sequence: no hex digit");
 464                            }
 465                        }
 466                        else
 467                        {
 1468                            for (int j = 0; j < 3 && start < end; ++j)
 469                            {
 1470                                int charVal = s[start++] - '0';
 1471                                if (charVal < 0 || charVal > 7)
 472                                {
 1473                                    --start; // move back
 474                                    Debug.Assert(j != 0); // must be at least one digit
 1475                                    break; // for
 476                                }
 1477                                val = (val * 8) + charVal;
 478                            }
 1479                            if (val > 255)
 480                            {
 1481                                string msg =
 1482                                    "octal value \\" +
 1483                                    System.Convert.ToString(val, 8) +
 1484                                    " (" + val + ") is out of range";
 1485                                throw new System.ArgumentException(msg);
 486                            }
 487                        }
 488
 1489                        arr[i++] = (byte)val;
 490
 1491                        more = false;
 492
 1493                        if ((start + 1 < end) && s[start] == '\\')
 494                        {
 1495                            c = s[start + 1];
 1496                            if (c == 'x' || (c >= '0' && c <= '9'))
 497                            {
 1498                                start++;
 1499                                more = true;
 500                            }
 501                        }
 502                    }
 503
 1504                    result.Append(utf8Encoding.GetString(arr, 0, i)); // May raise ArgumentException.
 1505                    break;
 506                }
 507                default:
 508                {
 1509                    if (string.IsNullOrEmpty(special) || !special.Contains(c, StringComparison.Ordinal))
 510                    {
 0511                        result.Append('\\'); // not in special, so we keep the backslash
 512                    }
 1513                    result.Append(checkChar(s, start++));
 514                    break;
 515                }
 516            }
 517        }
 1518        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
 1529        if (special != null)
 530        {
 1531            for (int i = 0; i < special.Length; ++i)
 532            {
 1533                if (special[i] < 32 || special[i] > 126)
 534                {
 0535                    throw new ArgumentException("special characters must be in ASCII range 32-126", nameof(special));
 536                }
 537            }
 538        }
 539
 540        // Optimization for strings without escapes
 1541        if (start == end || s.IndexOf('\\', start, end - start) == -1)
 542        {
 1543            int p = start;
 1544            while (p < end)
 545            {
 1546                checkChar(s, p++);
 547            }
 1548            return s[start..end];
 549        }
 550        else
 551        {
 1552            var sb = new StringBuilder(end - start);
 1553            var utf8Encoding = new UTF8Encoding(false, true);
 1554            while (start < end)
 555            {
 1556                start = decodeChar(s, start, end, special, sb, utf8Encoding);
 557            }
 1558            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    {
 1567        var l = new List<string>();
 1568        char[] arr = new char[str.Length];
 1569        int pos = 0;
 570
 1571        int n = 0;
 1572        char quoteChar = '\0';
 1573        while (pos < str.Length)
 574        {
 1575            if (quoteChar == '\0' && (str[pos] == '"' || str[pos] == '\''))
 576            {
 1577                quoteChar = str[pos++];
 1578                continue; // Skip the quote.
 579            }
 1580            else if (quoteChar == '\0' && str[pos] == '\\' && pos + 1 < str.Length &&
 1581                    (str[pos + 1] == '\'' || str[pos + 1] == '"'))
 582            {
 1583                ++pos; // Skip the backslash
 584            }
 1585            else if (quoteChar != '\0' && str[pos] == '\\' && pos + 1 < str.Length && str[pos + 1] == quoteChar)
 586            {
 1587                ++pos; // Skip the backslash
 588            }
 1589            else if (quoteChar != '\0' && str[pos] == quoteChar)
 590            {
 1591                ++pos;
 1592                quoteChar = '\0';
 1593                continue; // Skip the quote.
 594            }
 1595            else if (delim.Contains(str[pos], StringComparison.Ordinal))
 596            {
 1597                if (quoteChar == '\0')
 598                {
 1599                    ++pos;
 1600                    if (n > 0)
 601                    {
 1602                        l.Add(new string(arr, 0, n));
 1603                        n = 0;
 604                    }
 1605                    continue;
 606                }
 607            }
 608
 1609            if (pos < str.Length)
 610            {
 1611                arr[n++] = str[pos++];
 612            }
 613        }
 614
 1615        if (n > 0)
 616        {
 1617            l.Add(new string(arr, 0, n));
 618        }
 1619        if (quoteChar != '\0')
 620        {
 1621            return null; // Unmatched quote.
 622        }
 1623        return l.ToArray();
 624    }
 625
 0626    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    {
 1636        char quoteChar = s[start];
 1637        if (quoteChar == '"' || quoteChar == '\'')
 638        {
 1639            start++;
 1640            int len = s.Length;
 641            int pos;
 1642            while (start < len && (pos = s.IndexOf(quoteChar, start)) != -1)
 643            {
 1644                if (s[pos - 1] != '\\')
 645                {
 1646                    return pos;
 647                }
 0648                start = pos + 1;
 649            }
 1650            return -1; // Unmatched quote
 651        }
 1652        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        //
 1663        int beginIndex = pat.IndexOf('*', StringComparison.Ordinal);
 1664        if (beginIndex < 0)
 665        {
 1666            return s.Equals(pat, StringComparison.Ordinal);
 667        }
 668
 669        //
 670        // Make sure start of the strings match
 671        //
 1672        if (beginIndex > s.Length ||
 1673            !s[..beginIndex].Equals(pat[..beginIndex], StringComparison.Ordinal))
 674        {
 1675            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        //
 1682        int endLength = pat.Length - beginIndex - 1;
 1683        if (endLength > s.Length)
 684        {
 0685            return false;
 686        }
 1687        int endIndex = s.Length - endLength;
 1688        if (endIndex < beginIndex || (!emptyMatch && endIndex == beginIndex))
 689        {
 0690            return false;
 691        }
 692
 693        //
 694        // Make sure end of the strings match
 695        //
 1696        if (!s[endIndex..].Equals(
 1697               pat.Substring(beginIndex + 1, pat.Length - beginIndex - 1),
 1698               StringComparison.Ordinal))
 699        {
 0700            return false;
 701        }
 702
 1703        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
 0711    public static IComparer<string> OrdinalStringComparer = new OrdinalStringComparerImpl();
 712}