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.

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