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.

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