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.

1190 lines
43 KiB

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