StringExtensions.cs 43 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Globalization;
  4. using System.Runtime.CompilerServices;
  5. using System.Security;
  6. using System.Text;
  7. using System.Text.RegularExpressions;
  8. namespace Godot
  9. {
  10. public static class StringExtensions
  11. {
  12. private static int GetSliceCount(this string instance, string splitter)
  13. {
  14. if (string.IsNullOrEmpty(instance) || string.IsNullOrEmpty(splitter))
  15. return 0;
  16. int pos = 0;
  17. int slices = 1;
  18. while ((pos = instance.Find(splitter, pos, caseSensitive: true)) >= 0)
  19. {
  20. slices++;
  21. pos += splitter.Length;
  22. }
  23. return slices;
  24. }
  25. private static string GetSliceCharacter(this string instance, char splitter, int slice)
  26. {
  27. if (!string.IsNullOrEmpty(instance) && slice >= 0)
  28. {
  29. int i = 0;
  30. int prev = 0;
  31. int count = 0;
  32. while (true)
  33. {
  34. bool end = instance.Length <= i;
  35. if (end || instance[i] == splitter)
  36. {
  37. if (slice == count)
  38. {
  39. return instance.Substring(prev, i - prev);
  40. }
  41. else if (end)
  42. {
  43. return string.Empty;
  44. }
  45. count++;
  46. prev = i + 1;
  47. }
  48. i++;
  49. }
  50. }
  51. return string.Empty;
  52. }
  53. /// <summary>
  54. /// If the string is a path to a file, return the path to the file without the extension.
  55. /// </summary>
  56. public static string GetBaseName(this string instance)
  57. {
  58. int index = instance.LastIndexOf('.');
  59. if (index > 0)
  60. return instance.Substring(0, index);
  61. return instance;
  62. }
  63. /// <summary>
  64. /// Return <see langword="true"/> if the strings begins with the given string.
  65. /// </summary>
  66. public static bool BeginsWith(this string instance, string text)
  67. {
  68. return instance.StartsWith(text);
  69. }
  70. /// <summary>
  71. /// Return the bigrams (pairs of consecutive letters) of this string.
  72. /// </summary>
  73. public static string[] Bigrams(this string instance)
  74. {
  75. string[] b = new string[instance.Length - 1];
  76. for (int i = 0; i < b.Length; i++)
  77. {
  78. b[i] = instance.Substring(i, 2);
  79. }
  80. return b;
  81. }
  82. /// <summary>
  83. /// Converts a string containing a binary number into an integer.
  84. /// Binary strings can either be prefixed with `0b` or not,
  85. /// and they can also start with a `-` before the optional prefix.
  86. /// </summary>
  87. /// <param name="instance">The string to convert.</param>
  88. /// <returns>The converted string.</returns>
  89. public static int BinToInt(this string instance)
  90. {
  91. if (instance.Length == 0)
  92. {
  93. return 0;
  94. }
  95. int sign = 1;
  96. if (instance[0] == '-')
  97. {
  98. sign = -1;
  99. instance = instance.Substring(1);
  100. }
  101. if (instance.StartsWith("0b"))
  102. {
  103. instance = instance.Substring(2);
  104. }
  105. return sign * Convert.ToInt32(instance, 2);
  106. }
  107. /// <summary>
  108. /// Return the amount of substrings in string.
  109. /// </summary>
  110. public static int Count(this string instance, string what, bool caseSensitive = true, int from = 0, int to = 0)
  111. {
  112. if (what.Length == 0)
  113. {
  114. return 0;
  115. }
  116. int len = instance.Length;
  117. int slen = what.Length;
  118. if (len < slen)
  119. {
  120. return 0;
  121. }
  122. string str;
  123. if (from >= 0 && to >= 0)
  124. {
  125. if (to == 0)
  126. {
  127. to = len;
  128. }
  129. else if (from >= to)
  130. {
  131. return 0;
  132. }
  133. if (from == 0 && to == len)
  134. {
  135. str = instance;
  136. }
  137. else
  138. {
  139. str = instance.Substring(from, to - from);
  140. }
  141. }
  142. else
  143. {
  144. return 0;
  145. }
  146. int c = 0;
  147. int idx;
  148. do
  149. {
  150. idx = str.IndexOf(what, caseSensitive ? StringComparison.Ordinal : StringComparison.OrdinalIgnoreCase);
  151. if (idx != -1)
  152. {
  153. str = str.Substring(idx + slen);
  154. ++c;
  155. }
  156. } while (idx != -1);
  157. return c;
  158. }
  159. /// <summary>
  160. /// Return a copy of the string with special characters escaped using the C language standard.
  161. /// </summary>
  162. public static string CEscape(this string instance)
  163. {
  164. var sb = new StringBuilder(string.Copy(instance));
  165. sb.Replace("\\", "\\\\");
  166. sb.Replace("\a", "\\a");
  167. sb.Replace("\b", "\\b");
  168. sb.Replace("\f", "\\f");
  169. sb.Replace("\n", "\\n");
  170. sb.Replace("\r", "\\r");
  171. sb.Replace("\t", "\\t");
  172. sb.Replace("\v", "\\v");
  173. sb.Replace("\'", "\\'");
  174. sb.Replace("\"", "\\\"");
  175. sb.Replace("?", "\\?");
  176. return sb.ToString();
  177. }
  178. /// <summary>
  179. /// Return a copy of the string with escaped characters replaced by their meanings
  180. /// according to the C language standard.
  181. /// </summary>
  182. public static string CUnescape(this string instance)
  183. {
  184. var sb = new StringBuilder(string.Copy(instance));
  185. sb.Replace("\\a", "\a");
  186. sb.Replace("\\b", "\b");
  187. sb.Replace("\\f", "\f");
  188. sb.Replace("\\n", "\n");
  189. sb.Replace("\\r", "\r");
  190. sb.Replace("\\t", "\t");
  191. sb.Replace("\\v", "\v");
  192. sb.Replace("\\'", "\'");
  193. sb.Replace("\\\"", "\"");
  194. sb.Replace("\\?", "?");
  195. sb.Replace("\\\\", "\\");
  196. return sb.ToString();
  197. }
  198. /// <summary>
  199. /// Change the case of some letters. Replace underscores with spaces, convert all letters
  200. /// to lowercase then capitalize first and every letter following the space character.
  201. /// For <c>capitalize camelCase mixed_with_underscores</c> it will return
  202. /// <c>Capitalize Camelcase Mixed With Underscores</c>.
  203. /// </summary>
  204. public static string Capitalize(this string instance)
  205. {
  206. string aux = instance.Replace("_", " ").ToLower();
  207. string cap = string.Empty;
  208. for (int i = 0; i < aux.GetSliceCount(" "); i++)
  209. {
  210. string slice = aux.GetSliceCharacter(' ', i);
  211. if (slice.Length > 0)
  212. {
  213. slice = char.ToUpper(slice[0]) + slice.Substring(1);
  214. if (i > 0)
  215. cap += " ";
  216. cap += slice;
  217. }
  218. }
  219. return cap;
  220. }
  221. /// <summary>
  222. /// Perform a case-sensitive comparison to another string, return -1 if less, 0 if equal and +1 if greater.
  223. /// </summary>
  224. public static int CasecmpTo(this string instance, string to)
  225. {
  226. return instance.CompareTo(to, caseSensitive: true);
  227. }
  228. /// <summary>
  229. /// Perform a comparison to another string, return -1 if less, 0 if equal and +1 if greater.
  230. /// </summary>
  231. public static int CompareTo(this string instance, string to, bool caseSensitive = true)
  232. {
  233. if (string.IsNullOrEmpty(instance))
  234. return string.IsNullOrEmpty(to) ? 0 : -1;
  235. if (string.IsNullOrEmpty(to))
  236. return 1;
  237. int instanceIndex = 0;
  238. int toIndex = 0;
  239. if (caseSensitive) // Outside while loop to avoid checking multiple times, despite some code duplication.
  240. {
  241. while (true)
  242. {
  243. if (to[toIndex] == 0 && instance[instanceIndex] == 0)
  244. return 0; // We're equal
  245. if (instance[instanceIndex] == 0)
  246. return -1; // If this is empty, and the other one is not, then we're less... I think?
  247. if (to[toIndex] == 0)
  248. return 1; // Otherwise the other one is smaller...
  249. if (instance[instanceIndex] < to[toIndex]) // More than
  250. return -1;
  251. if (instance[instanceIndex] > to[toIndex]) // Less than
  252. return 1;
  253. instanceIndex++;
  254. toIndex++;
  255. }
  256. }
  257. else
  258. {
  259. while (true)
  260. {
  261. if (to[toIndex] == 0 && instance[instanceIndex] == 0)
  262. return 0; // We're equal
  263. if (instance[instanceIndex] == 0)
  264. return -1; // If this is empty, and the other one is not, then we're less... I think?
  265. if (to[toIndex] == 0)
  266. return 1; // Otherwise the other one is smaller..
  267. if (char.ToUpper(instance[instanceIndex]) < char.ToUpper(to[toIndex])) // More than
  268. return -1;
  269. if (char.ToUpper(instance[instanceIndex]) > char.ToUpper(to[toIndex])) // Less than
  270. return 1;
  271. instanceIndex++;
  272. toIndex++;
  273. }
  274. }
  275. }
  276. /// <summary>
  277. /// Return <see langword="true"/> if the strings ends with the given string.
  278. /// </summary>
  279. public static bool EndsWith(this string instance, string text)
  280. {
  281. return instance.EndsWith(text);
  282. }
  283. /// <summary>
  284. /// Erase <paramref name="chars"/> characters from the string starting from <paramref name="pos"/>.
  285. /// </summary>
  286. public static void Erase(this StringBuilder instance, int pos, int chars)
  287. {
  288. instance.Remove(pos, chars);
  289. }
  290. /// <summary>
  291. /// If the string is a path to a file, return the extension.
  292. /// </summary>
  293. public static string GetExtension(this string instance)
  294. {
  295. int pos = instance.FindLast(".");
  296. if (pos < 0)
  297. return instance;
  298. return instance.Substring(pos + 1);
  299. }
  300. /// <summary>
  301. /// Find the first occurrence of a substring. Optionally, the search starting position can be passed.
  302. /// </summary>
  303. /// <returns>The starting position of the substring, or -1 if not found.</returns>
  304. public static int Find(this string instance, string what, int from = 0, bool caseSensitive = true)
  305. {
  306. return instance.IndexOf(what, from, caseSensitive ? StringComparison.Ordinal : StringComparison.OrdinalIgnoreCase);
  307. }
  308. /// <summary>
  309. /// Find the first occurrence of a char. Optionally, the search starting position can be passed.
  310. /// </summary>
  311. /// <returns>The first instance of the char, or -1 if not found.</returns>
  312. public static int Find(this string instance, char what, int from = 0, bool caseSensitive = true)
  313. {
  314. // TODO: Could be more efficient if we get a char version of `IndexOf`.
  315. // See https://github.com/dotnet/runtime/issues/44116
  316. return instance.IndexOf(what.ToString(), from, caseSensitive ? StringComparison.Ordinal : StringComparison.OrdinalIgnoreCase);
  317. }
  318. /// <summary>Find the last occurrence of a substring.</summary>
  319. /// <returns>The starting position of the substring, or -1 if not found.</returns>
  320. public static int FindLast(this string instance, string what, bool caseSensitive = true)
  321. {
  322. return instance.FindLast(what, instance.Length - 1, caseSensitive);
  323. }
  324. /// <summary>Find the last occurrence of a substring specifying the search starting position.</summary>
  325. /// <returns>The starting position of the substring, or -1 if not found.</returns>
  326. public static int FindLast(this string instance, string what, int from, bool caseSensitive = true)
  327. {
  328. return instance.LastIndexOf(what, from, caseSensitive ? StringComparison.Ordinal : StringComparison.OrdinalIgnoreCase);
  329. }
  330. /// <summary>
  331. /// Find the first occurrence of a substring but search as case-insensitive.
  332. /// Optionally, the search starting position can be passed.
  333. /// </summary>
  334. /// <returns>The starting position of the substring, or -1 if not found.</returns>
  335. public static int FindN(this string instance, string what, int from = 0)
  336. {
  337. return instance.IndexOf(what, from, StringComparison.OrdinalIgnoreCase);
  338. }
  339. /// <summary>
  340. /// If the string is a path to a file, return the base directory.
  341. /// </summary>
  342. public static string GetBaseDir(this string instance)
  343. {
  344. int basepos = instance.Find("://");
  345. string rs;
  346. string directory = string.Empty;
  347. if (basepos != -1)
  348. {
  349. int end = basepos + 3;
  350. rs = instance.Substring(end);
  351. directory = instance.Substring(0, end);
  352. }
  353. else
  354. {
  355. if (instance.BeginsWith("/"))
  356. {
  357. rs = instance.Substring(1);
  358. directory = "/";
  359. }
  360. else
  361. {
  362. rs = instance;
  363. }
  364. }
  365. int sep = Mathf.Max(rs.FindLast("/"), rs.FindLast("\\"));
  366. if (sep == -1)
  367. return directory;
  368. return directory + rs.Substr(0, sep);
  369. }
  370. /// <summary>
  371. /// If the string is a path to a file, return the file and ignore the base directory.
  372. /// </summary>
  373. public static string GetFile(this string instance)
  374. {
  375. int sep = Mathf.Max(instance.FindLast("/"), instance.FindLast("\\"));
  376. if (sep == -1)
  377. return instance;
  378. return instance.Substring(sep + 1);
  379. }
  380. /// <summary>
  381. /// Converts the given byte array of ASCII encoded text to a string.
  382. /// Faster alternative to <see cref="GetStringFromUTF8"/> if the
  383. /// content is ASCII-only. Unlike the UTF-8 function this function
  384. /// maps every byte to a character in the array. Multibyte sequences
  385. /// will not be interpreted correctly. For parsing user input always
  386. /// use <see cref="GetStringFromUTF8"/>.
  387. /// </summary>
  388. /// <param name="bytes">A byte array of ASCII characters (on the range of 0-127).</param>
  389. /// <returns>A string created from the bytes.</returns>
  390. public static string GetStringFromASCII(this byte[] bytes)
  391. {
  392. return Encoding.ASCII.GetString(bytes);
  393. }
  394. /// <summary>
  395. /// Converts the given byte array of UTF-8 encoded text to a string.
  396. /// Slower than <see cref="GetStringFromASCII"/> but supports UTF-8
  397. /// encoded data. Use this function if you are unsure about the
  398. /// source of the data. For user input this function
  399. /// should always be preferred.
  400. /// </summary>
  401. /// <param name="bytes">A byte array of UTF-8 characters (a character may take up multiple bytes).</param>
  402. /// <returns>A string created from the bytes.</returns>
  403. public static string GetStringFromUTF8(this byte[] bytes)
  404. {
  405. return Encoding.UTF8.GetString(bytes);
  406. }
  407. /// <summary>
  408. /// Hash the string and return a 32 bits unsigned integer.
  409. /// </summary>
  410. public static uint Hash(this string instance)
  411. {
  412. uint hash = 5381;
  413. foreach (uint c in instance)
  414. {
  415. hash = (hash << 5) + hash + c; // hash * 33 + c
  416. }
  417. return hash;
  418. }
  419. /// <summary>
  420. /// Returns a hexadecimal representation of this byte as a string.
  421. /// </summary>
  422. /// <param name="b">The byte to encode.</param>
  423. /// <returns>The hexadecimal representation of this byte.</returns>
  424. internal static string HexEncode(this byte b)
  425. {
  426. string ret = string.Empty;
  427. for (int i = 0; i < 2; i++)
  428. {
  429. char c;
  430. int lv = b & 0xF;
  431. if (lv < 10)
  432. {
  433. c = (char)('0' + lv);
  434. }
  435. else
  436. {
  437. c = (char)('a' + lv - 10);
  438. }
  439. b >>= 4;
  440. ret = c + ret;
  441. }
  442. return ret;
  443. }
  444. /// <summary>
  445. /// Returns a hexadecimal representation of this byte array as a string.
  446. /// </summary>
  447. /// <param name="bytes">The byte array to encode.</param>
  448. /// <returns>The hexadecimal representation of this byte array.</returns>
  449. public static string HexEncode(this byte[] bytes)
  450. {
  451. string ret = string.Empty;
  452. foreach (byte b in bytes)
  453. {
  454. ret += b.HexEncode();
  455. }
  456. return ret;
  457. }
  458. /// <summary>
  459. /// Converts a string containing a hexadecimal number into an integer.
  460. /// Hexadecimal strings can either be prefixed with `0x` or not,
  461. /// and they can also start with a `-` before the optional prefix.
  462. /// </summary>
  463. /// <param name="instance">The string to convert.</param>
  464. /// <returns>The converted string.</returns>
  465. public static int HexToInt(this string instance)
  466. {
  467. if (instance.Length == 0)
  468. {
  469. return 0;
  470. }
  471. int sign = 1;
  472. if (instance[0] == '-')
  473. {
  474. sign = -1;
  475. instance = instance.Substring(1);
  476. }
  477. if (instance.StartsWith("0x"))
  478. {
  479. instance = instance.Substring(2);
  480. }
  481. return sign * int.Parse(instance, NumberStyles.HexNumber);
  482. }
  483. /// <summary>
  484. /// Insert a substring at a given position.
  485. /// </summary>
  486. public static string Insert(this string instance, int pos, string what)
  487. {
  488. return instance.Insert(pos, what);
  489. }
  490. /// <summary>
  491. /// If the string is a path to a file or directory, return <see langword="true"/> if the path is absolute.
  492. /// </summary>
  493. public static bool IsAbsolutePath(this string instance)
  494. {
  495. if (string.IsNullOrEmpty(instance))
  496. return false;
  497. else if (instance.Length > 1)
  498. return instance[0] == '/' || instance[0] == '\\' || instance.Contains(":/") || instance.Contains(":\\");
  499. else
  500. return instance[0] == '/' || instance[0] == '\\';
  501. }
  502. /// <summary>
  503. /// If the string is a path to a file or directory, return <see langword="true"/> if the path is relative.
  504. /// </summary>
  505. public static bool IsRelativePath(this string instance)
  506. {
  507. return !IsAbsolutePath(instance);
  508. }
  509. /// <summary>
  510. /// Check whether this string is a subsequence of the given string.
  511. /// </summary>
  512. public static bool IsSubsequenceOf(this string instance, string text, bool caseSensitive = true)
  513. {
  514. int len = instance.Length;
  515. if (len == 0)
  516. return true; // Technically an empty string is subsequence of any string
  517. if (len > text.Length)
  518. return false;
  519. int source = 0;
  520. int target = 0;
  521. while (source < len && target < text.Length)
  522. {
  523. bool match;
  524. if (!caseSensitive)
  525. {
  526. char sourcec = char.ToLower(instance[source]);
  527. char targetc = char.ToLower(text[target]);
  528. match = sourcec == targetc;
  529. }
  530. else
  531. {
  532. match = instance[source] == text[target];
  533. }
  534. if (match)
  535. {
  536. source++;
  537. if (source >= len)
  538. return true;
  539. }
  540. target++;
  541. }
  542. return false;
  543. }
  544. /// <summary>
  545. /// Check whether this string is a subsequence of the given string, ignoring case differences.
  546. /// </summary>
  547. public static bool IsSubsequenceOfI(this string instance, string text)
  548. {
  549. return instance.IsSubsequenceOf(text, caseSensitive: false);
  550. }
  551. /// <summary>
  552. /// Check whether the string contains a valid <see langword="float"/>.
  553. /// </summary>
  554. public static bool IsValidFloat(this string instance)
  555. {
  556. float f;
  557. return float.TryParse(instance, out f);
  558. }
  559. /// <summary>
  560. /// Check whether the string contains a valid color in HTML notation.
  561. /// </summary>
  562. public static bool IsValidHtmlColor(this string instance)
  563. {
  564. return Color.HtmlIsValid(instance);
  565. }
  566. /// <summary>
  567. /// Check whether the string is a valid identifier. As is common in
  568. /// programming languages, a valid identifier may contain only letters,
  569. /// digits and underscores (_) and the first character may not be a digit.
  570. /// </summary>
  571. public static bool IsValidIdentifier(this string instance)
  572. {
  573. int len = instance.Length;
  574. if (len == 0)
  575. return false;
  576. if (instance[0] >= '0' && instance[0] <= '9')
  577. return false; // Identifiers cannot start with numbers.
  578. for (int i = 0; i < len; i++)
  579. {
  580. bool validChar = instance[i] == '_' ||
  581. (instance[i] >= 'a' && instance[i] <= 'z') ||
  582. (instance[i] >= 'A' && instance[i] <= 'Z') ||
  583. (instance[i] >= '0' && instance[i] <= '9');
  584. if (!validChar)
  585. return false;
  586. }
  587. return true;
  588. }
  589. /// <summary>
  590. /// Check whether the string contains a valid integer.
  591. /// </summary>
  592. public static bool IsValidInteger(this string instance)
  593. {
  594. int f;
  595. return int.TryParse(instance, out f);
  596. }
  597. /// <summary>
  598. /// Check whether the string contains a valid IP address.
  599. /// </summary>
  600. public static bool IsValidIPAddress(this string instance)
  601. {
  602. // TODO: Support IPv6 addresses
  603. string[] ip = instance.Split(".");
  604. if (ip.Length != 4)
  605. return false;
  606. for (int i = 0; i < ip.Length; i++)
  607. {
  608. string n = ip[i];
  609. if (!n.IsValidInteger())
  610. return false;
  611. int val = n.ToInt();
  612. if (val < 0 || val > 255)
  613. return false;
  614. }
  615. return true;
  616. }
  617. /// <summary>
  618. /// Return a copy of the string with special characters escaped using the JSON standard.
  619. /// </summary>
  620. public static string JSONEscape(this string instance)
  621. {
  622. var sb = new StringBuilder(string.Copy(instance));
  623. sb.Replace("\\", "\\\\");
  624. sb.Replace("\b", "\\b");
  625. sb.Replace("\f", "\\f");
  626. sb.Replace("\n", "\\n");
  627. sb.Replace("\r", "\\r");
  628. sb.Replace("\t", "\\t");
  629. sb.Replace("\v", "\\v");
  630. sb.Replace("\"", "\\\"");
  631. return sb.ToString();
  632. }
  633. /// <summary>
  634. /// Return an amount of characters from the left of the string.
  635. /// </summary>
  636. public static string Left(this string instance, int pos)
  637. {
  638. if (pos <= 0)
  639. return string.Empty;
  640. if (pos >= instance.Length)
  641. return instance;
  642. return instance.Substring(0, pos);
  643. }
  644. /// <summary>
  645. /// Return the length of the string in characters.
  646. /// </summary>
  647. public static int Length(this string instance)
  648. {
  649. return instance.Length;
  650. }
  651. /// <summary>
  652. /// Returns a copy of the string with characters removed from the left.
  653. /// </summary>
  654. /// <param name="instance">The string to remove characters from.</param>
  655. /// <param name="chars">The characters to be removed.</param>
  656. /// <returns>A copy of the string with characters removed from the left.</returns>
  657. public static string LStrip(this string instance, string chars)
  658. {
  659. int len = instance.Length;
  660. int beg;
  661. for (beg = 0; beg < len; beg++)
  662. {
  663. if (chars.Find(instance[beg]) == -1)
  664. {
  665. break;
  666. }
  667. }
  668. if (beg == 0)
  669. {
  670. return instance;
  671. }
  672. return instance.Substr(beg, len - beg);
  673. }
  674. /// <summary>
  675. /// Do a simple expression match, where '*' matches zero or more
  676. /// arbitrary characters and '?' matches any single character except '.'.
  677. /// </summary>
  678. private static bool ExprMatch(this string instance, string expr, bool caseSensitive)
  679. {
  680. // case '\0':
  681. if (expr.Length == 0)
  682. return instance.Length == 0;
  683. switch (expr[0])
  684. {
  685. case '*':
  686. return ExprMatch(instance, expr.Substring(1), caseSensitive) || (instance.Length > 0 && ExprMatch(instance.Substring(1), expr, caseSensitive));
  687. case '?':
  688. return instance.Length > 0 && instance[0] != '.' && ExprMatch(instance.Substring(1), expr.Substring(1), caseSensitive);
  689. default:
  690. if (instance.Length == 0)
  691. return false;
  692. if (caseSensitive)
  693. return instance[0] == expr[0];
  694. return (char.ToUpper(instance[0]) == char.ToUpper(expr[0])) && ExprMatch(instance.Substring(1), expr.Substring(1), caseSensitive);
  695. }
  696. }
  697. /// <summary>
  698. /// Do a simple case sensitive expression match, using ? and * wildcards
  699. /// (see <see cref="ExprMatch(string, string, bool)"/>).
  700. /// </summary>
  701. public static bool Match(this string instance, string expr, bool caseSensitive = true)
  702. {
  703. if (instance.Length == 0 || expr.Length == 0)
  704. return false;
  705. return instance.ExprMatch(expr, caseSensitive);
  706. }
  707. /// <summary>
  708. /// Do a simple case insensitive expression match, using ? and * wildcards
  709. /// (see <see cref="ExprMatch(string, string, bool)"/>).
  710. /// </summary>
  711. public static bool MatchN(this string instance, string expr)
  712. {
  713. if (instance.Length == 0 || expr.Length == 0)
  714. return false;
  715. return instance.ExprMatch(expr, caseSensitive: false);
  716. }
  717. /// <summary>
  718. /// Return the MD5 hash of the string as an array of bytes.
  719. /// </summary>
  720. public static byte[] MD5Buffer(this string instance)
  721. {
  722. return godot_icall_String_md5_buffer(instance);
  723. }
  724. [MethodImpl(MethodImplOptions.InternalCall)]
  725. internal static extern byte[] godot_icall_String_md5_buffer(string str);
  726. /// <summary>
  727. /// Return the MD5 hash of the string as a string.
  728. /// </summary>
  729. public static string MD5Text(this string instance)
  730. {
  731. return godot_icall_String_md5_text(instance);
  732. }
  733. [MethodImpl(MethodImplOptions.InternalCall)]
  734. internal static extern string godot_icall_String_md5_text(string str);
  735. /// <summary>
  736. /// Perform a case-insensitive comparison to another string, return -1 if less, 0 if equal and +1 if greater.
  737. /// </summary>
  738. public static int NocasecmpTo(this string instance, string to)
  739. {
  740. return instance.CompareTo(to, caseSensitive: false);
  741. }
  742. /// <summary>
  743. /// Return the character code at position <paramref name="at"/>.
  744. /// </summary>
  745. public static int OrdAt(this string instance, int at)
  746. {
  747. return instance[at];
  748. }
  749. /// <summary>
  750. /// Format a number to have an exact number of <paramref name="digits"/> after the decimal point.
  751. /// </summary>
  752. public static string PadDecimals(this string instance, int digits)
  753. {
  754. int c = instance.Find(".");
  755. if (c == -1)
  756. {
  757. if (digits <= 0)
  758. return instance;
  759. instance += ".";
  760. c = instance.Length - 1;
  761. }
  762. else
  763. {
  764. if (digits <= 0)
  765. return instance.Substring(0, c);
  766. }
  767. if (instance.Length - (c + 1) > digits)
  768. {
  769. instance = instance.Substring(0, c + digits + 1);
  770. }
  771. else
  772. {
  773. while (instance.Length - (c + 1) < digits)
  774. {
  775. instance += "0";
  776. }
  777. }
  778. return instance;
  779. }
  780. /// <summary>
  781. /// Format a number to have an exact number of <paramref name="digits"/> before the decimal point.
  782. /// </summary>
  783. public static string PadZeros(this string instance, int digits)
  784. {
  785. string s = instance;
  786. int end = s.Find(".");
  787. if (end == -1)
  788. end = s.Length;
  789. if (end == 0)
  790. return s;
  791. int begin = 0;
  792. while (begin < end && (s[begin] < '0' || s[begin] > '9'))
  793. {
  794. begin++;
  795. }
  796. if (begin >= end)
  797. return s;
  798. while (end - begin < digits)
  799. {
  800. s = s.Insert(begin, "0");
  801. end++;
  802. }
  803. return s;
  804. }
  805. /// <summary>
  806. /// If the string is a path, this concatenates <paramref name="file"/> at the end of the string as a subpath.
  807. /// E.g. <c>"this/is".PlusFile("path") == "this/is/path"</c>.
  808. /// </summary>
  809. public static string PlusFile(this string instance, string file)
  810. {
  811. if (instance.Length > 0 && instance[instance.Length - 1] == '/')
  812. return instance + file;
  813. return instance + "/" + file;
  814. }
  815. /// <summary>
  816. /// Replace occurrences of a substring for different ones inside the string.
  817. /// </summary>
  818. public static string Replace(this string instance, string what, string forwhat)
  819. {
  820. return instance.Replace(what, forwhat);
  821. }
  822. /// <summary>
  823. /// Replace occurrences of a substring for different ones inside the string, but search case-insensitive.
  824. /// </summary>
  825. public static string ReplaceN(this string instance, string what, string forwhat)
  826. {
  827. return Regex.Replace(instance, what, forwhat, RegexOptions.IgnoreCase);
  828. }
  829. /// <summary>
  830. /// Perform a search for a substring, but start from the end of the string instead of the beginning.
  831. /// </summary>
  832. public static int RFind(this string instance, string what, int from = -1)
  833. {
  834. return godot_icall_String_rfind(instance, what, from);
  835. }
  836. [MethodImpl(MethodImplOptions.InternalCall)]
  837. internal static extern int godot_icall_String_rfind(string str, string what, int from);
  838. /// <summary>
  839. /// Perform a search for a substring, but start from the end of the string instead of the beginning.
  840. /// Also search case-insensitive.
  841. /// </summary>
  842. public static int RFindN(this string instance, string what, int from = -1)
  843. {
  844. return godot_icall_String_rfindn(instance, what, from);
  845. }
  846. [MethodImpl(MethodImplOptions.InternalCall)]
  847. internal static extern int godot_icall_String_rfindn(string str, string what, int from);
  848. /// <summary>
  849. /// Return the right side of the string from a given position.
  850. /// </summary>
  851. public static string Right(this string instance, int pos)
  852. {
  853. if (pos >= instance.Length)
  854. return instance;
  855. if (pos < 0)
  856. return string.Empty;
  857. return instance.Substring(pos, instance.Length - pos);
  858. }
  859. /// <summary>
  860. /// Returns a copy of the string with characters removed from the right.
  861. /// </summary>
  862. /// <param name="instance">The string to remove characters from.</param>
  863. /// <param name="chars">The characters to be removed.</param>
  864. /// <returns>A copy of the string with characters removed from the right.</returns>
  865. public static string RStrip(this string instance, string chars)
  866. {
  867. int len = instance.Length;
  868. int end;
  869. for (end = len - 1; end >= 0; end--)
  870. {
  871. if (chars.Find(instance[end]) == -1)
  872. {
  873. break;
  874. }
  875. }
  876. if (end == len - 1)
  877. {
  878. return instance;
  879. }
  880. return instance.Substr(0, end + 1);
  881. }
  882. public static byte[] SHA256Buffer(this string instance)
  883. {
  884. return godot_icall_String_sha256_buffer(instance);
  885. }
  886. [MethodImpl(MethodImplOptions.InternalCall)]
  887. internal static extern byte[] godot_icall_String_sha256_buffer(string str);
  888. /// <summary>
  889. /// Return the SHA-256 hash of the string as a string.
  890. /// </summary>
  891. public static string SHA256Text(this string instance)
  892. {
  893. return godot_icall_String_sha256_text(instance);
  894. }
  895. [MethodImpl(MethodImplOptions.InternalCall)]
  896. internal static extern string godot_icall_String_sha256_text(string str);
  897. /// <summary>
  898. /// Return the similarity index of the text compared to this string.
  899. /// 1 means totally similar and 0 means totally dissimilar.
  900. /// </summary>
  901. public static float Similarity(this string instance, string text)
  902. {
  903. if (instance == text)
  904. {
  905. // Equal strings are totally similar
  906. return 1.0f;
  907. }
  908. if (instance.Length < 2 || text.Length < 2)
  909. {
  910. // No way to calculate similarity without a single bigram
  911. return 0.0f;
  912. }
  913. string[] sourceBigrams = instance.Bigrams();
  914. string[] targetBigrams = text.Bigrams();
  915. int sourceSize = sourceBigrams.Length;
  916. int targetSize = targetBigrams.Length;
  917. float sum = sourceSize + targetSize;
  918. float inter = 0;
  919. for (int i = 0; i < sourceSize; i++)
  920. {
  921. for (int j = 0; j < targetSize; j++)
  922. {
  923. if (sourceBigrams[i] == targetBigrams[j])
  924. {
  925. inter++;
  926. break;
  927. }
  928. }
  929. }
  930. return 2.0f * inter / sum;
  931. }
  932. /// <summary>
  933. /// Returns a simplified canonical path.
  934. /// </summary>
  935. public static string SimplifyPath(this string instance)
  936. {
  937. return godot_icall_String_simplify_path(instance);
  938. }
  939. [MethodImpl(MethodImplOptions.InternalCall)]
  940. internal extern static string godot_icall_String_simplify_path(string str);
  941. /// <summary>
  942. /// Split the string by a divisor string, return an array of the substrings.
  943. /// Example "One,Two,Three" will return ["One","Two","Three"] if split by ",".
  944. /// </summary>
  945. public static string[] Split(this string instance, string divisor, bool allowEmpty = true)
  946. {
  947. return instance.Split(new[] { divisor }, allowEmpty ? StringSplitOptions.None : StringSplitOptions.RemoveEmptyEntries);
  948. }
  949. /// <summary>
  950. /// Split the string in floats by using a divisor string, return an array of the substrings.
  951. /// Example "1,2.5,3" will return [1,2.5,3] if split by ",".
  952. /// </summary>
  953. public static float[] SplitFloats(this string instance, string divisor, bool allowEmpty = true)
  954. {
  955. var ret = new List<float>();
  956. int from = 0;
  957. int len = instance.Length;
  958. while (true)
  959. {
  960. int end = instance.Find(divisor, from, caseSensitive: true);
  961. if (end < 0)
  962. end = len;
  963. if (allowEmpty || end > from)
  964. ret.Add(float.Parse(instance.Substring(from)));
  965. if (end == len)
  966. break;
  967. from = end + divisor.Length;
  968. }
  969. return ret.ToArray();
  970. }
  971. private static readonly char[] _nonPrintable =
  972. {
  973. (char)00, (char)01, (char)02, (char)03, (char)04, (char)05,
  974. (char)06, (char)07, (char)08, (char)09, (char)10, (char)11,
  975. (char)12, (char)13, (char)14, (char)15, (char)16, (char)17,
  976. (char)18, (char)19, (char)20, (char)21, (char)22, (char)23,
  977. (char)24, (char)25, (char)26, (char)27, (char)28, (char)29,
  978. (char)30, (char)31, (char)32
  979. };
  980. /// <summary>
  981. /// Return a copy of the string stripped of any non-printable character at the beginning and the end.
  982. /// The optional arguments are used to toggle stripping on the left and right edges respectively.
  983. /// </summary>
  984. public static string StripEdges(this string instance, bool left = true, bool right = true)
  985. {
  986. if (left)
  987. {
  988. if (right)
  989. return instance.Trim(_nonPrintable);
  990. return instance.TrimStart(_nonPrintable);
  991. }
  992. return instance.TrimEnd(_nonPrintable);
  993. }
  994. /// <summary>
  995. /// Return part of the string from the position <paramref name="from"/>, with length <paramref name="len"/>.
  996. /// </summary>
  997. public static string Substr(this string instance, int from, int len)
  998. {
  999. int max = instance.Length - from;
  1000. return instance.Substring(from, len > max ? max : len);
  1001. }
  1002. /// <summary>
  1003. /// Convert the String (which is a character array) to PackedByteArray (which is an array of bytes).
  1004. /// The conversion is speeded up in comparison to <see cref="ToUTF8(string)"/> with the assumption
  1005. /// that all the characters the String contains are only ASCII characters.
  1006. /// </summary>
  1007. public static byte[] ToAscii(this string instance)
  1008. {
  1009. return Encoding.ASCII.GetBytes(instance);
  1010. }
  1011. /// <summary>
  1012. /// Convert a string, containing a decimal number, into a <see langword="float" />.
  1013. /// </summary>
  1014. public static float ToFloat(this string instance)
  1015. {
  1016. return float.Parse(instance);
  1017. }
  1018. /// <summary>
  1019. /// Convert a string, containing an integer number, into an <see langword="int" />.
  1020. /// </summary>
  1021. public static int ToInt(this string instance)
  1022. {
  1023. return int.Parse(instance);
  1024. }
  1025. /// <summary>
  1026. /// Return the string converted to lowercase.
  1027. /// </summary>
  1028. public static string ToLower(this string instance)
  1029. {
  1030. return instance.ToLower();
  1031. }
  1032. /// <summary>
  1033. /// Return the string converted to uppercase.
  1034. /// </summary>
  1035. public static string ToUpper(this string instance)
  1036. {
  1037. return instance.ToUpper();
  1038. }
  1039. /// <summary>
  1040. /// Convert the String (which is an array of characters) to PackedByteArray (which is an array of bytes).
  1041. /// The conversion is a bit slower than <see cref="ToAscii(string)"/>, but supports all UTF-8 characters.
  1042. /// Therefore, you should prefer this function over <see cref="ToAscii(string)"/>.
  1043. /// </summary>
  1044. public static byte[] ToUTF8(this string instance)
  1045. {
  1046. return Encoding.UTF8.GetBytes(instance);
  1047. }
  1048. /// <summary>
  1049. /// Decodes a string in URL encoded format. This is meant to
  1050. /// decode parameters in a URL when receiving an HTTP request.
  1051. /// This mostly wraps around `System.Uri.UnescapeDataString()`,
  1052. /// but also handles `+`.
  1053. /// See <see cref="URIEncode"/> for encoding.
  1054. /// </summary>
  1055. /// <param name="instance">The string to decode.</param>
  1056. /// <returns>The unescaped string.</returns>
  1057. public static string URIDecode(this string instance)
  1058. {
  1059. return Uri.UnescapeDataString(instance.Replace("+", "%20"));
  1060. }
  1061. /// <summary>
  1062. /// Encodes a string to URL friendly format. This is meant to
  1063. /// encode parameters in a URL when sending an HTTP request.
  1064. /// This wraps around `System.Uri.EscapeDataString()`.
  1065. /// See <see cref="URIDecode"/> for decoding.
  1066. /// </summary>
  1067. /// <param name="instance">The string to encode.</param>
  1068. /// <returns>The escaped string.</returns>
  1069. public static string URIEncode(this string instance)
  1070. {
  1071. return Uri.EscapeDataString(instance);
  1072. }
  1073. /// <summary>
  1074. /// Return a copy of the string with special characters escaped using the XML standard.
  1075. /// </summary>
  1076. public static string XMLEscape(this string instance)
  1077. {
  1078. return SecurityElement.Escape(instance);
  1079. }
  1080. /// <summary>
  1081. /// Return a copy of the string with escaped characters replaced by their meanings
  1082. /// according to the XML standard.
  1083. /// </summary>
  1084. public static string XMLUnescape(this string instance)
  1085. {
  1086. return SecurityElement.FromString(instance).Text;
  1087. }
  1088. }
  1089. }