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.

1371 lines
49 KiB

5 years ago
3 years ago
5 years ago
5 years ago
5 years ago
5 years ago
3 years ago
5 years ago
5 years ago
5 years ago
5 years ago
4 years ago
5 years ago
5 years ago
4 years ago
4 years ago
5 years ago
4 years ago
5 years ago
5 years ago
3 years ago
5 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
3 years ago
5 years ago
5 years ago
4 years ago
5 years ago
4 years ago
5 years ago
4 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
4 years ago
5 years ago
5 years ago
4 years ago
5 years ago
  1. using Apewer.Internals;
  2. using System;
  3. using System.Collections;
  4. using System.Collections.Generic;
  5. using System.Globalization;
  6. using System.Text;
  7. using System.Text.RegularExpressions;
  8. namespace Apewer
  9. {
  10. /// <summary>文本实用工具。</summary>
  11. public static class TextUtility
  12. {
  13. const string LetterChars = LowerCase + UpperCase;
  14. const string BlankChars = "  \n\r\t\f\b\a"; // 在 IsBlank 和 Trim 中视为空白的字符。
  15. const string LineFeed = "\r\n"; // 换行符,由 ASCII 13 和 ASCII 10 组成。
  16. /// <summary>半角空格。</summary>
  17. public const string Space = " ";
  18. /// <summary>全角空格。</summary>
  19. public const string SpaceSbc = " ";
  20. /// <summary>十进制字符。</summary>
  21. public const string Decimal = "0123456789";
  22. /// <summary>十六进制字符。</summary>
  23. public const string Hexadecimal = "0123456789abcdef";
  24. /// <summary>小写字母。</summary>
  25. public const string LowerCase = "abcdefghijklmnopqrstuvwxyz";
  26. /// <summary>大写字母。</summary>
  27. public const string UpperCase = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
  28. /// <summary>UTF-8 BOM。</summary>
  29. public static byte[] Bom { get => new byte[] { 0xEF, 0xBB, 0xBF }; }
  30. /// <summary>CRLF。</summary>
  31. public const string CRLF = "\r\n";
  32. /// <summary>CR。</summary>
  33. public const char CR = '\r';
  34. /// <summary>LF。</summary>
  35. public const char LF = '\n';
  36. /// <summary>长度为 0 的空字符串。</summary>
  37. public const string Empty = "";
  38. /// <summary>无效指针。</summary>
  39. public const string Null = null;
  40. /// <summary>返回表示指定对象的字符串。</summary>
  41. public static string Text(object value)
  42. {
  43. if (value is string str) return str;
  44. if (value == null) return null;
  45. if (value.Equals(DBNull.Value)) return null;
  46. if (value is Type t) return t.Name;
  47. if (value is char[] chars) return new string(chars);
  48. var type = value.GetType();
  49. var toString = type.GetMethod(nameof(object.ToString), Type.EmptyTypes);
  50. if (toString.DeclaringType.Equals(type))
  51. {
  52. try { return value.ToString(); }
  53. catch { return null; }
  54. }
  55. return "<" + type.Name + ">";
  56. }
  57. /// <summary>字符串为空。</summary>
  58. public static bool IsEmpty(string text) => text == null || text == Empty;
  59. /// <summary>字符串不为空。</summary>
  60. public static bool NotEmpty(string text) => text != null && text != Empty;
  61. /// <summary>字符串为空,或只含有空白字符。</summary>
  62. public static bool IsBlank(string text)
  63. {
  64. if (IsEmpty(text)) return true;
  65. var length = text.Length;
  66. var bcs = BlankChars.ToCharArray();
  67. var bcl = bcs.Length;
  68. bool b;
  69. char c;
  70. for (var i = 0; i < length; i++)
  71. {
  72. c = text[i];
  73. b = false;
  74. for (var j = 0; j < bcl; j++)
  75. {
  76. if (c == bcs[j])
  77. {
  78. b = true;
  79. break;
  80. }
  81. }
  82. if (!b) return false;
  83. }
  84. return true;
  85. }
  86. /// <summary>字符串不为空,切含有非空白字符。</summary>
  87. public static bool NotBlank(string text) => !IsBlank(text);
  88. /// <summary>获取文本的 Int32 哈希。</summary>
  89. private static int HashCode(string text)
  90. {
  91. if (text == null) return 0;
  92. int hash = 0;
  93. var length = text.Length;
  94. for (int i = 0; i < length; i++)
  95. {
  96. hash = 31 * hash + text[i];
  97. }
  98. return hash;
  99. }
  100. private static string PrivateJoin(string separator, IEnumerable cells)
  101. {
  102. if (cells == null) return Empty;
  103. if (cells is string str) return str ?? "";
  104. var sb = new StringBuilder();
  105. var first = true;
  106. var hasSeparator = !string.IsNullOrEmpty(separator);
  107. foreach (var cell in cells)
  108. {
  109. if (cell == null) continue;
  110. var text = null as string;
  111. if (cell is string) text = cell as string;
  112. else if (cell is Type type) text = type.Name;
  113. else text = cell.ToString();
  114. if (string.IsNullOrEmpty(text)) continue;
  115. if (!first && hasSeparator) sb.Append(separator);
  116. first = false;
  117. sb.Append(text);
  118. }
  119. var result = sb.ToString();
  120. return result;
  121. }
  122. /// <summary>合并为字符串。</summary>
  123. public static string Merge(params object[] cells) => Join(null, cells);
  124. /// <summary>合并为字符串。</summary>
  125. public static string Join(string separator, params object[] cells)
  126. {
  127. if (cells == null) return Empty;
  128. while (cells.Length == 1)
  129. {
  130. var first = cells[0];
  131. if (first.IsNull()) return Empty;
  132. if (first is string str) return str ?? Empty;
  133. if (first is IEnumerable<char> chars)
  134. {
  135. var list = new List<char>();
  136. foreach (var @char in chars) list.Add(@char);
  137. return new string(list.ToArray());
  138. }
  139. if (!first.GetType().IsValueType && first is IEnumerable enumerable)
  140. {
  141. var list = new List<object>();
  142. foreach (var item in enumerable) list.Add(item);
  143. cells = list.ToArray();
  144. continue;
  145. }
  146. break;
  147. }
  148. {
  149. var sb = new StringBuilder();
  150. var first = true;
  151. var hasSeparator = !string.IsNullOrEmpty(separator);
  152. foreach (var cell in cells)
  153. {
  154. if (cell.IsNull()) continue;
  155. var text = null as string;
  156. if (cell is string str) text = str;
  157. else if (cell is char[] chars) text = new string(chars);
  158. else text = Text(cell);
  159. if (string.IsNullOrEmpty(text)) continue;
  160. if (hasSeparator)
  161. {
  162. if (first) first = false;
  163. else sb.Append(separator);
  164. }
  165. sb.Append(text);
  166. }
  167. var result = sb.ToString();
  168. return result;
  169. }
  170. }
  171. /// <summary>重复指定字符,直到达到指定长度。</summary>
  172. /// <param name="cell">要重复的字符。</param>
  173. /// <param name="count">重复的次数。</param>
  174. public static string Duplicate(char cell, int count)
  175. {
  176. if (count < 1) return Empty;
  177. var chars = new char[count];
  178. for (var i = 0; i < count; i++) chars[i] = cell;
  179. return new string(chars);
  180. }
  181. /// <summary>重复指定字符串,直到达到指定长度。</summary>
  182. /// <param name="cell">要重复的字符串。</param>
  183. /// <param name="count">重复的次数。</param>
  184. public static string Duplicate(string cell, int count)
  185. {
  186. if (IsEmpty(cell) || count < 1) return Empty;
  187. var length = cell.Length;
  188. var total = length * count;
  189. var output = new char[total];
  190. var input = cell.ToCharArray();
  191. for (var i = 0; i < count; i++)
  192. {
  193. Array.Copy(input, 0, output, length * i, length);
  194. }
  195. return new string(output);
  196. }
  197. /// <summary>将文本以转换为字节数组。默认 Encoding 为 UTF-8。</summary>
  198. public static byte[] Bytes(string text, Encoding encoding = null)
  199. {
  200. if (text == null || text == Empty) return BytesUtility.Empty;
  201. try { return (encoding ?? Encoding.UTF8).GetBytes(text); }
  202. catch { return BytesUtility.Empty; }
  203. }
  204. /// <summary>将字节数组转换为文本。默认 Encoding 为 UTF-8。</summary>
  205. public static string FromBytes(byte[] bytes, Encoding encoding = null)
  206. {
  207. if (bytes == null || bytes.LongLength < 1L) return Empty;
  208. // 计算非零字节的长度
  209. var count = 0;
  210. for (var i = 0; i < bytes.Length; i++)
  211. {
  212. if (bytes[i] == 0) continue;
  213. count++;
  214. }
  215. if (count < 1) return Empty;
  216. // 转换
  217. if (encoding == null) encoding = Encoding.UTF8;
  218. try
  219. {
  220. var result = encoding.GetString(bytes, 0, count);
  221. return result;
  222. }
  223. catch { return Empty; }
  224. }
  225. /// <summary>将明文文本以 UTF-8 转换为 Base64 文本。</summary>
  226. public static string ToBase64(string plain)
  227. {
  228. if (plain == null || plain == Empty) return Empty;
  229. return BytesUtility.ToBase64(Bytes(plain));
  230. }
  231. /// <summary>将 Base64 文本以 UTF-8 转换为明文文本。</summary>
  232. public static string FromBase64(string cipher)
  233. {
  234. if (cipher == null || cipher == Empty) return Empty;
  235. return FromBytes(BytesUtility.FromBase64(cipher));
  236. }
  237. /// <summary>从字符串中删除子字符串。</summary>
  238. /// <param name="text">要查询的字符串。</param>
  239. /// <param name="sub">子字符串。</param>
  240. /// <param name="ignoreCase">是否忽略大小写。</param>
  241. /// <returns>删除子字符串后的父字符串。</returns>
  242. private static string Exlude(string text, string sub, bool ignoreCase = false)
  243. {
  244. if (string.IsNullOrEmpty(text)) return "";
  245. if (string.IsNullOrEmpty(sub)) return text;
  246. try
  247. {
  248. string vp = text;
  249. string vs = sub;
  250. string vr = "";
  251. int vl;
  252. int vi;
  253. if (ignoreCase)
  254. {
  255. vp = TextHelper.LCase(vp);
  256. vs = TextHelper.LCase(vs);
  257. }
  258. vl = vs.Length;
  259. vi = 1;
  260. while (vi <= (vp.Length - vl + 1))
  261. {
  262. if (TextHelper.Middle(vp, vi, vl) == vs)
  263. {
  264. vi = vi + vl;
  265. }
  266. else
  267. {
  268. vr = vr + TextHelper.Middle(text, vi, 1);
  269. vi = vi + 1;
  270. }
  271. }
  272. return vr;
  273. }
  274. catch { return text; }
  275. }
  276. /// <summary>替换字符串中的子字符串。</summary>
  277. /// <param name="text">要查询的字符串。</param>
  278. /// <param name="new">新子字符串,保留大小写。</param>
  279. /// <param name="old">原子字符串。</param>
  280. /// <param name="ignoreCase">查找时是否忽略父字符串和原子字符串大小写。</param>
  281. /// <returns>替换后的父字符串。</returns>
  282. private static string Replace(string text, string old, string @new, bool ignoreCase = false)
  283. {
  284. if (string.IsNullOrEmpty(text)) return "";
  285. if (string.IsNullOrEmpty(old)) return text;
  286. if (string.IsNullOrEmpty(@new)) return Exlude(text, old, ignoreCase);
  287. if (TextHelper.Len(text) < TextHelper.Len(old)) return text;
  288. if (ignoreCase)
  289. {
  290. try
  291. {
  292. string p = TextHelper.LCase(text);
  293. string o = TextHelper.LCase(old);
  294. int vil = TextHelper.Len(old);
  295. int viv = 1;
  296. int vend = TextHelper.Len(text) - vil + 1;
  297. string vcell;
  298. string vresult = "";
  299. while (viv <= vend)
  300. {
  301. vcell = TextHelper.Middle(p, viv, vil);
  302. if (vcell == o)
  303. {
  304. vresult = vresult + @new;
  305. viv = viv + vil;
  306. }
  307. else
  308. {
  309. vresult = vresult + TextHelper.Middle(text, viv, 1);
  310. viv = viv + 1;
  311. }
  312. }
  313. return vresult;
  314. }
  315. catch { return text; }
  316. }
  317. else
  318. {
  319. try
  320. {
  321. string vresult = text.Replace(old, @new);
  322. return vresult;
  323. }
  324. catch { return ""; }
  325. }
  326. }
  327. /// <summary>修复文本前缀。</summary>
  328. /// <param name="text">原文本。</param>
  329. /// <param name="include">TRUE:追加指定缀;FALSE:去除指定缀。</param>
  330. /// <param name="head">前缀文本。</param>
  331. public static string AssureStarts(string text, string head, bool include = true)
  332. {
  333. if (string.IsNullOrEmpty(text)) return Empty;
  334. if (string.IsNullOrEmpty(head)) return text;
  335. if (include)
  336. {
  337. return text.StartsWith(head) ? text : (head + text);
  338. }
  339. else
  340. {
  341. var headLength = head.Length;
  342. if (!text.StartsWith(head)) return text;
  343. var result = text;
  344. while (true)
  345. {
  346. result = result.Substring(headLength);
  347. if (!result.StartsWith(head)) return result;
  348. }
  349. }
  350. }
  351. /// <summary>修复文本后缀。</summary>
  352. /// <param name="text">原文本。</param>
  353. /// <param name="include">TRUE:追加指定后缀;FALSE:去除指定后缀。</param>
  354. /// <param name="foot">后缀文本。</param>
  355. public static string AssureEnds(string text, string foot, bool include = true)
  356. {
  357. if (string.IsNullOrEmpty(text)) return Empty;
  358. if (string.IsNullOrEmpty(foot)) return text;
  359. if (include == true)
  360. {
  361. return text.EndsWith(foot) ? text : (text + foot);
  362. }
  363. else
  364. {
  365. var footLength = foot.Length;
  366. if (!text.EndsWith(foot)) return text;
  367. var result = text;
  368. while (true)
  369. {
  370. result = result.Substring(0, result.Length - footLength);
  371. if (!result.EndsWith(foot)) return result;
  372. }
  373. }
  374. }
  375. /// <summary>用单字符作为分隔符拆分文本。</summary>
  376. public static string[] Split(string text, char separator)
  377. {
  378. if (text == null) return new string[0];
  379. if (text.Length < 1) return new string[] { "" };
  380. if ((object)separator == null) return new string[] { text };
  381. return text.Split(separator);
  382. }
  383. /// <summary>用字符串作为分隔符拆分文本。</summary>
  384. public static string[] Split(string text, string separator)
  385. {
  386. if (text == null) return new string[0];
  387. if (text.Length < 1) return new string[] { "" };
  388. if (string.IsNullOrEmpty(separator)) return new string[] { text };
  389. if (separator.Length > text.Length) return new string[] { text };
  390. var list = new List<string>();
  391. var position = 0;
  392. var total = text.Length;
  393. var length = separator.Length;
  394. var cell = new StringBuilder();
  395. while (position < total)
  396. {
  397. var read = null as string;
  398. if (position + length < total) read = text.Substring(position, length);
  399. else read = text.Substring(position);
  400. if (read == separator)
  401. {
  402. if (cell.Length > 0)
  403. {
  404. list.Add(cell.ToString());
  405. // cell.Clear();
  406. cell = new StringBuilder();
  407. }
  408. else
  409. {
  410. list.Add("");
  411. }
  412. position += length;
  413. }
  414. else
  415. {
  416. cell.Append((char)text[position]);
  417. position += 1;
  418. }
  419. if (position >= total)
  420. {
  421. list.Add(cell.ToString());
  422. }
  423. }
  424. var array = list.ToArray();
  425. return array;
  426. }
  427. /// <summary>用多个分隔符拆分文本。</summary>
  428. public static string[] Split(string text, params char[] separators)
  429. {
  430. if (text == null) return new string[0];
  431. if (text.Length < 1) return new string[] { "" };
  432. if (separators == null || separators.Length < 1) return new string[] { text };
  433. if (separators.Length == 1) return Split(text, separators[0]);
  434. var list = new List<string>();
  435. var separatorsText = new string(separators);
  436. var sb = new StringBuilder();
  437. foreach (var c in text)
  438. {
  439. if (separatorsText.IndexOf(c) >= 0)
  440. {
  441. list.Add(sb.ToString());
  442. //sb.Clear();
  443. sb = new StringBuilder();
  444. continue;
  445. }
  446. sb.Append(c);
  447. }
  448. list.Add(sb.ToString());
  449. #if !NET20
  450. sb.Clear();
  451. #endif
  452. return list.ToArray();
  453. }
  454. /// <summary>移除字符串前后的空字符。</summary>
  455. /// <param name="text">原始字符串。</param>
  456. /// <param name="trimBlank">移除空格、全角空格、换行符、回车符、制表符和换页符。</param>
  457. public static string Trim(string text, bool trimBlank = false)
  458. {
  459. if (text == null || text == Empty) return Empty;
  460. if (!trimBlank) return Trim(text, ' ');
  461. return Trim(text, BlankChars.ToCharArray());
  462. }
  463. /// <summary>移除字符串前后的指定字符。</summary>
  464. /// <param name="text">原始字符串。</param>
  465. /// <param name="chars">要移除的字符。</param>
  466. public static string Trim(string text, params char[] chars)
  467. {
  468. if (text == null || text == Empty) return Empty;
  469. if (chars == null || chars.Length < 1) return text;
  470. var length = text.Length;
  471. var charsLength = chars.Length;
  472. var starts = 0;
  473. var offset = 0;
  474. while (true)
  475. {
  476. if (offset >= length) break;
  477. var c = text[offset];
  478. var trim = false;
  479. for (var i = 0; i < charsLength; i++)
  480. {
  481. if (c == chars[i])
  482. {
  483. starts += 1;
  484. offset += 1;
  485. trim = true;
  486. break;
  487. }
  488. }
  489. if (trim) continue; else break;
  490. }
  491. var ends = 0;
  492. if (starts < length)
  493. {
  494. offset = length - 1;
  495. while (true)
  496. {
  497. if (offset <= starts) break;
  498. var c = text[offset];
  499. var trim = false;
  500. for (var i = 0; i < charsLength; i++)
  501. {
  502. if (c == chars[i])
  503. {
  504. ends += 1;
  505. offset -= 1;
  506. trim = true;
  507. break;
  508. }
  509. }
  510. if (trim) continue; else break;
  511. }
  512. }
  513. if (starts == 0 && ends == 0) return text;
  514. return text.Substring(starts, length - starts - ends);
  515. }
  516. /// <summary>修剪字符串数组,修剪元素,并去除空字符串。</summary>
  517. public static string[] Trim(this IEnumerable<string> strings)
  518. {
  519. var ab = new ArrayBuilder<string>();
  520. if (strings == null) return ab.Export();
  521. foreach (var str in strings)
  522. {
  523. var trim = Trim(str);
  524. if (string.IsNullOrEmpty(trim)) continue;
  525. ab.Add(trim);
  526. }
  527. var array = ab.Export();
  528. return array;
  529. }
  530. /// <summary>剪取文本内容,若指定头部为空则从原文本首部起,若指定尾部为空则至原文本末尾。</summary>
  531. /// <returns>剪取后的内容,不包含 head 和 foot。</returns>
  532. public static string Cut(string text, string head = null, string foot = null)
  533. {
  534. if (IsEmpty(text)) return Empty;
  535. int start, length;
  536. if (IsEmpty(head))
  537. {
  538. // none
  539. if (IsEmpty(foot)) return Empty;
  540. // foot
  541. length = text.IndexOf(foot);
  542. if (length == 0) return Empty;
  543. if (length < 0) return text;
  544. return text.Substring(0, length);
  545. }
  546. else
  547. {
  548. if (IsEmpty(foot))
  549. {
  550. // head
  551. start = text.IndexOf(head);
  552. if (start < 0) return text;
  553. start += head.Length;
  554. return text.Substring(start);
  555. }
  556. // both
  557. start = text.IndexOf(head);
  558. if (start < 0) start = 0;
  559. else start += head.Length;
  560. var temp = start == 0 ? text : text.Substring(start);
  561. length = temp.IndexOf(foot);
  562. if (length < 0) return temp;
  563. if (length == 0) return Empty;
  564. return temp.Substring(0, length);
  565. }
  566. }
  567. /// <summary>比较两个字符串的相似度。返回值大于 0,小于等于 1。</summary>
  568. /// <param name="arg1"></param>
  569. /// <param name="arg2"></param>
  570. /// <returns></returns>
  571. public static double Similarity(string arg1, string arg2) => Levenshtein.Compute(arg1, arg2).Rate;
  572. /// <summary>生成新的 GUID。</summary>
  573. public static string Guid(bool hyphenation = true, bool lower = true)
  574. {
  575. var guid = System.Guid.NewGuid();
  576. var str = hyphenation ? guid.ToString() : guid.ToString("n");
  577. if (!lower) str = str.ToUpper();
  578. return str;
  579. }
  580. /// <summary>生成新主键。</summary>
  581. public static string Key() => System.Guid.NewGuid().ToString("n");
  582. /// <summary>生成随机字符串,出现的字符由字符池指定,默认池包含数字和字母。</summary>
  583. /// <param name="length">随机字符串的长度。</param>
  584. /// <param name="pool">字符池,字符池中每个字符在随机字符串中出现的概率约等。</param>
  585. public static string Random(int length, string pool = "0123456789abcdefghijklmnopqrstuvwxyz")
  586. {
  587. if (length < 1) return Empty;
  588. if (IsEmpty(pool)) return Duplicate(Space, length);
  589. var array = new char[length];
  590. var max = pool.Length - 1;
  591. for (var i = 0; i < length; i++) array[i] = pool[NumberUtility.Random(0, max)];
  592. return new string(array);
  593. }
  594. /// <summary>对字符串集合去重,同时去除 NULL 值和空字符串。</summary>
  595. public static string[] Distinct(this IEnumerable<string> strings) => Distinct(strings, false, false);
  596. /// <summary>对字符串集合去重。</summary>
  597. /// <param name="strings">字符串集合。</param>
  598. /// <param name="withEmpty">保留空字符串。</param>
  599. /// <param name="withNull">保留 NULL 值。</param>
  600. public static string[] Distinct(this IEnumerable<string> strings, bool withEmpty, bool withNull)
  601. {
  602. if (strings == null) return new string[0];
  603. // 保留 NULL 和空字符串
  604. var @null = false;
  605. var empty = false;
  606. // 遍历,重组
  607. var cache = new List<string>();
  608. if (strings is IList<string> list)
  609. {
  610. var count = list.Count;
  611. cache.Capacity = count;
  612. for (var i = 0; i < count; i++)
  613. {
  614. var item = list[i];
  615. if (item == null)
  616. {
  617. if (withNull && !@null)
  618. {
  619. cache.Add(item);
  620. @null = true;
  621. }
  622. continue;
  623. }
  624. if (item == Empty)
  625. {
  626. if (withEmpty && !empty)
  627. {
  628. cache.Add(item);
  629. empty = true;
  630. }
  631. continue;
  632. }
  633. var added = false;
  634. for (var j = 0; j < cache.Count; j++)
  635. {
  636. if (cache[j] == item)
  637. {
  638. added = true;
  639. break;
  640. }
  641. }
  642. if (!added) cache.Add(item);
  643. }
  644. }
  645. else
  646. {
  647. foreach (var item in strings)
  648. {
  649. if (item == null)
  650. {
  651. if (withNull && !@null)
  652. {
  653. cache.Add(item);
  654. @null = true;
  655. }
  656. continue;
  657. }
  658. if (item == Empty)
  659. {
  660. if (withEmpty && !empty)
  661. {
  662. cache.Add(item);
  663. empty = true;
  664. }
  665. continue;
  666. }
  667. var added = false;
  668. for (var j = 0; j < cache.Count; j++)
  669. {
  670. if (cache[j] == item)
  671. {
  672. added = true;
  673. break;
  674. }
  675. }
  676. if (!added) cache.Add(item);
  677. }
  678. }
  679. return cache.ToArray();
  680. }
  681. /// <summary>约束字符串中的字符,只包含指定的字符。</summary>
  682. public static string Restrict(string text, char[] chars)
  683. {
  684. if (IsEmpty(text)) return Empty;
  685. if (chars == null || chars.Length < 1) return Empty;
  686. var total = text.Length;
  687. var count = chars.Length;
  688. var array = new char[total];
  689. var added = 0;
  690. for (var i = 0; i < total; i++)
  691. {
  692. var c = text[i];
  693. for (var j = 0; j < count; j++)
  694. {
  695. if (c == chars[j])
  696. {
  697. array[added] = c;
  698. added += 1;
  699. break;
  700. }
  701. }
  702. }
  703. if (added < 1) return Empty;
  704. return new string(array, 0, added);
  705. }
  706. /// <summary>约束字符串中的字符,只包含指定的字符。</summary>
  707. public static string Restrict(string text, string chars) => IsEmpty(text) || IsEmpty(chars) ? Empty : Restrict(text, chars.ToCharArray());
  708. /// <summary>约束字符串中的字符,只包含字母。</summary>
  709. public static string RestrictLetters(string text) => Restrict(text, LetterChars.ToCharArray());
  710. /// <summary>约束字符串中的字符,只包含数字。</summary>
  711. public static string RestrictNumeric(string text) => Restrict(text, Decimal.ToCharArray());
  712. /// <summary>返回此字符串的安全键副本,只保留数据记录主键中可能出现的字符,默认限制长度为 255 字符。</summary>
  713. public static string SafeKey(string text, int maxLength = 255)
  714. {
  715. if (string.IsNullOrEmpty(text)) return Empty;
  716. var input = text;
  717. var max = maxLength;
  718. if (max < 1 || max > input.Length) max = input.Length;
  719. // 允许用于主键值的字符。
  720. const string KeyCollection = "-_0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
  721. var sb = new StringBuilder();
  722. var total = input.Length;
  723. var length = 0;
  724. for (var i = 0; i < total; i++)
  725. {
  726. var c = input[i];
  727. if (KeyCollection.IndexOf(c) < 0) continue;
  728. sb.Append(c);
  729. length += 1;
  730. if (length >= max) break;
  731. }
  732. var result = sb.ToString();
  733. if (result.Length > max) result = result.Substring(0, max);
  734. return result;
  735. }
  736. /// <summary>追加字符串。</summary>
  737. public static StringBuilder Append(StringBuilder builder, params object[] cells)
  738. {
  739. if (builder != null) builder.Append(Join(null, cells));
  740. return builder;
  741. }
  742. /// <summary>对 URL 编码。</summary>
  743. public static string EncodeUrl(string plain)
  744. {
  745. return UrlEncoding.Encode(plain);
  746. }
  747. /// <summary>对 URL 解码。</summary>
  748. public static string DecodeUrl(string escaped)
  749. {
  750. return UrlEncoding.Decode(escaped);
  751. }
  752. /// <summary>返回此字符串转换为小写形式的副本。</summary>
  753. public static string Lower(string text)
  754. {
  755. if (text == null) return null;
  756. else if (text.Length < 1) return Empty;
  757. else return text.ToLower();
  758. }
  759. /// <summary>返回此字符串转换为大写形式的副本。</summary>
  760. public static string Upper(string text)
  761. {
  762. if (text == null) return null;
  763. else if (text.Length < 1) return Empty;
  764. else return text.ToUpper();
  765. }
  766. /// <summary>检查中国手机号码,包含 13x、14x、15x、16x、17x、18x 和 19x 号段。</summary>
  767. public static bool IsPhone(string phone)
  768. {
  769. if (string.IsNullOrEmpty(phone)) return false;
  770. var regex = new Regex(@"^(13|14|15|16|17|18|19)\d{9}$", RegexOptions.None);
  771. var match = regex.Match(phone);
  772. return match.Success;
  773. }
  774. /// <summary>渲染 Markdown 文本为 HTML 文本。</summary>
  775. public static string RenderMarkdown(string markdown) => MarkdownSharp.Demo.ToHtml(markdown);
  776. /// <summary>合并用于启动进程的参数。</summary>
  777. public static string MergeProcessArgument(params object[] args)
  778. {
  779. // var special = " \"\n\r\b\t\f";
  780. var list = new List<string>();
  781. if (args != null)
  782. {
  783. foreach (var i in args)
  784. {
  785. var arg = null as string;
  786. if (i != null)
  787. {
  788. if (i is string) arg = i as string;
  789. else arg = i.ToString();
  790. }
  791. if (string.IsNullOrEmpty(arg))
  792. {
  793. list.Add("\"\"");
  794. continue;
  795. }
  796. if (arg.Contains(" ") || arg.Contains("\""))
  797. {
  798. list.Add(Merge("\"", arg.Replace("\"", "\\\""), "\""));
  799. continue;
  800. }
  801. list.Add(arg);
  802. }
  803. }
  804. var result = Join(" ", list.ToArray());
  805. return result;
  806. }
  807. /// <summary>合并用于启动进程的参数。</summary>
  808. private static string MergeProcessArgument_2(params object[] args)
  809. {
  810. if (args == null) return "";
  811. if (args.Length < 1) return "";
  812. var sb = new StringBuilder();
  813. for (var i = 0; i < args.Length; i++)
  814. {
  815. if (i > 0) sb.Append(" ");
  816. var arg = null as string;
  817. if (args[i] != null)
  818. {
  819. if (args[i] is string) arg = args[i] as string;
  820. else arg = args[i].ToString();
  821. }
  822. if (arg.IsEmpty())
  823. {
  824. sb.Append("\"\"");
  825. continue;
  826. }
  827. // var special = " \"\n\r\b\t\f";
  828. var special = " \"";
  829. if (arg.IndexOfAny(special.ToCharArray()) < 0)
  830. {
  831. sb.Append(arg);
  832. }
  833. else
  834. {
  835. sb.Append("\"");
  836. if (arg.NotEmpty())
  837. {
  838. foreach (var c in arg)
  839. {
  840. switch (c)
  841. {
  842. case '"':
  843. sb.Append("\\\"");
  844. break;
  845. // case '\n':
  846. // sb.Append("\\n");
  847. // break;
  848. // case '\r':
  849. // sb.Append("\\r");
  850. // break;
  851. // case '\b':
  852. // sb.Append("\\b");
  853. // break;
  854. // case '\t':
  855. // sb.Append("\\t");
  856. // break;
  857. default:
  858. sb.Append(c);
  859. break;
  860. }
  861. }
  862. }
  863. sb.Append("\"");
  864. }
  865. }
  866. var result = sb.ToString();
  867. return result;
  868. }
  869. /// <summary>字符串仅使用英文。可指定空字符串的返回值。</summary>
  870. public static bool IsEnglish(string text, bool ifEmpty = true)
  871. {
  872. if (string.IsNullOrEmpty(text)) return ifEmpty;
  873. var trim = text.Trim();
  874. if (string.IsNullOrEmpty(trim)) return ifEmpty;
  875. return Regex.IsMatch(trim, "(^[0-9a-zA-Z_ -;:,~!@#%&=<>~\\(\\)\\.\\$\\^\\`\\'\\\"\\&\\{\\}\\[\\]\\|\\*\\+\\?]{0,80}$)");
  876. }
  877. /// <summary>转换文本为驼峰形式,首字小写,其它词首字母大写。</summary>
  878. /// <remarks>示例:toCamelCase</remarks>
  879. public static string Camel(this string text)
  880. {
  881. if (string.IsNullOrEmpty(text) || !char.IsUpper(text[0])) return text;
  882. var chars = text.ToCharArray();
  883. for (int i = 0; i < chars.Length; i++)
  884. {
  885. if (i == 1 && !char.IsUpper(chars[i])) break;
  886. bool hasNext = (i + 1) < chars.Length;
  887. if (i > 0 && hasNext)
  888. {
  889. var nextChar = chars[i + 1];
  890. if (!char.IsUpper(nextChar))
  891. {
  892. if (char.IsSeparator(nextChar)) chars[i] = char.ToLowerInvariant(chars[i]);
  893. break;
  894. }
  895. }
  896. chars[i] = char.ToLowerInvariant(chars[i]);
  897. }
  898. return new string(chars);
  899. }
  900. /// <summary>转换文本为帕斯卡形式,所有词首字母大写。</summary>
  901. /// <remarks>示例:ToPascalCase</remarks>
  902. public static string Pascal(this string text)
  903. {
  904. if (string.IsNullOrEmpty(text)) return text;
  905. var sb = new StringBuilder();
  906. var snake = Snake(text);
  907. var split = snake.Split('_');
  908. foreach (var word in split)
  909. {
  910. if (word.Length < 1) continue;
  911. var chars = word.ToCharArray();
  912. chars[0] = char.ToUpper(chars[0]);
  913. sb.Append(new string(chars));
  914. }
  915. return sb.ToString();
  916. }
  917. /// <summary>转换文本为串式形式,使用横线连接每个词。</summary>
  918. /// <remarks>示例:to-kebab-case</remarks>
  919. public static string Kebab(this string text) => WordCase(text, '-');
  920. /// <summary>转换文本为蛇形形式,使用下划线连接每个词。</summary>
  921. /// <remarks>示例:to_sname_case</remarks>
  922. public static string Snake(this string text) => WordCase(text, '_');
  923. /// <summary>转换单词风格。</summary>
  924. static string WordCase(string text, char separator)
  925. {
  926. if (string.IsNullOrEmpty(text)) return text;
  927. const int StateStart = 1;
  928. const int StateLower = 2;
  929. const int StateUpper = 3;
  930. const int StateNewWord = 4;
  931. var sb = new StringBuilder();
  932. var state = StateStart;
  933. for (int i = 0; i < text.Length; i++)
  934. {
  935. if (text[i] == ' ')
  936. {
  937. if (state != StateStart)
  938. {
  939. state = StateNewWord;
  940. }
  941. }
  942. else if (char.IsUpper(text[i]))
  943. {
  944. switch (state)
  945. {
  946. case StateUpper:
  947. bool hasNext = (i + 1 < text.Length);
  948. if (i > 0 && hasNext)
  949. {
  950. char nextChar = text[i + 1];
  951. if (!char.IsUpper(nextChar) && nextChar != separator)
  952. {
  953. sb.Append(separator);
  954. }
  955. }
  956. break;
  957. case StateLower:
  958. case StateNewWord:
  959. sb.Append(separator);
  960. break;
  961. }
  962. char c;
  963. c = char.ToLower(text[i], CultureInfo.InvariantCulture);
  964. sb.Append(c);
  965. state = StateUpper;
  966. }
  967. else if (text[i] == separator)
  968. {
  969. sb.Append(separator);
  970. state = StateStart;
  971. }
  972. else
  973. {
  974. if (state == StateNewWord) sb.Append(separator);
  975. sb.Append(text[i]);
  976. state = StateLower;
  977. }
  978. }
  979. return sb.ToString();
  980. }
  981. /// <summary>移除参数 chars 中的每一个字符。</summary>
  982. public static string RemoveChars(string text, string chars)
  983. {
  984. if (IsEmpty(text)) return Empty;
  985. if (IsEmpty(chars)) return text;
  986. return RemoveChar(text, chars.ToCharArray());
  987. }
  988. /// <summary>移除指定的一个或多个字符。</summary>
  989. public static string RemoveChar(string text, params char[] chars)
  990. {
  991. if (IsEmpty(text)) return Empty;
  992. if (chars == null || chars.Length < 1) return text;
  993. var length = text.Length;
  994. var array = new char[length];
  995. var count = chars.Length;
  996. var added = 0;
  997. for (var i = 0; i < length; i++)
  998. {
  999. var c = text[i];
  1000. var removed = false;
  1001. for (var j = 0; j < count; j++)
  1002. {
  1003. if (c == chars[j])
  1004. {
  1005. removed = true;
  1006. break;
  1007. }
  1008. }
  1009. if (removed) continue;
  1010. array[added] = c;
  1011. added += 1;
  1012. }
  1013. if (added < 1) return Empty;
  1014. return new string(array, 0, added);
  1015. }
  1016. /// <summary>防注入,去除常见的功能符号。可限定字符串长度。</summary>
  1017. public static string AntiInject(string text, int length = -1, string chars = "\"'`\b\f\n\r\t\\/:*?<>|@")
  1018. {
  1019. if (IsEmpty(text) || length == 0) return Empty;
  1020. var t = Trim(text);
  1021. t = RemoveChars(t, chars);
  1022. if (length > 0 && t.Length > length) t = t.Substring(0, length);
  1023. return t;
  1024. }
  1025. /// <summary>获取指定长度的字符串片段,可指定 trim 参数对片段再次修剪。</summary>
  1026. public static string Left(string text, int maxLength, bool trim = false, bool trimBlank = false)
  1027. {
  1028. if (IsEmpty(text)) return Empty;
  1029. if (maxLength > 0 && text.Length > maxLength)
  1030. {
  1031. var left = text.Substring(0, maxLength);
  1032. return trim ? Trim(left, trimBlank) : left;
  1033. }
  1034. else return trim ? Trim(text, trimBlank) : text;
  1035. }
  1036. /// <summary>获取指定长度的字符串片段,可指定 trim 参数对片段再次修剪。</summary>
  1037. public static string Right(string text, int maxLength, bool trim = false, bool trimBlank = false)
  1038. {
  1039. if (IsEmpty(text)) return Empty;
  1040. if (maxLength > 0 && text.Length > maxLength)
  1041. {
  1042. var left = text.Substring(text.Length - maxLength);
  1043. return trim ? Trim(left, trimBlank) : left;
  1044. }
  1045. else return trim ? Trim(text, trimBlank) : text;
  1046. }
  1047. /// <summary>获取字符串片段,起始位置从 0 开始,可指定 trim 参数对片段再次修剪。</summary>
  1048. /// <exception cref="ArgumentOutOfRangeException"></exception>
  1049. public static string Middle(string text, int startIndex, int maxLength = -1, bool trim = false, bool trimBlank = false)
  1050. {
  1051. if (IsEmpty(text) || maxLength == 0) return Empty;
  1052. var total = text.Length;
  1053. var start = startIndex;
  1054. var length = maxLength;
  1055. if (start < 0)
  1056. {
  1057. length = length + start;
  1058. start = 0;
  1059. }
  1060. if (maxLength < 0) length = total;
  1061. if (start + length > total) length = total - start;
  1062. if (start == 0 && length == total) return trim ? Trim(text, trimBlank) : text;
  1063. var middle = text.Substring(start, length);
  1064. return trim ? Trim(middle, trimBlank) : middle;
  1065. }
  1066. #region Lock
  1067. static TextLocker _locker = new TextLocker();
  1068. /// <summary>锁定文本,在锁中执行函数。</summary>
  1069. /// <param name="text">要锁定的文本。</param>
  1070. /// <param name="inLock">要在锁中执行的函数。</param>
  1071. /// <exception cref="ArgumentNullException"></exception>
  1072. public static T Lock<T>(this string text, Func<T> inLock)
  1073. {
  1074. if (inLock == null) throw new ArgumentNullException(nameof(inLock));
  1075. return _locker.InLock(text, inLock);
  1076. }
  1077. /// <summary>锁定文本,在锁中执行函数。</summary>
  1078. /// <param name="text">要锁定的文本。</param>
  1079. /// <param name="inLock">要在锁中执行的函数。</param>
  1080. /// <exception cref="ArgumentNullException"></exception>
  1081. public static void Lock(this string text, Action inLock)
  1082. {
  1083. if (inLock == null) throw new ArgumentNullException(nameof(inLock));
  1084. _locker.InLock(text, inLock);
  1085. }
  1086. #endregion
  1087. #region encoding
  1088. /// <summary>检查字节数组包含 UTF-8 BOM 头。</summary>
  1089. public static bool ContainsBOM(byte[] bytes)
  1090. {
  1091. if (bytes == null) return false;
  1092. if (bytes.LongLength < 3L) return false;
  1093. return bytes[0L] == 0xEF && bytes[1L] == 0xBB && bytes[2L] == 0xBF;
  1094. }
  1095. /// <summary>检查字节数组是 UTF-8 文本。可指定检查的最大字节长度。</summary>
  1096. /// <param name="bytes">要检查的字节数组。</param>
  1097. /// <param name="offset">已检查的偏移量。</param>
  1098. /// <param name="checkLength">检查的最大字节长度。</param>
  1099. public static bool IsUTF8(byte[] bytes, Class<int> offset, int checkLength = 1048576)
  1100. {
  1101. return IsUTF8(bytes, offset, checkLength);
  1102. }
  1103. /// <summary>检查字节数组是 UTF-8 文本,默认最多检测 1MB 数据。</summary>
  1104. /// <param name="bytes">要检查的字节数组。</param>
  1105. /// <param name="checkLength">检查的最大字节长度,指定为 0 将不限制检查长度。</param>
  1106. /// <param name="offset">已检查的偏移量,用于调试。</param>
  1107. public static bool IsUTF8(byte[] bytes, int checkLength = 1048576, Class<int> offset = null)
  1108. {
  1109. // UTF8在Unicode的基础上制定了这样一套规则:
  1110. // 1.对于单字节字符,比特位的最高位为0;
  1111. // 2.对于多字节字符,第一个字节的比特位中,最高位有n个1,剩下的n - 1个字节的比特位中,最高位都是10。
  1112. // 好了,我知道你一定看不懂,那就先来看看下面例子后,再去看上面定义吧。
  1113. // 比如一个字符(“A”),它在UTF8中的编码为(用二进制表示):01000001。由于比特位的最高位是0,表示它是单字节,它只需要1个字节就可以表示。
  1114. // 再比如一个字符(“判”),它在UTF8中的编码为(用二进制表示):11100101 10001000 10100100。由于在第一个字节中,比特位最高位有3个1,说明这个字符总共需要3个字节来表示,且后3 - 1 = 2位字节中,比特位的最高位为10。
  1115. if (bytes == null) return false;
  1116. var length = bytes.LongLength;
  1117. // 检查 BOM 头。
  1118. if (ContainsBOM(bytes)) return true;
  1119. var hasOffset = offset != null;
  1120. var append = 0;
  1121. if (hasOffset) offset.Value = 0;
  1122. for (int i = 0; i < length; i++)
  1123. {
  1124. if (checkLength > 0 && i >= checkLength) break;
  1125. var b = bytes[i];
  1126. if (hasOffset) offset.Value = i;
  1127. // 追加字节最高位为 0。
  1128. if (append > 0)
  1129. {
  1130. if (b >> 6 != 2) return false;
  1131. append -= 1;
  1132. continue;
  1133. }
  1134. // ASCII 字符。
  1135. if (b < 128) continue;
  1136. // 2 字节 UTF-8。
  1137. if (b >= 0xC0 && b <= 0xDF)
  1138. {
  1139. append = 1;
  1140. continue;
  1141. }
  1142. // 3 字节 UTF-8 字符。
  1143. if (b >= 0xE0 && b <= 0xEF)
  1144. {
  1145. append = 2;
  1146. continue;
  1147. }
  1148. // 4 字节 UTF-8 字符。
  1149. if (b >= 0xF0 && b <= 0xF7)
  1150. {
  1151. append = 3;
  1152. continue;
  1153. }
  1154. // 5 字节 UTF-8 字符。
  1155. if (b >= 0xF8 && b <= 0xFB)
  1156. {
  1157. append = 4;
  1158. continue;
  1159. }
  1160. // 6 字节 UTF-8 字符。
  1161. if (b >= 0xFC && b <= 0xFD)
  1162. {
  1163. append = 5;
  1164. continue;
  1165. }
  1166. // 未知字节,非 UTF-8 定义。
  1167. return false;
  1168. }
  1169. return true;
  1170. }
  1171. /// <summary>解析编码名称。</summary>
  1172. /// <returns>解析失败时,返回 NULL 值。</returns>
  1173. private static Encoding ParseEncoding(string encoding)
  1174. {
  1175. if (encoding.IsEmpty()) return null;
  1176. var lower = encoding.Lower();
  1177. var nick = lower.Replace("-", "");
  1178. switch (nick)
  1179. {
  1180. case "ascii":
  1181. return Encoding.ASCII;
  1182. case "bigendia":
  1183. case "bigendianunicode":
  1184. return Encoding.BigEndianUnicode;
  1185. case "utf7":
  1186. return Encoding.UTF7;
  1187. case "utf8":
  1188. return Encoding.UTF8;
  1189. case "utf16":
  1190. case "unicode":
  1191. return Encoding.Unicode;
  1192. case "utf32":
  1193. return Encoding.UTF7;
  1194. case "default":
  1195. return Encoding.Default;
  1196. case "ansi":
  1197. case "gb2312":
  1198. case "gb18030":
  1199. return Encoding.Default;
  1200. }
  1201. return null;
  1202. }
  1203. #endregion
  1204. }
  1205. }