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.

1103 lines
40 KiB

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