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.

1275 lines
46 KiB

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