< Summary

Information
Class: Ice.UtilInternal.StringUtil
Assembly: Ice
File(s): /_/csharp/src/Ice/UtilInternal/StringUtil.cs
Tag: 125_37167941578
Line coverage
83%
Covered lines: 218
Uncovered lines: 43
Coverable lines: 261
Total lines: 694
Line coverage: 83.5%
Branch coverage
86%
Covered branches: 236
Total branches: 272
Branch coverage: 86.7%
Method coverage
100%
Covered methods: 11
Fully covered methods: 4
Total methods: 11
Method coverage: 100%
Full method coverage: 36.3%

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
findFirstOf(...)100%11100%
findFirstOf(...)100%44100%
findFirstNotOf(...)100%44100%
encodeChar(...)83.33%962450%
escapeString(...)86.36%222292.31%
checkChar(...)83.33%6687.5%
decodeChar(...)83.59%16512886.87%
unescapeString(...)87.5%161692.86%
splitString(...)100%4242100%
checkQuote(...)90%101090%
match(...)75%181680%

File(s)

/_/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 start. Returns -1 if none is
 40    // found.
 41    //
 42    public static int findFirstNotOf(string str, string match, int start)
 43    {
 144        int len = str.Length;
 145        for (int i = start; i < len; i++)
 46        {
 147            char ch = str[i];
 148            if (!match.Contains(ch, StringComparison.Ordinal))
 49            {
 150                return i;
 51            }
 52        }
 53
 154        return -1;
 55    }
 56
 57    private static void
 58    encodeChar(char c, StringBuilder sb, string special, Ice.ToStringMode toStringMode)
 59    {
 60        switch (c)
 61        {
 62            case '\\':
 63            {
 164                sb.Append("\\\\");
 165                break;
 66            }
 67            case '\'':
 68            {
 069                sb.Append("\\'");
 070                break;
 71            }
 72            case '"':
 73            {
 074                sb.Append("\\\"");
 075                break;
 76            }
 77            case '\a':
 78            {
 079                if (toStringMode == Ice.ToStringMode.Compat)
 80                {
 81                    // Octal escape for compatibility with 3.6 and earlier
 082                    sb.Append("\\007");
 83                }
 84                else
 85                {
 086                    sb.Append("\\a");
 87                }
 088                break;
 89            }
 90            case '\b':
 91            {
 092                sb.Append("\\b");
 093                break;
 94            }
 95            case '\f':
 96            {
 097                sb.Append("\\f");
 098                break;
 99            }
 100            case '\n':
 101            {
 0102                sb.Append("\\n");
 0103                break;
 104            }
 105            case '\r':
 106            {
 0107                sb.Append("\\r");
 0108                break;
 109            }
 110            case '\t':
 111            {
 0112                sb.Append("\\t");
 0113                break;
 114            }
 115            case '\v':
 116            {
 0117                if (toStringMode == Ice.ToStringMode.Compat)
 118                {
 119                    // Octal escape for compatibility with 3.6 and earlier
 0120                    sb.Append("\\013");
 121                }
 122                else
 123                {
 0124                    sb.Append("\\v");
 125                }
 0126                break;
 127            }
 128            default:
 129            {
 1130                if (special != null && special.Contains(c, StringComparison.Ordinal))
 131                {
 1132                    sb.Append('\\');
 1133                    sb.Append(c);
 134                }
 135                else
 136                {
 1137                    int i = (int)c;
 1138                    if (i < 32 || i > 126)
 139                    {
 1140                        if (toStringMode == Ice.ToStringMode.Compat)
 141                        {
 142                            //
 143                            // When ToStringMode=Compat, c is a UTF-8 byte
 144                            //
 145                            Debug.Assert(i < 256);
 146
 1147                            sb.Append('\\');
 1148                            string octal = System.Convert.ToString(i, 8);
 149                            //
 150                            // Add leading zeroes so that we avoid problems during
 151                            // decoding. For example, consider the encoded string
 152                            // \0013 (i.e., a character with value 1 followed by
 153                            // the character '3'). If the leading zeroes were omitted,
 154                            // the result would be incorrectly interpreted by the
 155                            // decoder as a single character with value 11.
 156                            //
 1157                            for (int j = octal.Length; j < 3; j++)
 158                            {
 1159                                sb.Append('0');
 160                            }
 1161                            sb.Append(octal);
 162                        }
 1163                        else if (i < 32 || i == 127 || toStringMode == Ice.ToStringMode.ASCII)
 164                        {
 165                            // append \\unnnn
 1166                            sb.Append("\\u");
 1167                            string hex = System.Convert.ToString(i, 16);
 1168                            for (int j = hex.Length; j < 4; j++)
 169                            {
 1170                                sb.Append('0');
 171                            }
 1172                            sb.Append(hex);
 173                        }
 174                        else
 175                        {
 176                            // keep as is
 1177                            sb.Append(c);
 178                        }
 179                    }
 180                    else
 181                    {
 182                        // printable ASCII character
 1183                        sb.Append(c);
 184                    }
 185                }
 186                break;
 187            }
 188        }
 1189    }
 190
 191    //
 192    // Add escape sequences (such as "\n", or "\007") to the input string
 193    //
 194    public static string escapeString(string s, string special, Ice.ToStringMode toStringMode)
 195    {
 1196        if (special != null)
 197        {
 1198            for (int i = 0; i < special.Length; ++i)
 199            {
 1200                if (special[i] < 32 || special[i] > 126)
 201                {
 0202                    throw new ArgumentException("special characters must be in ASCII range 32-126", nameof(special));
 203                }
 204            }
 205        }
 206
 1207        if (toStringMode == Ice.ToStringMode.Compat)
 208        {
 209            // Encode UTF-8 bytes
 210
 1211            var utf8 = new UTF8Encoding();
 1212            byte[] bytes = utf8.GetBytes(s);
 213
 1214            var result = new StringBuilder(bytes.Length);
 1215            for (int i = 0; i < bytes.Length; i++)
 216            {
 1217                encodeChar((char)bytes[i], result, special, toStringMode);
 218            }
 219
 1220            return result.ToString();
 221        }
 222        else
 223        {
 1224            var result = new StringBuilder(s.Length);
 225
 1226            for (int i = 0; i < s.Length; i++)
 227            {
 1228                char c = s[i];
 1229                if (toStringMode == Ice.ToStringMode.Unicode || !char.IsSurrogate(c))
 230                {
 1231                    encodeChar(c, result, special, toStringMode);
 232                }
 233                else
 234                {
 235                    Debug.Assert(toStringMode == Ice.ToStringMode.ASCII && char.IsSurrogate(c));
 1236                    if (i + 1 == s.Length)
 237                    {
 0238                        throw new System.ArgumentException("High surrogate without low surrogate");
 239                    }
 240                    else
 241                    {
 1242                        i++;
 1243                        int codePoint = char.ConvertToUtf32(c, s[i]);
 244                        // append \Unnnnnnnn
 1245                        result.Append("\\U");
 1246                        string hex = System.Convert.ToString(codePoint, 16);
 1247                        for (int j = hex.Length; j < 8; j++)
 248                        {
 1249                            result.Append('0');
 250                        }
 1251                        result.Append(hex);
 252                    }
 253                }
 254            }
 255
 1256            return result.ToString();
 257        }
 258    }
 259
 260    private static char
 261    checkChar(string s, int pos)
 262    {
 1263        char c = s[pos];
 1264        if (c < 32 || c == 127)
 265        {
 266            string msg;
 1267            if (pos > 0)
 268            {
 1269                msg = $"character after `{s[..pos]}'";
 270            }
 271            else
 272            {
 0273                msg = "first character";
 274            }
 1275            msg += " is not a printable ASCII character (ordinal " + (int)c + ")";
 1276            throw new System.ArgumentException(msg);
 277        }
 1278        return c;
 279    }
 280
 281    //
 282    // Decode the character or escape sequence starting at start and appends it to result;
 283    // returns the index of the first character following the decoded character
 284    // or escape sequence.
 285    //
 286    private static int
 287    decodeChar(string s, int start, int end, string special, StringBuilder result, UTF8Encoding utf8Encoding)
 288    {
 289        Debug.Assert(start >= 0);
 290        Debug.Assert(start < end);
 291        Debug.Assert(end <= s.Length);
 292
 1293        if (s[start] != '\\')
 294        {
 1295            result.Append(checkChar(s, start++));
 296        }
 1297        else if (start + 1 == end)
 298        {
 1299            ++start;
 1300            result.Append('\\'); // trailing backslash
 301        }
 302        else
 303        {
 1304            char c = s[++start];
 305
 306            switch (c)
 307            {
 308                case '\\':
 309                case '\'':
 310                case '"':
 311                case '?':
 312                {
 1313                    ++start;
 1314                    result.Append(c);
 1315                    break;
 316                }
 317                case 'a':
 318                {
 0319                    ++start;
 0320                    result.Append('\a');
 0321                    break;
 322                }
 323                case 'b':
 324                {
 1325                    ++start;
 1326                    result.Append('\b');
 1327                    break;
 328                }
 329                case 'f':
 330                {
 1331                    ++start;
 1332                    result.Append('\f');
 1333                    break;
 334                }
 335                case 'n':
 336                {
 1337                    ++start;
 1338                    result.Append('\n');
 1339                    break;
 340                }
 341                case 'r':
 342                {
 1343                    ++start;
 1344                    result.Append('\r');
 1345                    break;
 346                }
 347                case 't':
 348                {
 1349                    ++start;
 1350                    result.Append('\t');
 1351                    break;
 352                }
 353                case 'v':
 354                {
 0355                    ++start;
 0356                    result.Append('\v');
 0357                    break;
 358                }
 359                case 'u':
 360                case 'U':
 361                {
 1362                    int codePoint = 0;
 1363                    bool inBMP = c == 'u';
 1364                    int size = inBMP ? 4 : 8;
 1365                    ++start;
 1366                    while (size > 0 && start < end)
 367                    {
 1368                        c = s[start++];
 369                        int charVal;
 1370                        if (c >= '0' && c <= '9')
 371                        {
 1372                            charVal = c - '0';
 373                        }
 1374                        else if (c >= 'a' && c <= 'f')
 375                        {
 1376                            charVal = 10 + (c - 'a');
 377                        }
 0378                        else if (c >= 'A' && c <= 'F')
 379                        {
 0380                            charVal = 10 + (c - 'A');
 381                        }
 382                        else
 383                        {
 384                            break; // while
 385                        }
 1386                        codePoint = (codePoint * 16) + charVal;
 1387                        --size;
 388                    }
 1389                    if (size > 0)
 390                    {
 0391                        throw new System.ArgumentException("Invalid universal character name: too few hex digits");
 392                    }
 1393                    if (codePoint >= 0xD800 && codePoint <= 0xDFFF)
 394                    {
 1395                        throw new System.ArgumentException("A universal character name cannot designate a surrogate");
 396                    }
 1397                    if (inBMP || codePoint <= 0xFFFF)
 398                    {
 1399                        result.Append((char)codePoint);
 400                    }
 401                    else
 402                    {
 1403                        result.Append(char.ConvertFromUtf32(codePoint));
 404                    }
 1405                    break;
 406                }
 407
 408                case '0':
 409                case '1':
 410                case '2':
 411                case '3':
 412                case '4':
 413                case '5':
 414                case '6':
 415                case '7':
 416                case 'x':
 417                {
 418                    // UTF-8 byte sequence encoded with octal escapes
 419
 1420                    byte[] arr = new byte[end - start];
 1421                    int i = 0;
 1422                    bool more = true;
 1423                    while (more)
 424                    {
 1425                        int val = 0;
 1426                        if (c == 'x')
 427                        {
 1428                            int size = 2;
 1429                            ++start;
 1430                            while (size > 0 && start < end)
 431                            {
 1432                                c = s[start++];
 433                                int charVal;
 1434                                if (c >= '0' && c <= '9')
 435                                {
 1436                                    charVal = c - '0';
 437                                }
 1438                                else if (c >= 'a' && c <= 'f')
 439                                {
 0440                                    charVal = 10 + (c - 'a');
 441                                }
 1442                                else if (c >= 'A' && c <= 'F')
 443                                {
 0444                                    charVal = 10 + (c - 'A');
 445                                }
 446                                else
 447                                {
 1448                                    --start; // move back
 1449                                    break; // while
 450                                }
 1451                                val = (val * 16) + charVal;
 1452                                --size;
 453                            }
 1454                            if (size == 2)
 455                            {
 0456                                throw new System.ArgumentException("Invalid \\x escape sequence: no hex digit");
 457                            }
 458                        }
 459                        else
 460                        {
 1461                            for (int j = 0; j < 3 && start < end; ++j)
 462                            {
 1463                                int charVal = s[start++] - '0';
 1464                                if (charVal < 0 || charVal > 7)
 465                                {
 1466                                    --start; // move back
 467                                    Debug.Assert(j != 0); // must be at least one digit
 1468                                    break; // for
 469                                }
 1470                                val = (val * 8) + charVal;
 471                            }
 1472                            if (val > 255)
 473                            {
 1474                                string msg =
 1475                                    "octal value \\" +
 1476                                    System.Convert.ToString(val, 8) +
 1477                                    " (" + val + ") is out of range";
 1478                                throw new System.ArgumentException(msg);
 479                            }
 480                        }
 481
 1482                        arr[i++] = (byte)val;
 483
 1484                        more = false;
 485
 1486                        if ((start + 1 < end) && s[start] == '\\')
 487                        {
 1488                            c = s[start + 1];
 1489                            if (c == 'x' || (c >= '0' && c <= '9'))
 490                            {
 1491                                start++;
 1492                                more = true;
 493                            }
 494                        }
 495                    }
 496
 1497                    result.Append(utf8Encoding.GetString(arr, 0, i)); // May raise ArgumentException.
 1498                    break;
 499                }
 500                default:
 501                {
 1502                    if (string.IsNullOrEmpty(special) || !special.Contains(c, StringComparison.Ordinal))
 503                    {
 0504                        result.Append('\\'); // not in special, so we keep the backslash
 505                    }
 1506                    result.Append(checkChar(s, start++));
 507                    break;
 508                }
 509            }
 510        }
 1511        return start;
 512    }
 513
 514    //
 515    // Remove escape sequences added by escapeString. Throws System.ArgumentException
 516    // for an invalid input string.
 517    //
 518    public static string unescapeString(string s, int start, int end, string special)
 519    {
 520        Debug.Assert(start >= 0 && start <= end && end <= s.Length);
 521
 1522        if (special != null)
 523        {
 1524            for (int i = 0; i < special.Length; ++i)
 525            {
 1526                if (special[i] < 32 || special[i] > 126)
 527                {
 0528                    throw new ArgumentException("special characters must be in ASCII range 32-126", nameof(special));
 529                }
 530            }
 531        }
 532
 533        // Optimization for strings without escapes
 1534        if (start == end || s.IndexOf('\\', start, end - start) == -1)
 535        {
 1536            int p = start;
 1537            while (p < end)
 538            {
 1539                checkChar(s, p++);
 540            }
 1541            return s[start..end];
 542        }
 543        else
 544        {
 1545            var sb = new StringBuilder(end - start);
 1546            var utf8Encoding = new UTF8Encoding(false, true);
 1547            while (start < end)
 548            {
 1549                start = decodeChar(s, start, end, special, sb, utf8Encoding);
 550            }
 1551            return sb.ToString();
 552        }
 553    }
 554
 555    //
 556    // Split string helper; returns null for unmatched quotes
 557    //
 558    public static string[] splitString(string str, string delim)
 559    {
 1560        var l = new List<string>();
 1561        char[] arr = new char[str.Length];
 1562        int pos = 0;
 563
 1564        int n = 0;
 1565        char quoteChar = '\0';
 1566        while (pos < str.Length)
 567        {
 1568            if (quoteChar == '\0' && (str[pos] == '"' || str[pos] == '\''))
 569            {
 1570                quoteChar = str[pos++];
 1571                continue; // Skip the quote.
 572            }
 1573            else if (quoteChar == '\0' && str[pos] == '\\' && pos + 1 < str.Length &&
 1574                    (str[pos + 1] == '\'' || str[pos + 1] == '"'))
 575            {
 1576                ++pos; // Skip the backslash
 577            }
 1578            else if (quoteChar != '\0' && str[pos] == '\\' && pos + 1 < str.Length && str[pos + 1] == quoteChar)
 579            {
 1580                ++pos; // Skip the backslash
 581            }
 1582            else if (quoteChar != '\0' && str[pos] == quoteChar)
 583            {
 1584                ++pos;
 1585                quoteChar = '\0';
 1586                continue; // Skip the quote.
 587            }
 1588            else if (delim.Contains(str[pos], StringComparison.Ordinal))
 589            {
 1590                if (quoteChar == '\0')
 591                {
 1592                    ++pos;
 1593                    if (n > 0)
 594                    {
 1595                        l.Add(new string(arr, 0, n));
 1596                        n = 0;
 597                    }
 1598                    continue;
 599                }
 600            }
 601
 1602            if (pos < str.Length)
 603            {
 1604                arr[n++] = str[pos++];
 605            }
 606        }
 607
 1608        if (n > 0)
 609        {
 1610            l.Add(new string(arr, 0, n));
 611        }
 1612        if (quoteChar != '\0')
 613        {
 1614            return null; // Unmatched quote.
 615        }
 1616        return l.ToArray();
 617    }
 618
 619    //
 620    // If a single or double quotation mark is found at the start position,
 621    // then the position of the matching closing quote is returned. If no
 622    // quotation mark is found at the start position, then 0 is returned.
 623    // If no matching closing quote is found, then -1 is returned.
 624    //
 625    public static int checkQuote(string s, int start)
 626    {
 1627        char quoteChar = s[start];
 1628        if (quoteChar == '"' || quoteChar == '\'')
 629        {
 1630            start++;
 1631            int len = s.Length;
 632            int pos;
 1633            while (start < len && (pos = s.IndexOf(quoteChar, start)) != -1)
 634            {
 1635                if (s[pos - 1] != '\\')
 636                {
 1637                    return pos;
 638                }
 0639                start = pos + 1;
 640            }
 1641            return -1; // Unmatched quote
 642        }
 1643        return 0; // Not quoted
 644    }
 645
 646    public static bool match(string s, string pat, bool emptyMatch)
 647    {
 648        Debug.Assert(s.Length > 0);
 649        Debug.Assert(pat.Length > 0);
 650
 651        //
 652        // If pattern does not contain a wildcard just compare strings.
 653        //
 1654        int beginIndex = pat.IndexOf('*', StringComparison.Ordinal);
 1655        if (beginIndex < 0)
 656        {
 1657            return s.Equals(pat, StringComparison.Ordinal);
 658        }
 659
 660        //
 661        // Make sure start of the strings match
 662        //
 1663        if (beginIndex > s.Length ||
 1664            !s[..beginIndex].Equals(pat[..beginIndex], StringComparison.Ordinal))
 665        {
 1666            return false;
 667        }
 668
 669        //
 670        // Make sure there is something present in the middle to match the
 671        // wildcard. If emptyMatch is true, allow a match of "".
 672        //
 1673        int endLength = pat.Length - beginIndex - 1;
 1674        if (endLength > s.Length)
 675        {
 0676            return false;
 677        }
 1678        int endIndex = s.Length - endLength;
 1679        if (endIndex < beginIndex || (!emptyMatch && endIndex == beginIndex))
 680        {
 0681            return false;
 682        }
 683
 684        //
 685        // Make sure end of the strings match
 686        //
 1687        if (!s[endIndex..].Equals(pat[(beginIndex + 1)..], StringComparison.Ordinal))
 688        {
 0689            return false;
 690        }
 691
 1692        return true;
 693    }
 694}