You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

337 lines
11 KiB

  1. #region License
  2. // Copyright (c) 2007 James Newton-King
  3. //
  4. // Permission is hereby granted, free of charge, to any person
  5. // obtaining a copy of this software and associated documentation
  6. // files (the "Software"), to deal in the Software without
  7. // restriction, including without limitation the rights to use,
  8. // copy, modify, merge, publish, distribute, sublicense, and/or sell
  9. // copies of the Software, and to permit persons to whom the
  10. // Software is furnished to do so, subject to the following
  11. // conditions:
  12. //
  13. // The above copyright notice and this permission notice shall be
  14. // included in all copies or substantial portions of the Software.
  15. //
  16. // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
  17. // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
  18. // OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
  19. // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
  20. // HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
  21. // WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
  22. // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
  23. // OTHER DEALINGS IN THE SOFTWARE.
  24. #endregion
  25. using System;
  26. using System.Collections.Generic;
  27. using System.IO;
  28. using System.Text;
  29. using System.Globalization;
  30. #if NET20
  31. using Newtonsoft.Json.Utilities.LinqBridge;
  32. #else
  33. using System.Linq;
  34. #endif
  35. using Newtonsoft.Json.Serialization;
  36. namespace Newtonsoft.Json.Utilities
  37. {
  38. internal static class StringUtils
  39. {
  40. public const string CarriageReturnLineFeed = "\r\n";
  41. public const string Empty = "";
  42. public const char CarriageReturn = '\r';
  43. public const char LineFeed = '\n';
  44. public const char Tab = '\t';
  45. public static string FormatWith(this string format, IFormatProvider provider, object arg0)
  46. {
  47. return format.FormatWith(provider, new[] { arg0 });
  48. }
  49. public static string FormatWith(this string format, IFormatProvider provider, object arg0, object arg1)
  50. {
  51. return format.FormatWith(provider, new[] { arg0, arg1 });
  52. }
  53. public static string FormatWith(this string format, IFormatProvider provider, object arg0, object arg1, object arg2)
  54. {
  55. return format.FormatWith(provider, new[] { arg0, arg1, arg2 });
  56. }
  57. public static string FormatWith(this string format, IFormatProvider provider, object arg0, object arg1, object arg2, object arg3)
  58. {
  59. return format.FormatWith(provider, new[] { arg0, arg1, arg2, arg3 });
  60. }
  61. private static string FormatWith(this string format, IFormatProvider provider, params object[] args)
  62. {
  63. // leave this a private to force code to use an explicit overload
  64. // avoids stack memory being reserved for the object array
  65. ValidationUtils.ArgumentNotNull(format, nameof(format));
  66. return string.Format(provider, format, args);
  67. }
  68. /// <summary>
  69. /// Determines whether the string is all white space. Empty string will return <c>false</c>.
  70. /// </summary>
  71. /// <param name="s">The string to test whether it is all white space.</param>
  72. /// <returns>
  73. /// <c>true</c> if the string is all white space; otherwise, <c>false</c>.
  74. /// </returns>
  75. public static bool IsWhiteSpace(string s)
  76. {
  77. if (s == null)
  78. {
  79. throw new ArgumentNullException(nameof(s));
  80. }
  81. if (s.Length == 0)
  82. {
  83. return false;
  84. }
  85. for (int i = 0; i < s.Length; i++)
  86. {
  87. if (!char.IsWhiteSpace(s[i]))
  88. {
  89. return false;
  90. }
  91. }
  92. return true;
  93. }
  94. public static StringWriter CreateStringWriter(int capacity)
  95. {
  96. StringBuilder sb = new StringBuilder(capacity);
  97. StringWriter sw = new StringWriter(sb, CultureInfo.InvariantCulture);
  98. return sw;
  99. }
  100. public static void ToCharAsUnicode(char c, char[] buffer)
  101. {
  102. buffer[0] = '\\';
  103. buffer[1] = 'u';
  104. buffer[2] = MathUtils.IntToHex((c >> 12) & '\x000f');
  105. buffer[3] = MathUtils.IntToHex((c >> 8) & '\x000f');
  106. buffer[4] = MathUtils.IntToHex((c >> 4) & '\x000f');
  107. buffer[5] = MathUtils.IntToHex(c & '\x000f');
  108. }
  109. public static TSource ForgivingCaseSensitiveFind<TSource>(this IEnumerable<TSource> source, Func<TSource, string> valueSelector, string testValue)
  110. {
  111. if (source == null)
  112. {
  113. throw new ArgumentNullException(nameof(source));
  114. }
  115. if (valueSelector == null)
  116. {
  117. throw new ArgumentNullException(nameof(valueSelector));
  118. }
  119. IEnumerable<TSource> caseInsensitiveResults = source.Where(s => string.Equals(valueSelector(s), testValue, StringComparison.OrdinalIgnoreCase));
  120. if (caseInsensitiveResults.Count() <= 1)
  121. {
  122. return caseInsensitiveResults.SingleOrDefault();
  123. }
  124. else
  125. {
  126. // multiple results returned. now filter using case sensitivity
  127. IEnumerable<TSource> caseSensitiveResults = source.Where(s => string.Equals(valueSelector(s), testValue, StringComparison.Ordinal));
  128. return caseSensitiveResults.SingleOrDefault();
  129. }
  130. }
  131. public static string ToCamelCase(string s)
  132. {
  133. if (string.IsNullOrEmpty(s) || !char.IsUpper(s[0]))
  134. {
  135. return s;
  136. }
  137. char[] chars = s.ToCharArray();
  138. for (int i = 0; i < chars.Length; i++)
  139. {
  140. if (i == 1 && !char.IsUpper(chars[i]))
  141. {
  142. break;
  143. }
  144. bool hasNext = (i + 1 < chars.Length);
  145. if (i > 0 && hasNext && !char.IsUpper(chars[i + 1]))
  146. {
  147. // if the next character is a space, which is not considered uppercase
  148. // (otherwise we wouldn't be here...)
  149. // we want to ensure that the following:
  150. // 'FOO bar' is rewritten as 'foo bar', and not as 'foO bar'
  151. // The code was written in such a way that the first word in uppercase
  152. // ends when if finds an uppercase letter followed by a lowercase letter.
  153. // now a ' ' (space, (char)32) is considered not upper
  154. // but in that case we still want our current character to become lowercase
  155. if (char.IsSeparator(chars[i + 1]))
  156. {
  157. chars[i] = ToLower(chars[i]);
  158. }
  159. break;
  160. }
  161. chars[i] = ToLower(chars[i]);
  162. }
  163. return new string(chars);
  164. }
  165. private static char ToLower(char c)
  166. {
  167. #if HAVE_CHAR_TO_STRING_WITH_CULTURE
  168. c = char.ToLower(c, CultureInfo.InvariantCulture);
  169. #else
  170. c = char.ToLowerInvariant(c);
  171. #endif
  172. return c;
  173. }
  174. internal enum SnakeCaseState
  175. {
  176. Start,
  177. Lower,
  178. Upper,
  179. NewWord
  180. }
  181. public static string ToSnakeCase(string s)
  182. {
  183. if (string.IsNullOrEmpty(s))
  184. {
  185. return s;
  186. }
  187. StringBuilder sb = new StringBuilder();
  188. SnakeCaseState state = SnakeCaseState.Start;
  189. for (int i = 0; i < s.Length; i++)
  190. {
  191. if (s[i] == ' ')
  192. {
  193. if (state != SnakeCaseState.Start)
  194. {
  195. state = SnakeCaseState.NewWord;
  196. }
  197. }
  198. else if (char.IsUpper(s[i]))
  199. {
  200. switch (state)
  201. {
  202. case SnakeCaseState.Upper:
  203. bool hasNext = (i + 1 < s.Length);
  204. if (i > 0 && hasNext)
  205. {
  206. char nextChar = s[i + 1];
  207. if (!char.IsUpper(nextChar) && nextChar != '_')
  208. {
  209. sb.Append('_');
  210. }
  211. }
  212. break;
  213. case SnakeCaseState.Lower:
  214. case SnakeCaseState.NewWord:
  215. sb.Append('_');
  216. break;
  217. }
  218. char c;
  219. //#if HAVE_CHAR_TO_LOWER_WITH_CULTURE
  220. c = char.ToLower(s[i], CultureInfo.InvariantCulture);
  221. //#else
  222. // c = char.ToLowerInvariant(s[i]);
  223. //#endif
  224. sb.Append(c);
  225. state = SnakeCaseState.Upper;
  226. }
  227. else if (s[i] == '_')
  228. {
  229. sb.Append('_');
  230. state = SnakeCaseState.Start;
  231. }
  232. else
  233. {
  234. if (state == SnakeCaseState.NewWord)
  235. {
  236. sb.Append('_');
  237. }
  238. sb.Append(s[i]);
  239. state = SnakeCaseState.Lower;
  240. }
  241. }
  242. return sb.ToString();
  243. }
  244. public static bool IsHighSurrogate(char c)
  245. {
  246. return char.IsHighSurrogate(c);
  247. // return (c >= 55296 && c <= 56319);
  248. }
  249. public static bool IsLowSurrogate(char c)
  250. {
  251. return char.IsLowSurrogate(c);
  252. //return (c >= 56320 && c <= 57343);
  253. }
  254. public static bool StartsWith(this string source, char value)
  255. {
  256. return (source.Length > 0 && source[0] == value);
  257. }
  258. public static bool EndsWith(this string source, char value)
  259. {
  260. return (source.Length > 0 && source[source.Length - 1] == value);
  261. }
  262. public static string Trim(this string s, int start, int length)
  263. {
  264. // References: https://referencesource.microsoft.com/#mscorlib/system/string.cs,2691
  265. // https://referencesource.microsoft.com/#mscorlib/system/string.cs,1226
  266. if (s == null)
  267. {
  268. throw new ArgumentNullException();
  269. }
  270. if (start < 0)
  271. {
  272. throw new ArgumentOutOfRangeException(nameof(start));
  273. }
  274. if (length < 0)
  275. {
  276. throw new ArgumentOutOfRangeException(nameof(length));
  277. }
  278. int end = start + length - 1;
  279. if (end >= s.Length)
  280. {
  281. throw new ArgumentOutOfRangeException(nameof(length));
  282. }
  283. for (; start < end; start++)
  284. {
  285. if (!char.IsWhiteSpace(s[start]))
  286. {
  287. break;
  288. }
  289. }
  290. for (; end >= start; end--)
  291. {
  292. if (!char.IsWhiteSpace(s[end]))
  293. {
  294. break;
  295. }
  296. }
  297. return s.Substring(start, end - start + 1);
  298. }
  299. }
  300. }