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.

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