hutool-python 1.0.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (89) hide show
  1. hutool/__init__.py +174 -0
  2. hutool/cache/__init__.py +7 -0
  3. hutool/cache/cache_util.py +47 -0
  4. hutool/cache/fifo_cache.py +87 -0
  5. hutool/cache/lfu_cache.py +129 -0
  6. hutool/cache/lru_cache.py +93 -0
  7. hutool/cache/timed_cache.py +115 -0
  8. hutool/captcha/__init__.py +3 -0
  9. hutool/captcha/captcha_util.py +215 -0
  10. hutool/core/__init__.py +23 -0
  11. hutool/core/_base.py +61 -0
  12. hutool/core/bean.py +214 -0
  13. hutool/core/codec.py +111 -0
  14. hutool/core/coll.py +635 -0
  15. hutool/core/date.py +1024 -0
  16. hutool/core/exceptions.py +66 -0
  17. hutool/core/io/__init__.py +0 -0
  18. hutool/core/io/data_size_util.py +79 -0
  19. hutool/core/io/file_name_util.py +111 -0
  20. hutool/core/io/file_util.py +650 -0
  21. hutool/core/io/io_util.py +133 -0
  22. hutool/core/io/path_util.py +247 -0
  23. hutool/core/io/resource_util.py +137 -0
  24. hutool/core/map.py +933 -0
  25. hutool/core/math_util.py +105 -0
  26. hutool/core/net.py +288 -0
  27. hutool/core/text/__init__.py +0 -0
  28. hutool/core/text/csv_util.py +54 -0
  29. hutool/core/text/str_builder.py +224 -0
  30. hutool/core/text/unicode_util.py +58 -0
  31. hutool/core/tree.py +242 -0
  32. hutool/core/util/__init__.py +63 -0
  33. hutool/core/util/array_util.py +503 -0
  34. hutool/core/util/boolean_util.py +124 -0
  35. hutool/core/util/charset_util.py +60 -0
  36. hutool/core/util/class_util.py +136 -0
  37. hutool/core/util/coordinate_util.py +186 -0
  38. hutool/core/util/credit_code_util.py +110 -0
  39. hutool/core/util/desensitized_util.py +194 -0
  40. hutool/core/util/enum_util.py +94 -0
  41. hutool/core/util/escape_util.py +97 -0
  42. hutool/core/util/hash_util.py +243 -0
  43. hutool/core/util/hex_util.py +140 -0
  44. hutool/core/util/id_util.py +147 -0
  45. hutool/core/util/idcard_util.py +300 -0
  46. hutool/core/util/number_util.py +720 -0
  47. hutool/core/util/object_util.py +294 -0
  48. hutool/core/util/page_util.py +61 -0
  49. hutool/core/util/phone_util.py +140 -0
  50. hutool/core/util/random_util.py +112 -0
  51. hutool/core/util/re_util.py +231 -0
  52. hutool/core/util/reflect_util.py +135 -0
  53. hutool/core/util/runtime_util.py +89 -0
  54. hutool/core/util/str_util.py +2320 -0
  55. hutool/core/util/system_util.py +62 -0
  56. hutool/core/util/url_util.py +232 -0
  57. hutool/core/util/version_util.py +41 -0
  58. hutool/core/util/xml_util.py +158 -0
  59. hutool/core/util/zip_util.py +126 -0
  60. hutool/cron/__init__.py +4 -0
  61. hutool/cron/cron_pattern.py +123 -0
  62. hutool/cron/cron_util.py +115 -0
  63. hutool/crypto/__init__.py +5 -0
  64. hutool/crypto/digest_util.py +167 -0
  65. hutool/crypto/secure_util.py +311 -0
  66. hutool/crypto/sign_util.py +74 -0
  67. hutool/dfa/__init__.py +3 -0
  68. hutool/dfa/sensitive_util.py +114 -0
  69. hutool/extra/__init__.py +6 -0
  70. hutool/extra/emoji_util.py +90 -0
  71. hutool/extra/pinyin_util.py +44 -0
  72. hutool/extra/qr_code_util.py +58 -0
  73. hutool/extra/template_util.py +41 -0
  74. hutool/http/__init__.py +6 -0
  75. hutool/http/html_util.py +88 -0
  76. hutool/http/http_request.py +188 -0
  77. hutool/http/http_response.py +139 -0
  78. hutool/http/http_util.py +237 -0
  79. hutool/json_util.py +251 -0
  80. hutool/jwt_util.py +57 -0
  81. hutool/setting/__init__.py +5 -0
  82. hutool/setting/props_util.py +79 -0
  83. hutool/setting/setting_util.py +80 -0
  84. hutool/setting/yaml_util.py +45 -0
  85. hutool_python-1.0.0.dist-info/LICENSE +127 -0
  86. hutool_python-1.0.0.dist-info/METADATA +438 -0
  87. hutool_python-1.0.0.dist-info/RECORD +89 -0
  88. hutool_python-1.0.0.dist-info/WHEEL +5 -0
  89. hutool_python-1.0.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,2320 @@
1
+ """
2
+ Java Hutool StrUtil 的 Python 移植版。
3
+
4
+ 包含字符和字符串工具类:CharPool、CharUtil、CharSequenceUtil、StrPool、StrUtil。
5
+ """
6
+
7
+ import array
8
+ import codecs
9
+ import re
10
+ from difflib import SequenceMatcher
11
+ from typing import Any, Callable, List, Optional, Union
12
+
13
+ __all__ = [
14
+ "CharPool",
15
+ "CharSequenceUtil",
16
+ "CharUtil",
17
+ "StrPool",
18
+ "StrUtil",
19
+ ]
20
+
21
+
22
+ # ---------------------------------------------------------------------------
23
+ # CharPool
24
+ # ---------------------------------------------------------------------------
25
+ class CharPool:
26
+ """常用字符常量。"""
27
+
28
+ # 空字符串 ''
29
+ EMPTY: str = ""
30
+ # 字符常量:空格符 ' '
31
+ SPACE: str = " "
32
+ # 字符常量:制表符 '\t'
33
+ TAB: str = "\t"
34
+ # 字符常量:点 '.'
35
+ DOT: str = "."
36
+ # 字符常量:斜杠 '/'
37
+ SLASH: str = "/"
38
+ # 字符常量:反斜杠 '\\'
39
+ BACKSLASH: str = "\\"
40
+ # 字符常量:回车符 '\r'
41
+ CR: str = "\r"
42
+ # 字符常量:换行符 '\n'
43
+ LF: str = "\n"
44
+ # 字符常量:减号(连接符) '-'
45
+ DASHED: str = "-"
46
+ # 字符常量:下划线 '_'
47
+ UNDERLINE: str = "_"
48
+ # 字符常量:逗号 ','
49
+ COMMA: str = ","
50
+ # 字符常量:花括号(左) '{'
51
+ DELIM_START: str = "{"
52
+ # 字符常量:花括号(右) '}'
53
+ DELIM_END: str = "}"
54
+ # 字符常量:中括号(左) '['
55
+ BRACKET_START: str = "["
56
+ # 字符常量:中括号(右) ']'
57
+ BRACKET_END: str = "]"
58
+ # 字符常量:双引号 '"'
59
+ DOUBLE_QUOTES: str = '"'
60
+ # 字符常量:单引号 '\''
61
+ SINGLE_QUOTE: str = "'"
62
+ # 字符常量:与 '&'
63
+ AMP: str = "&"
64
+ # 字符常量:冒号 ':'
65
+ COLON: str = ":"
66
+ # 字符常量:艾特 '@'
67
+ AT: str = "@"
68
+
69
+
70
+ # ---------------------------------------------------------------------------
71
+ # CharUtil
72
+ # ---------------------------------------------------------------------------
73
+ class CharUtil(CharPool):
74
+ """字符工具类。"""
75
+
76
+ @staticmethod
77
+ def is_ascii(ch: str) -> bool:
78
+ """
79
+ 判断字符是否为ASCII字符(0~127)。
80
+
81
+ :param ch: 待检查的字符
82
+ :return: 是否为ASCII字符
83
+ """
84
+ return ord(ch) < 128
85
+
86
+ @staticmethod
87
+ def is_ascii_printable(ch: str) -> bool:
88
+ """
89
+ 判断字符是否为可打印ASCII字符(32~126)。
90
+
91
+ :param ch: 待检查的字符
92
+ :return: 是否为可打印ASCII字符
93
+ """
94
+ return 32 <= ord(ch) < 127
95
+
96
+ @staticmethod
97
+ def is_ascii_control(ch: str) -> bool:
98
+ """
99
+ 判断字符是否为ASCII控制字符(0~31和127)。
100
+
101
+ :param ch: 待检查的字符
102
+ :return: 是否为控制字符
103
+ """
104
+ ord_ch = ord(ch)
105
+ return ord_ch < 32 or ord_ch == 127
106
+
107
+ @staticmethod
108
+ def is_letter(ch: str) -> bool:
109
+ """
110
+ 判断字符是否为字母(A~Z或a~z)。
111
+
112
+ :param ch: 待检查的字符
113
+ :return: 是否为字母
114
+ """
115
+ return CharUtil.is_letter_upper(ch) or CharUtil.is_letter_lower(ch)
116
+
117
+ @staticmethod
118
+ def is_letter_upper(ch: str) -> bool:
119
+ """
120
+ 判断字符是否为大写字母(A~Z)。
121
+
122
+ :param ch: 待检查的字符
123
+ :return: 是否为大写字母
124
+ """
125
+ return "A" <= ch <= "Z"
126
+
127
+ @staticmethod
128
+ def is_letter_lower(ch: str) -> bool:
129
+ """
130
+ 判断字符是否为小写字母(a~z)。
131
+
132
+ :param ch: 待检查的字符
133
+ :return: 是否为小写字母
134
+ """
135
+ return "a" <= ch <= "z"
136
+
137
+ @staticmethod
138
+ def is_number(ch: str) -> bool:
139
+ """
140
+ 判断字符是否为数字(0~9)。
141
+
142
+ :param ch: 待检查的字符
143
+ :return: 是否为数字
144
+ """
145
+ return "0" <= ch <= "9"
146
+
147
+ @staticmethod
148
+ def is_hex_char(ch: str) -> bool:
149
+ """
150
+ 判断字符是否为十六进制字符(0~9, a~f, A~F)。
151
+
152
+ :param ch: 待检查的字符
153
+ :return: 是否为十六进制字符
154
+ """
155
+ return CharUtil.is_number(ch) or ("a" <= ch <= "f") or ("A" <= ch <= "F")
156
+
157
+ @staticmethod
158
+ def is_letter_or_number(ch: str) -> bool:
159
+ """
160
+ 判断字符是否为字母或数字(A~Z, a~z, 0~9)。
161
+
162
+ :param ch: 待检查的字符
163
+ :return: 是否为字母或数字
164
+ """
165
+ return CharUtil.is_letter(ch) or CharUtil.is_number(ch)
166
+
167
+ @staticmethod
168
+ def is_blank_char(ch: str) -> bool:
169
+ """
170
+ 判断字符是否为空白字符。
171
+ 空白字符包括空格、制表符、全角空格和不间断空格。
172
+
173
+ :param ch: 待检查的字符
174
+ :return: 是否为空白字符
175
+ """
176
+ return ch.isspace() or ch == "" or ch == "‪" or ch == "\x00" or ch == "ㅤ" or ch == "⠀" or ch == "᠎"
177
+
178
+ @staticmethod
179
+ def is_emoji(ch: str) -> bool:
180
+ """
181
+ 判断字符是否为表情符号。
182
+
183
+ :param ch: 待检查的字符
184
+ :return: 是否为表情符号
185
+ """
186
+ ord_ch = ord(ch)
187
+ return not (
188
+ ord_ch == 0x0
189
+ or ord_ch == 0x9
190
+ or ord_ch == 0xA
191
+ or ord_ch == 0xD
192
+ or 0x20 <= ord_ch <= 0xD7FF
193
+ or 0xE000 <= ord_ch <= 0xFFFD
194
+ or 0x100000 <= ord_ch <= 0x10FFFF
195
+ )
196
+
197
+ @staticmethod
198
+ def is_file_separator(ch: str) -> bool:
199
+ """
200
+ 判断字符是否为文件分隔符('/' 或 '\\')。
201
+
202
+ :param ch: 待检查的字符
203
+ :return: 是否为文件分隔符
204
+ """
205
+ return ch == CharPool.SLASH or ch == CharPool.BACKSLASH
206
+
207
+ @staticmethod
208
+ def equals(ch1: str, ch2: str, case_insensitive: bool) -> bool:
209
+ """
210
+ 比较两个字符是否相等。
211
+
212
+ :param ch1: 第一个字符
213
+ :param ch2: 第二个字符
214
+ :param case_insensitive: 是否忽略大小写
215
+ :return: 两个字符是否相等
216
+ """
217
+ if case_insensitive:
218
+ return ch1.lower() == ch2.lower()
219
+ return ch1 == ch2
220
+
221
+ @staticmethod
222
+ def digit16(ch: str) -> str:
223
+ """
224
+ 获取字符的十六进制值。
225
+
226
+ :param ch: 待转换的字符
227
+ :return: 十六进制字符串表示
228
+ """
229
+ return hex(ord(ch))
230
+
231
+ @staticmethod
232
+ def to_close_char(ch: str) -> str:
233
+ """
234
+ 将字母或数字转换为带圈字符形式。
235
+
236
+ Examples::
237
+
238
+ '1' -> '①' (①)
239
+ 'A' -> 'Ⓐ' (Ⓐ)
240
+ 'a' -> 'ⓐ' (ⓐ)
241
+
242
+ 如果字符不支持转换,则返回原字符。
243
+
244
+ :param ch: 待转换的字符
245
+ :return: 带圈字符,不支持时返回原字符
246
+ """
247
+ result = ord(ch)
248
+ if "1" <= ch <= "9":
249
+ result = ord("①") + result - ord("1")
250
+ elif "A" <= ch <= "Z":
251
+ result = ord("Ⓐ") + result - ord("A")
252
+ elif "a" <= ch <= "z":
253
+ result = ord("ⓐ") + result - ord("a")
254
+ return chr(result)
255
+
256
+ @staticmethod
257
+ def to_close_by_number(number: int) -> str:
258
+ """
259
+ 将数字(1~20)转换为带圈数字形式。
260
+
261
+ Examples::
262
+
263
+ 1 -> '①' (①)
264
+ 12 -> '⑫' (⑫)
265
+ 20 -> '⑳' (⑳)
266
+
267
+ :param number: 待转换的数字(必须在1~20范围内)
268
+ :return: 带圈数字字符
269
+ :raises ValueError: 数字不在[1, 20]范围内时抛出异常
270
+ """
271
+ if number < 1 or number > 20:
272
+ raise ValueError("Number must be [1-20]")
273
+ return chr(ord("①") + number - 1)
274
+
275
+
276
+ # ---------------------------------------------------------------------------
277
+ # CharSequenceUtil
278
+ # ---------------------------------------------------------------------------
279
+ class CharSequenceUtil:
280
+ """
281
+ CharSequence 工具类,提供常用的字符串操作。
282
+ """
283
+
284
+ # ------------------------------------------------------------------
285
+ # Blank / empty checks
286
+ # ------------------------------------------------------------------
287
+
288
+ @staticmethod
289
+ def is_blank(string: Optional[str]) -> bool:
290
+ """
291
+ 判断字符串是否为空白。当字符串为 None、空字符串(``""``)
292
+ 或全部由空白字符组成时,视为空白。
293
+
294
+ :param string: 待检查的字符串
295
+ :return: 是否为空白字符串
296
+ """
297
+ if CharSequenceUtil.is_empty(string):
298
+ return True
299
+ for char in string:
300
+ if not CharUtil.is_blank_char(char):
301
+ return False
302
+ return True
303
+
304
+ @staticmethod
305
+ def is_not_blank(string: Optional[str]) -> bool:
306
+ """
307
+ 判断字符串是否不为空白(与 :meth:`is_blank` 相反)。
308
+
309
+ :param string: 待检查的字符串
310
+ :return: 是否不为空白字符串
311
+ """
312
+ return not CharSequenceUtil.is_blank(string)
313
+
314
+ @staticmethod
315
+ def has_blank(*strings: Optional[str]) -> bool:
316
+ """
317
+ 判断给定的多个字符串中是否存在空白字符串。
318
+ 当 *strings* 为空或其中任一元素为空白时返回 True。
319
+
320
+ :param strings: 一个或多个待检查的字符串
321
+ :return: 是否存在空白字符串
322
+ """
323
+ if not strings:
324
+ return True
325
+ for string in strings:
326
+ if CharSequenceUtil.is_blank(string):
327
+ return True
328
+ return False
329
+
330
+ @staticmethod
331
+ def is_all_blank(*strings: Optional[str]) -> bool:
332
+ """
333
+ 判断给定的多个字符串是否全部为空白。
334
+ 当 *strings* 为空或所有元素均为空白时返回 True。
335
+
336
+ :param strings: 一个或多个待检查的字符串
337
+ :return: 是否全部为空白字符串
338
+ """
339
+ if not strings:
340
+ return True
341
+ for string in strings:
342
+ if CharSequenceUtil.is_not_blank(string):
343
+ return False
344
+ return True
345
+
346
+ @staticmethod
347
+ def is_empty(string: Optional[str]) -> bool:
348
+ """
349
+ 判断字符串是否为空(None 或 ``""``)。
350
+
351
+ :param string: 待检查的字符串
352
+ :return: 是否为 None 或空字符串
353
+ """
354
+ return string is None or len(string) == 0
355
+
356
+ @staticmethod
357
+ def is_not_empty(string: Optional[str]) -> bool:
358
+ """
359
+ 判断字符串是否不为空(与 :meth:`is_empty` 相反)。
360
+
361
+ :param string: 待检查的字符串
362
+ :return: 是否不为 None 且不为空
363
+ """
364
+ return not CharSequenceUtil.is_empty(string)
365
+
366
+ @staticmethod
367
+ def empty_if_none(string: Optional[str]) -> str:
368
+ """
369
+ 如果 *string* 为 None 则返回空字符串,否则返回原字符串。
370
+
371
+ :param string: 待检查的字符串
372
+ :return: 原字符串或 ``""``
373
+ """
374
+ return CharSequenceUtil.none_to_empty(string)
375
+
376
+ @staticmethod
377
+ def none_to_empty(string: Optional[str]) -> str:
378
+ """
379
+ 将 None 转换为空字符串。
380
+
381
+ :param string: 待转换的字符串
382
+ :return: *string* 为 None 时返回 ``""``,否则返回原字符串
383
+ """
384
+ return CharSequenceUtil.none_to_default(string, CharPool.EMPTY)
385
+
386
+ @staticmethod
387
+ def none_to_default(string: Optional[str], default: str) -> str:
388
+ """
389
+ 如果 *string* 为 None 则返回 *default*,否则返回 str(*string*)。
390
+
391
+ :param string: 待转换的字符串
392
+ :param default: 默认值
393
+ :return: 字符串本身或默认值
394
+ """
395
+ return default if string is None else str(string)
396
+
397
+ @staticmethod
398
+ def empty_to_default(string: Optional[str], default: str) -> str:
399
+ """
400
+ 如果 *string* 为 None 或 ``""`` 则返回 *default*,否则返回 str(*string*)。
401
+
402
+ :param string: 待转换的字符串
403
+ :param default: 默认值
404
+ :return: 字符串本身或默认值
405
+ """
406
+ return default if CharSequenceUtil.is_empty(string) else str(string)
407
+
408
+ @staticmethod
409
+ def blank_to_default(string: Optional[str], default: str) -> str:
410
+ """
411
+ 如果 *string* 为 None、空字符串或空白字符串则返回 *default*,否则返回 str(*string*)。
412
+
413
+ :param string: 待转换的字符串
414
+ :param default: 默认值
415
+ :return: 字符串本身或默认值
416
+ """
417
+ return default if CharSequenceUtil.is_blank(string) else str(string)
418
+
419
+ @staticmethod
420
+ def empty_to_none(string: Optional[str]) -> Optional[str]:
421
+ """
422
+ 将空字符串转换为 None。
423
+
424
+ :param string: 待转换的字符串
425
+ :return: *string* 为空时返回 None,否则返回原字符串
426
+ """
427
+ return None if CharSequenceUtil.is_empty(string) else str(string)
428
+
429
+ @staticmethod
430
+ def has_empty(*strings: Optional[str]) -> bool:
431
+ """
432
+ 判断给定的多个字符串中是否存在空字符串(None 或 ``""``)。
433
+ 当 *strings* 为空或其中任一元素为空时返回 True。
434
+
435
+ :param strings: 一个或多个待检查的字符串
436
+ :return: 是否存在空字符串
437
+ """
438
+ if not strings:
439
+ return True
440
+ for string in strings:
441
+ if CharSequenceUtil.is_empty(string):
442
+ return True
443
+ return False
444
+
445
+ @staticmethod
446
+ def is_all_empty(*strings: Optional[str]) -> bool:
447
+ """
448
+ 判断给定的多个字符串是否全部为空。
449
+ 当 *strings* 为空或所有元素均为空时返回 True。
450
+
451
+ :param strings: 一个或多个待检查的字符串
452
+ :return: 是否全部为空字符串
453
+ """
454
+ if not strings:
455
+ return True
456
+ for string in strings:
457
+ if CharSequenceUtil.is_not_empty(string):
458
+ return False
459
+ return True
460
+
461
+ @staticmethod
462
+ def is_all_not_empty(*strings: Optional[str]) -> bool:
463
+ """
464
+ 判断给定的多个字符串是否全部不为空。
465
+
466
+ :param strings: 一个或多个待检查的字符串
467
+ :return: 是否没有空字符串
468
+ """
469
+ return not CharSequenceUtil.has_empty(*strings)
470
+
471
+ @staticmethod
472
+ def is_all_not_blank(*strings: Optional[str]) -> bool:
473
+ """
474
+ 判断给定的多个字符串是否全部不为空白。
475
+
476
+ :param strings: 一个或多个待检查的字符串
477
+ :return: 是否没有空白字符串
478
+ """
479
+ return not CharSequenceUtil.has_blank(*strings)
480
+
481
+ @staticmethod
482
+ def is_none_or_undefined(string: Optional[str]) -> bool:
483
+ """
484
+ 判断 *string* 是否为 None、``""``、``"None"`` 或 ``"undefined"``。
485
+
486
+ :param string: 待检查的字符串
487
+ :return: 是否匹配哨兵值
488
+ """
489
+ if string is None:
490
+ return True
491
+ return CharSequenceUtil.is_none_or_undefined_str(string)
492
+
493
+ @staticmethod
494
+ def is_empty_or_undefined(string: Optional[str]) -> bool:
495
+ """
496
+ 判断 *string* 是否为空或等于 ``"None"`` / ``"undefined"``。
497
+
498
+ :param string: 待检查的字符串
499
+ :return: 是否为空或哨兵值
500
+ """
501
+ if CharSequenceUtil.is_empty(string):
502
+ return True
503
+ return CharSequenceUtil.is_none_or_undefined_str(string)
504
+
505
+ @staticmethod
506
+ def is_blank_or_undefined(string: Optional[str]) -> bool:
507
+ """
508
+ 判断 *string* 是否为空白或等于 ``"None"`` / ``"undefined"``。
509
+
510
+ :param string: 待检查的字符串
511
+ :return: 是否为空白或哨兵值
512
+ """
513
+ if CharSequenceUtil.is_blank(string):
514
+ return True
515
+ return CharSequenceUtil.is_none_or_undefined_str(string)
516
+
517
+ @staticmethod
518
+ def is_none_or_undefined_str(string: str) -> bool:
519
+ """
520
+ 不做 None 检查,直接判断 *string* 去除空白后是否等于 ``"None"`` 或 ``"undefined"``。
521
+
522
+ :param string: 待检查的字符串
523
+ :return: 是否为 ``"None"`` 或 ``"undefined"``
524
+ """
525
+ str_string = str(string).strip()
526
+ return str_string == StrPool.NONE or str_string == "undefined"
527
+
528
+ # ------------------------------------------------------------------
529
+ # Trim
530
+ # ------------------------------------------------------------------
531
+
532
+ @staticmethod
533
+ def trim(
534
+ string: Optional[str],
535
+ mode: int = 0,
536
+ predicate: Callable[[str], bool] = CharUtil.is_blank_char,
537
+ ) -> Optional[str]:
538
+ """
539
+ 根据 *predicate* 从字符串的头部和/或尾部裁剪字符。
540
+
541
+ :param string: 待裁剪的字符串
542
+ :param mode: ``-1`` = 仅裁剪头部,``0`` = 两端都裁剪,``1`` = 仅裁剪尾部
543
+ :param predicate: 返回 True 时表示该字符应被移除的函数
544
+ :return: 裁剪后的字符串,*string* 为 None 时返回 None
545
+ """
546
+ result: Optional[str]
547
+ if string is None:
548
+ result = None
549
+ else:
550
+ length = len(string)
551
+ start = 0
552
+ end = length
553
+ if mode <= 0:
554
+ while start < end and predicate(string[start]):
555
+ start += 1
556
+ if mode >= 0:
557
+ while start < end and predicate(string[end - 1]):
558
+ end -= 1
559
+ if start > 0 or end < length:
560
+ result = str(string)[start:end]
561
+ else:
562
+ result = str(string)
563
+ return result
564
+
565
+ @staticmethod
566
+ def trim_to_empty(string: Optional[str]) -> str:
567
+ """
568
+ 裁剪两端空白。*string* 为 None 时返回 ``""``。
569
+
570
+ :param string: 待裁剪的字符串
571
+ :return: 裁剪后的字符串或 ``""``
572
+ """
573
+ return CharPool.EMPTY if string is None else CharSequenceUtil.trim(string) # type: ignore[return-value]
574
+
575
+ @staticmethod
576
+ def trim_to_none(string: Optional[str]) -> Optional[str]:
577
+ """
578
+ 裁剪两端空白。*string* 为 None 或结果为空字符串时返回 None。
579
+
580
+ :param string: 待裁剪的字符串
581
+ :return: 裁剪后的字符串或 None
582
+ """
583
+ trim_str = CharSequenceUtil.trim(string)
584
+ return None if trim_str == CharPool.EMPTY else trim_str
585
+
586
+ @staticmethod
587
+ def trim_start(string: Optional[str]) -> Optional[str]:
588
+ """
589
+ 仅裁剪字符串头部的空白。
590
+
591
+ :param string: 待裁剪的字符串
592
+ :return: 裁剪后的字符串,*string* 为 None 时返回 None
593
+ """
594
+ return CharSequenceUtil.trim(string, mode=-1)
595
+
596
+ @staticmethod
597
+ def trim_end(string: Optional[str]) -> Optional[str]:
598
+ """
599
+ 仅裁剪字符串尾部的空白。
600
+
601
+ :param string: 待裁剪的字符串
602
+ :return: 裁剪后的字符串,*string* 为 None 时返回 None
603
+ """
604
+ return CharSequenceUtil.trim(string, mode=1)
605
+
606
+ # ------------------------------------------------------------------
607
+ # Starts / ends with
608
+ # ------------------------------------------------------------------
609
+
610
+ @staticmethod
611
+ def start_with(
612
+ string: Optional[str],
613
+ prefix: Optional[str],
614
+ ignore_case: bool = False,
615
+ ignore_equals: bool = False,
616
+ ) -> bool:
617
+ """
618
+ 判断 *string* 是否以 *prefix* 开头。
619
+
620
+ :param string: 待检查的字符串
621
+ :param prefix: 前缀
622
+ :param ignore_case: 是否忽略大小写
623
+ :param ignore_equals: 字符串相等时是否返回 False
624
+ :return: 是否以指定前缀开头
625
+ """
626
+ if string is None or prefix is None:
627
+ if ignore_equals:
628
+ return False
629
+ return string is None and prefix is None
630
+
631
+ if ignore_case:
632
+ is_start = string.lower().startswith(prefix.lower())
633
+ else:
634
+ is_start = string.startswith(prefix)
635
+
636
+ if is_start:
637
+ if ignore_equals:
638
+ return not CharSequenceUtil.equals(string, prefix, ignore_case)
639
+ return True
640
+ return False
641
+
642
+ @staticmethod
643
+ def start_with_ignore_equals(string: Optional[str], prefix: Optional[str]) -> bool:
644
+ """
645
+ 判断 *string* 是否以 *prefix* 开头且不相等。
646
+
647
+ :param string: 待检查的字符串
648
+ :param prefix: 前缀
649
+ :return: 是否以指定前缀开头且两者不同
650
+ """
651
+ return CharSequenceUtil.start_with(string, prefix, ignore_case=False, ignore_equals=True)
652
+
653
+ @staticmethod
654
+ def start_with_ignore_case(string: Optional[str], prefix: Optional[str]) -> bool:
655
+ """
656
+ 判断 *string* 是否以 *prefix* 开头,忽略大小写。
657
+
658
+ :param string: 待检查的字符串
659
+ :param prefix: 前缀
660
+ :return: 是否以指定前缀开头(不区分大小写)
661
+ """
662
+ return CharSequenceUtil.start_with(string, prefix, ignore_case=True)
663
+
664
+ @staticmethod
665
+ def start_with_any(string: Optional[str], *prefixes: Optional[str]) -> bool:
666
+ """
667
+ 判断 *string* 是否以给定的任一 *prefixes* 开头。
668
+
669
+ :param string: 待检查的字符串
670
+ :param prefixes: 前缀列表
671
+ :return: 是否匹配任一前缀
672
+ """
673
+ if CharSequenceUtil.is_empty(string) or not prefixes:
674
+ return False
675
+ for prefix in prefixes:
676
+ if CharSequenceUtil.start_with(string, prefix, ignore_case=False):
677
+ return True
678
+ return False
679
+
680
+ @staticmethod
681
+ def start_with_any_ignore_case(string: Optional[str], *prefixes: Optional[str]) -> bool:
682
+ """
683
+ 判断 *string* 是否以给定的任一 *prefixes* 开头,忽略大小写。
684
+
685
+ :param string: 待检查的字符串
686
+ :param prefixes: 前缀列表
687
+ :return: 是否匹配任一前缀(不区分大小写)
688
+ """
689
+ if CharSequenceUtil.is_empty(string) or not prefixes:
690
+ return False
691
+ for prefix in prefixes:
692
+ if CharSequenceUtil.start_with(string, prefix, ignore_case=True):
693
+ return True
694
+ return False
695
+
696
+ @staticmethod
697
+ def end_with(
698
+ string: Optional[str],
699
+ suffix: Optional[str],
700
+ ignore_case: bool = False,
701
+ ignore_equals: bool = False,
702
+ ) -> bool:
703
+ """
704
+ 判断 *string* 是否以 *suffix* 结尾。
705
+
706
+ :param string: 待检查的字符串
707
+ :param suffix: 后缀
708
+ :param ignore_case: 是否忽略大小写
709
+ :param ignore_equals: 字符串相等时是否返回 False
710
+ :return: 是否以指定后缀结尾
711
+ """
712
+ if string is None or suffix is None:
713
+ if ignore_equals:
714
+ return False
715
+ return string is None and suffix is None
716
+
717
+ if ignore_case:
718
+ is_end = string.lower().endswith(suffix.lower())
719
+ else:
720
+ is_end = string.endswith(suffix)
721
+
722
+ if is_end:
723
+ if ignore_equals:
724
+ return not CharSequenceUtil.equals(string, suffix, ignore_case)
725
+ return True
726
+ return False
727
+
728
+ @staticmethod
729
+ def end_with_ignore_equals(string: Optional[str], suffix: Optional[str]) -> bool:
730
+ """
731
+ 判断 *string* 是否以 *suffix* 结尾且不相等。
732
+
733
+ :param string: 待检查的字符串
734
+ :param suffix: 后缀
735
+ :return: 是否以指定后缀结尾且两者不同
736
+ """
737
+ return CharSequenceUtil.end_with(string, suffix, ignore_case=False, ignore_equals=True)
738
+
739
+ @staticmethod
740
+ def end_with_ignore_case(string: Optional[str], prefix: Optional[str]) -> bool:
741
+ """
742
+ 判断 *string* 是否以 *prefix* 结尾,忽略大小写。
743
+
744
+ :param string: 待检查的字符串
745
+ :param prefix: 后缀
746
+ :return: 是否以指定后缀结尾(不区分大小写)
747
+ """
748
+ return CharSequenceUtil.end_with(string, prefix, ignore_case=True)
749
+
750
+ @staticmethod
751
+ def end_with_any(string: Optional[str], *prefixes: Optional[str]) -> bool:
752
+ """
753
+ 判断 *string* 是否以给定的任一 *prefixes* 结尾。
754
+
755
+ :param string: 待检查的字符串
756
+ :param prefixes: 后缀列表
757
+ :return: 是否匹配任一后缀
758
+ """
759
+ if CharSequenceUtil.is_empty(string) or not prefixes:
760
+ return False
761
+ for prefix in prefixes:
762
+ if CharSequenceUtil.end_with(string, prefix, ignore_case=False):
763
+ return True
764
+ return False
765
+
766
+ @staticmethod
767
+ def end_with_any_ignore_case(string: Optional[str], *prefixes: Optional[str]) -> bool:
768
+ """
769
+ 判断 *string* 是否以给定的任一 *prefixes* 结尾,忽略大小写。
770
+
771
+ :param string: 待检查的字符串
772
+ :param prefixes: 后缀列表
773
+ :return: 是否匹配任一后缀(不区分大小写)
774
+ """
775
+ if CharSequenceUtil.is_empty(string) or not prefixes:
776
+ return False
777
+ for prefix in prefixes:
778
+ if CharSequenceUtil.end_with(string, prefix, ignore_case=True):
779
+ return True
780
+ return False
781
+
782
+ # ------------------------------------------------------------------
783
+ # Contains
784
+ # ------------------------------------------------------------------
785
+
786
+ @staticmethod
787
+ def contains(string: Optional[str], search: Optional[str]) -> bool:
788
+ """
789
+ 判断 *string* 是否包含 *search*。
790
+
791
+ :param string: 待搜索的字符串
792
+ :param search: 待查找的子串
793
+ :return: 是否包含该子串
794
+ """
795
+ if string is None or search is None:
796
+ return False
797
+ return search in string
798
+
799
+ @staticmethod
800
+ def contains_any(string: Optional[str], *test_strings: Optional[str]) -> bool:
801
+ """
802
+ 判断 *string* 是否包含给定的任一子串。
803
+
804
+ :param string: 待搜索的字符串
805
+ :param test_strings: 待查找的子串列表
806
+ :return: 是否包含任一子串
807
+ """
808
+ return CharSequenceUtil.get_contains_str(string, *test_strings) is not None
809
+
810
+ @staticmethod
811
+ def contains_all(string: Optional[str], *test_strings: Optional[str]) -> bool:
812
+ """
813
+ 判断 *string* 是否包含所有给定的子串。
814
+
815
+ :param string: 待搜索的字符串
816
+ :param test_strings: 待查找的子串列表
817
+ :return: 是否包含所有子串
818
+ """
819
+ if CharSequenceUtil.is_blank(string) or not test_strings:
820
+ return False
821
+ for test_string in test_strings:
822
+ if not CharSequenceUtil.contains(string, test_string):
823
+ return False
824
+ return True
825
+
826
+ @staticmethod
827
+ def contains_blank(string: Optional[str]) -> bool:
828
+ """
829
+ 判断 *string* 是否包含空白字符。
830
+ *string* 为 None 或空字符串时返回 False。
831
+
832
+ :param string: 待检查的字符串
833
+ :return: 是否包含空白字符
834
+ """
835
+ if CharSequenceUtil.is_empty(string):
836
+ return False
837
+ for char in string:
838
+ if CharUtil.is_blank_char(char):
839
+ return True
840
+ return False
841
+
842
+ @staticmethod
843
+ def get_contains_str(string: Optional[str], *test_strings: Optional[str]) -> Optional[str]:
844
+ """
845
+ 查找 *test_strings* 中第一个被 *string* 包含的子串。
846
+
847
+ :param string: 待搜索的字符串
848
+ :param test_strings: 待查找的子串列表
849
+ :return: 第一个匹配的子串,或 None
850
+ """
851
+ if CharSequenceUtil.is_empty(string) or not test_strings:
852
+ return None
853
+ for test_string in test_strings:
854
+ if CharSequenceUtil.contains(string, test_string):
855
+ return test_string
856
+ return None
857
+
858
+ @staticmethod
859
+ def contains_ignore_case(string: Optional[str], test_string: Optional[str]) -> bool:
860
+ """
861
+ 判断 *string* 是否包含 *test_string*,忽略大小写。
862
+ 两者均为 None 时返回 True。
863
+
864
+ :param string: 待搜索的字符串
865
+ :param test_string: 待查找的子串
866
+ :return: 是否包含该子串(不区分大小写)
867
+ """
868
+ if string is None:
869
+ return test_string is None
870
+ return CharSequenceUtil.index_of(string, test_string, ignore_case=True) > -1
871
+
872
+ @staticmethod
873
+ def contains_any_ignore_case(string: Optional[str], *test_strings: Optional[str]) -> bool:
874
+ """
875
+ 判断 *string* 是否包含给定的任一子串,忽略大小写。
876
+
877
+ :param string: 待搜索的字符串
878
+ :param test_strings: 待查找的子串列表
879
+ :return: 是否包含任一子串(不区分大小写)
880
+ """
881
+ return CharSequenceUtil.get_contains_str_ignore_case(string, *test_strings) is not None
882
+
883
+ @staticmethod
884
+ def get_contains_str_ignore_case(string: Optional[str], *test_strings: Optional[str]) -> Optional[str]:
885
+ """
886
+ 查找 *test_strings* 中第一个被 *string* 包含的子串,忽略大小写。
887
+
888
+ :param string: 待搜索的字符串
889
+ :param test_strings: 待查找的子串列表
890
+ :return: 第一个匹配的子串,或 None
891
+ """
892
+ if CharSequenceUtil.is_empty(string) or not test_strings:
893
+ return None
894
+ for test_string in test_strings:
895
+ if CharSequenceUtil.contains_ignore_case(string, test_string):
896
+ return test_string
897
+ return None
898
+
899
+ # ------------------------------------------------------------------
900
+ # Index of
901
+ # ------------------------------------------------------------------
902
+
903
+ @staticmethod
904
+ def index_of(
905
+ text: Optional[str],
906
+ search_string: Optional[str],
907
+ start: Optional[int] = None,
908
+ end: Optional[int] = None,
909
+ ignore_case: bool = False,
910
+ ) -> int:
911
+ """
912
+ 在 *text* 的可选范围 [start, end) 内查找 *search_string* 的索引。
913
+
914
+ :param text: 待搜索的字符串
915
+ :param search_string: 待查找的子串
916
+ :param start: 可选的起始位置
917
+ :param end: 可选的结束位置
918
+ :param ignore_case: 是否忽略大小写
919
+ :return: 索引值,未找到时返回 -1
920
+ """
921
+ if CharSequenceUtil.is_empty(text) or CharSequenceUtil.is_empty(search_string):
922
+ if CharSequenceUtil.equals(text, search_string):
923
+ return 0
924
+ return -1
925
+ if ignore_case:
926
+ text = text.lower()
927
+ search_string = search_string.lower()
928
+ return text.find(search_string, start, end)
929
+
930
+ @staticmethod
931
+ def index_of_ignore_case(
932
+ text: Optional[str],
933
+ search_string: Optional[str],
934
+ start: Optional[int] = None,
935
+ end: Optional[int] = None,
936
+ ) -> int:
937
+ """
938
+ 在 *text* 中查找 *search_string* 的索引,忽略大小写。
939
+
940
+ :param text: 待搜索的字符串
941
+ :param search_string: 待查找的子串
942
+ :param start: 可选的起始位置
943
+ :param end: 可选的结束位置
944
+ :return: 索引值,未找到时返回 -1
945
+ """
946
+ return CharSequenceUtil.index_of(text, search_string, start=start, end=end, ignore_case=True)
947
+
948
+ @staticmethod
949
+ def last_index_of(
950
+ text: Optional[str],
951
+ search_string: Optional[str],
952
+ start: Optional[int] = None,
953
+ end: Optional[int] = None,
954
+ ignore_case: bool = False,
955
+ ) -> int:
956
+ """
957
+ 在 *text* 的可选范围 [start, end) 内查找 *search_string* 最后一次出现的索引。
958
+
959
+ :param text: 待搜索的字符串
960
+ :param search_string: 待查找的子串
961
+ :param start: 可选的起始位置
962
+ :param end: 可选的结束位置
963
+ :param ignore_case: 是否忽略大小写
964
+ :return: 索引值,未找到时返回 -1
965
+ """
966
+ if CharSequenceUtil.is_empty(text) or CharSequenceUtil.is_empty(search_string):
967
+ if CharSequenceUtil.equals(text, search_string):
968
+ return 0
969
+ return -1
970
+ if ignore_case:
971
+ text = text.lower()
972
+ search_string = search_string.lower()
973
+ return text.rfind(search_string, start, end)
974
+
975
+ @staticmethod
976
+ def last_index_of_ignore_case(
977
+ text: Optional[str],
978
+ search_string: Optional[str],
979
+ start: Optional[int] = None,
980
+ end: Optional[int] = None,
981
+ ) -> int:
982
+ """
983
+ 在 *text* 中查找 *search_string* 最后一次出现的索引,忽略大小写。
984
+
985
+ :param text: 待搜索的字符串
986
+ :param search_string: 待查找的子串
987
+ :param start: 可选的起始位置
988
+ :param end: 可选的结束位置
989
+ :return: 索引值,未找到时返回 -1
990
+ """
991
+ return CharSequenceUtil.last_index_of(text, search_string, start, end, ignore_case=True)
992
+
993
+ @staticmethod
994
+ def ordinal_index_of(text: Optional[str], search_string: Optional[str], ordinal: int) -> int:
995
+ """
996
+ 返回 *search_string* 在 *text* 中第 *ordinal* 次出现的索引。
997
+
998
+ :param text: 待搜索的字符串
999
+ :param search_string: 待查找的子串
1000
+ :param ordinal: 出现次数(从1开始)
1001
+ :return: 索引值,未找到时返回 -1
1002
+ """
1003
+ if text is None or search_string is None or ordinal <= 0:
1004
+ return -1
1005
+ if len(search_string) == 0:
1006
+ return 0
1007
+ found = 0
1008
+ index = -1
1009
+ for _ in range(ordinal):
1010
+ index = CharSequenceUtil.index_of(text, search_string, start=found)
1011
+ if index < 0:
1012
+ return index
1013
+ found = index + 1
1014
+ return index
1015
+
1016
+ # ------------------------------------------------------------------
1017
+ # Remove
1018
+ # ------------------------------------------------------------------
1019
+
1020
+ @staticmethod
1021
+ def remove_all(string: Optional[str], string_to_remove: Optional[str]) -> Optional[str]:
1022
+ """
1023
+ 从 *string* 中移除所有 *string_to_remove* 的出现。
1024
+
1025
+ :param string: 源字符串
1026
+ :param string_to_remove: 待移除的子串
1027
+ :return: 移除后的字符串
1028
+ """
1029
+ if CharSequenceUtil.is_empty(string) or CharSequenceUtil.is_empty(string_to_remove):
1030
+ return string
1031
+ return string.replace(string_to_remove, CharPool.EMPTY) # type: ignore[union-attr]
1032
+
1033
+ @staticmethod
1034
+ def remove_any(string: Optional[str], *strings_to_remove: Optional[str]) -> Optional[str]:
1035
+ """
1036
+ 从 *string* 中移除 *strings_to_remove* 中每个子串的所有出现。
1037
+
1038
+ :param string: 源字符串
1039
+ :param strings_to_remove: 待移除的子串列表
1040
+ :return: 移除后的字符串
1041
+ """
1042
+ result = string
1043
+ if CharSequenceUtil.is_not_empty(string):
1044
+ for string_to_remove in strings_to_remove:
1045
+ result = CharSequenceUtil.remove_all(result, string_to_remove)
1046
+ return result
1047
+
1048
+ @staticmethod
1049
+ def remove_all_line_breaks(string: Optional[str]) -> Optional[str]:
1050
+ """
1051
+ 从 *string* 中移除所有换行符(``\\r`` 和 ``\\n``)。
1052
+
1053
+ :param string: 源字符串
1054
+ :return: 不含换行符的字符串
1055
+ """
1056
+ return CharSequenceUtil.remove_any(string, CharPool.CR, CharPool.LF)
1057
+
1058
+ @staticmethod
1059
+ def remove_pre_and_lower_first(string: Optional[str], pre_or_length: Union[int, str]) -> Optional[str]:
1060
+ """
1061
+ 从 *string* 中移除前缀,并将剩余字符串的首字母小写。
1062
+
1063
+ :param string: 源字符串
1064
+ :param pre_or_length: 要移除的前缀字符串,或从头部移除的字符数
1065
+ :return: 处理后的字符串,*string* 为 None 时返回 None
1066
+ """
1067
+ if isinstance(pre_or_length, str):
1068
+ return CharSequenceUtil.lower_first(CharSequenceUtil.remove_prefix(string, pre_or_length))
1069
+ if string is None:
1070
+ return None
1071
+ if len(string) > pre_or_length:
1072
+ first = string[pre_or_length].lower()
1073
+ if len(string) > pre_or_length + 1:
1074
+ start_index = pre_or_length + 1
1075
+ return first + string[start_index:]
1076
+ return first
1077
+ return string
1078
+
1079
+ @staticmethod
1080
+ def remove_prefix(string: Optional[str], prefix: Optional[str]) -> Optional[str]:
1081
+ """
1082
+ 如果 *string* 以 *prefix* 开头则移除该前缀。
1083
+
1084
+ :param string: 源字符串
1085
+ :param prefix: 待移除的前缀
1086
+ :return: 移除前缀后的字符串
1087
+ """
1088
+ if CharSequenceUtil.is_empty(string) or CharSequenceUtil.is_empty(prefix):
1089
+ return string
1090
+ if CharSequenceUtil.start_with(string, prefix):
1091
+ return CharSequenceUtil.sub_suf(string, len(prefix)) # type: ignore[arg-type]
1092
+ return string
1093
+
1094
+ @staticmethod
1095
+ def remove_prefix_ignore_case(string: Optional[str], prefix: Optional[str]) -> Optional[str]:
1096
+ """
1097
+ 如果 *string* 以 *prefix* 开头则移除该前缀(忽略大小写)。
1098
+
1099
+ :param string: 源字符串
1100
+ :param prefix: 待移除的前缀
1101
+ :return: 移除前缀后的字符串
1102
+ """
1103
+ if CharSequenceUtil.is_empty(string) or CharSequenceUtil.is_empty(prefix):
1104
+ return string
1105
+ if CharSequenceUtil.start_with_ignore_case(string, prefix):
1106
+ return CharSequenceUtil.sub_suf(string, len(prefix)) # type: ignore[arg-type]
1107
+ return string
1108
+
1109
+ @staticmethod
1110
+ def remove_suffix(string: Optional[str], suffix: Optional[str]) -> Optional[str]:
1111
+ """
1112
+ 如果 *string* 以 *suffix* 结尾则移除该后缀。
1113
+
1114
+ :param string: 源字符串
1115
+ :param suffix: 待移除的后缀
1116
+ :return: 移除后缀后的字符串
1117
+ """
1118
+ if CharSequenceUtil.is_empty(string) or CharSequenceUtil.is_empty(suffix):
1119
+ return string
1120
+ if CharSequenceUtil.end_with(string, suffix):
1121
+ return CharSequenceUtil.sub_pre(string, len(suffix)) # type: ignore[arg-type]
1122
+ return string
1123
+
1124
+ @staticmethod
1125
+ def remove_suffix_and_lower_first(string: Optional[str], suffix: Optional[str]) -> Optional[str]:
1126
+ """
1127
+ 从 *string* 中移除后缀并将首字母小写。
1128
+
1129
+ :param string: 源字符串
1130
+ :param suffix: 待移除的后缀
1131
+ :return: 处理后的字符串
1132
+ """
1133
+ return CharSequenceUtil.lower_first(CharSequenceUtil.remove_suffix(string, suffix))
1134
+
1135
+ @staticmethod
1136
+ def remove_suffix_ignore_case(string: Optional[str], suffix: Optional[str]) -> Optional[str]:
1137
+ """
1138
+ 如果 *string* 以 *suffix* 结尾则移除该后缀(忽略大小写)。
1139
+
1140
+ :param string: 源字符串
1141
+ :param suffix: 待移除的后缀
1142
+ :return: 移除后缀后的字符串
1143
+ """
1144
+ if CharSequenceUtil.is_empty(string) or CharSequenceUtil.is_empty(suffix):
1145
+ return string
1146
+ if CharSequenceUtil.end_with_ignore_case(string, suffix):
1147
+ return CharSequenceUtil.sub_pre(string, len(suffix)) # type: ignore[arg-type]
1148
+ return string
1149
+
1150
+ # ------------------------------------------------------------------
1151
+ # Clean / strip
1152
+ # ------------------------------------------------------------------
1153
+
1154
+ @staticmethod
1155
+ def clean_blank(string: Optional[str]) -> Optional[str]:
1156
+ """
1157
+ 移除 *string* 中的所有空白字符。
1158
+
1159
+ :param string: 源字符串
1160
+ :return: 不含空白字符的字符串
1161
+ """
1162
+ return CharSequenceUtil.filter(string, lambda char: not CharUtil.is_blank_char(char))
1163
+
1164
+ @staticmethod
1165
+ def strip(string: Optional[str], prefix: str, suffix: Optional[str] = None) -> Optional[str]:
1166
+ """
1167
+ 从 *string* 头部去除 *prefix*,尾部去除 *suffix*。
1168
+ 若 *suffix* 为 None,则两端均去除 *prefix*。
1169
+
1170
+ :param string: 源字符串
1171
+ :param prefix: 待去除的前缀
1172
+ :param suffix: 待去除的后缀(默认为 *prefix*)
1173
+ :return: 去除后的字符串
1174
+ """
1175
+ if suffix is None:
1176
+ if CharSequenceUtil.equals(string, prefix):
1177
+ return CharPool.EMPTY
1178
+ suffix = prefix
1179
+
1180
+ if CharSequenceUtil.is_empty(string):
1181
+ return string
1182
+ from_index = 0
1183
+ to_index = len(string)
1184
+ if CharSequenceUtil.start_with(string, prefix):
1185
+ from_index = len(prefix)
1186
+ if CharSequenceUtil.end_with(string, suffix):
1187
+ to_index -= len(suffix)
1188
+ return string[from_index:to_index]
1189
+
1190
+ @staticmethod
1191
+ def strip_ignore_case(string: Optional[str], prefix: str, suffix: Optional[str] = None) -> Optional[str]:
1192
+ """
1193
+ 从 *string* 头部去除 *prefix*,尾部去除 *suffix*,忽略大小写。
1194
+
1195
+ :param string: 源字符串
1196
+ :param prefix: 待去除的前缀
1197
+ :param suffix: 待去除的后缀(默认为 *prefix*)
1198
+ :return: 去除后的字符串
1199
+ """
1200
+ if suffix is None:
1201
+ if CharSequenceUtil.equals_ignore_case(string, prefix):
1202
+ return CharPool.EMPTY
1203
+ suffix = prefix
1204
+
1205
+ if CharSequenceUtil.is_empty(string):
1206
+ return string
1207
+ from_index = 0
1208
+ to_index = len(string)
1209
+ if CharSequenceUtil.start_with_ignore_case(string, prefix):
1210
+ from_index = len(prefix)
1211
+ if CharSequenceUtil.end_with_ignore_case(string, suffix):
1212
+ to_index -= len(suffix)
1213
+ return string[from_index:to_index]
1214
+
1215
+ # ------------------------------------------------------------------
1216
+ # Add prefix / suffix if missing
1217
+ # ------------------------------------------------------------------
1218
+
1219
+ @staticmethod
1220
+ def add_prefix_if_not(string: Optional[str], prefix: Optional[str]) -> Optional[str]:
1221
+ """
1222
+ 如果 *string* 不以 *prefix* 开头则添加该前缀。
1223
+
1224
+ :param string: 源字符串
1225
+ :param prefix: 待添加的前缀
1226
+ :return: 确保有前缀的字符串
1227
+ """
1228
+ return CharSequenceUtil.prepend_if_missing(string, prefix, prefix)
1229
+
1230
+ @staticmethod
1231
+ def add_suffix_if_not(string: Optional[str], suffix: Optional[str]) -> Optional[str]:
1232
+ """
1233
+ 如果 *string* 不以 *suffix* 结尾则添加该后缀。
1234
+
1235
+ :param string: 源字符串
1236
+ :param suffix: 待添加的后缀
1237
+ :return: 确保有后缀的字符串
1238
+ """
1239
+ return CharSequenceUtil.append_if_missing(string, suffix, suffix)
1240
+
1241
+ # ------------------------------------------------------------------
1242
+ # Cut
1243
+ # ------------------------------------------------------------------
1244
+
1245
+ @staticmethod
1246
+ def cut(string: Optional[str], part_length: int) -> List[str]:
1247
+ """
1248
+ 将 *string* 按 *part_length* 个字符一组进行分割。
1249
+
1250
+ :param string: 源字符串
1251
+ :param part_length: 每组的长度
1252
+ :return: 分割后的字符串列表
1253
+ """
1254
+ if string is None:
1255
+ return []
1256
+ length = len(string)
1257
+ if length < part_length:
1258
+ return [string]
1259
+ part = (length // part_length) if length % part_length == 0 else (length // part_length + 1)
1260
+ result: List[str] = []
1261
+ for i in range(part):
1262
+ start = i * part_length
1263
+ end = length if i == part - 1 else (part_length + i * part_length)
1264
+ result.append(string[start:end])
1265
+ return result
1266
+
1267
+ # ------------------------------------------------------------------
1268
+ # Equals
1269
+ # ------------------------------------------------------------------
1270
+
1271
+ @staticmethod
1272
+ def equals(string1: Optional[str], string2: Optional[str], ignore_case: bool = False) -> bool:
1273
+ """
1274
+ 比较两个字符串是否相等。
1275
+
1276
+ :param string1: 第一个字符串
1277
+ :param string2: 第二个字符串
1278
+ :param ignore_case: 是否忽略大小写
1279
+ :return: 两个字符串是否相等(或均为 None)
1280
+ """
1281
+ if string1 is None:
1282
+ return string2 is None
1283
+ if string2 is None:
1284
+ return False
1285
+ if ignore_case:
1286
+ return string1.lower() == string2.lower()
1287
+ return string1 == string2
1288
+
1289
+ @staticmethod
1290
+ def equals_ignore_case(string1: Optional[str], string2: Optional[str]) -> bool:
1291
+ """
1292
+ 比较两个字符串是否相等,忽略大小写。
1293
+
1294
+ :param string1: 第一个字符串
1295
+ :param string2: 第二个字符串
1296
+ :return: 两个字符串是否相等(不区分大小写)
1297
+ """
1298
+ return CharSequenceUtil.equals(string1, string2, ignore_case=True)
1299
+
1300
+ # ------------------------------------------------------------------
1301
+ # Append / prepend if missing
1302
+ # ------------------------------------------------------------------
1303
+
1304
+ @staticmethod
1305
+ def append_if_missing(
1306
+ string: Optional[str], suffix: Optional[str], *suffixes: Optional[str], ignore_case: bool = False
1307
+ ) -> Optional[str]:
1308
+ """
1309
+ 如果 *string* 不以指定后缀结尾,则追加 *suffix*。
1310
+
1311
+ :param string: 源字符串
1312
+ :param suffix: 待追加的后缀
1313
+ :param suffixes: 额外的待检查后缀
1314
+ :param ignore_case: 检查时是否忽略大小写
1315
+ :return: 确保有后缀的字符串
1316
+ """
1317
+ if (
1318
+ string is None
1319
+ or CharSequenceUtil.is_empty(suffix)
1320
+ or CharSequenceUtil.end_with(string, suffix, ignore_case=ignore_case)
1321
+ ):
1322
+ return string
1323
+ if suffixes:
1324
+ for s in suffixes:
1325
+ if CharSequenceUtil.end_with(string, s, ignore_case):
1326
+ return string
1327
+ return string + suffix # type: ignore[operator]
1328
+
1329
+ @staticmethod
1330
+ def append_if_missing_ignore_case(
1331
+ string: Optional[str], suffix: Optional[str], *suffixes: Optional[str]
1332
+ ) -> Optional[str]:
1333
+ """
1334
+ 如果 *string* 不以 *suffix* 结尾则追加(忽略大小写)。
1335
+
1336
+ :param string: 源字符串
1337
+ :param suffix: 待追加的后缀
1338
+ :param suffixes: 额外的待检查后缀
1339
+ :return: 确保有后缀的字符串
1340
+ """
1341
+ return CharSequenceUtil.append_if_missing(string, suffix, *suffixes, ignore_case=True)
1342
+
1343
+ @staticmethod
1344
+ def prepend_if_missing(
1345
+ string: Optional[str], prefix: Optional[str], *prefixes: Optional[str], ignore_case: bool = False
1346
+ ) -> Optional[str]:
1347
+ """
1348
+ 如果 *string* 不以指定前缀开头,则前置 *prefix*。
1349
+
1350
+ :param string: 源字符串
1351
+ :param prefix: 待前置的前缀
1352
+ :param prefixes: 额外的待检查前缀
1353
+ :param ignore_case: 检查时是否忽略大小写
1354
+ :return: 确保有前缀的字符串
1355
+ """
1356
+ if (
1357
+ string is None
1358
+ or CharSequenceUtil.is_empty(prefix)
1359
+ or CharSequenceUtil.start_with(string, prefix, ignore_case=ignore_case)
1360
+ ):
1361
+ return string
1362
+ if prefixes:
1363
+ for p in prefixes:
1364
+ if CharSequenceUtil.start_with(string, p, ignore_case):
1365
+ return string
1366
+ return prefix + string # type: ignore[operator]
1367
+
1368
+ @staticmethod
1369
+ def prepend_if_missing_ignore_case(
1370
+ string: Optional[str], prefix: Optional[str], *prefixes: Optional[str]
1371
+ ) -> Optional[str]:
1372
+ """
1373
+ 如果 *string* 不以 *prefix* 开头则前置(忽略大小写)。
1374
+
1375
+ :param string: 源字符串
1376
+ :param prefix: 待前置的前缀
1377
+ :param prefixes: 额外的待检查前缀
1378
+ :return: 确保有前缀的字符串
1379
+ """
1380
+ return CharSequenceUtil.prepend_if_missing(string, prefix, *prefixes, ignore_case=True)
1381
+
1382
+ # ------------------------------------------------------------------
1383
+ # Upper / lower first
1384
+ # ------------------------------------------------------------------
1385
+
1386
+ @staticmethod
1387
+ def upper_first_and_add_pre(string: Optional[str], pre_string: Optional[str]) -> Optional[str]:
1388
+ """
1389
+ 将 *string* 首字母大写并前置 *pre_string*。
1390
+
1391
+ :param string: 源字符串
1392
+ :param pre_string: 待前置的前缀
1393
+ :return: 处理后的字符串,任一参数为 None 时返回 None
1394
+ """
1395
+ if string is None or pre_string is None:
1396
+ return None
1397
+ return pre_string + CharSequenceUtil.upper_first(string) # type: ignore[operator]
1398
+
1399
+ @staticmethod
1400
+ def upper_first(string: Optional[str]) -> Optional[str]:
1401
+ """
1402
+ 将 *string* 的首字母大写。
1403
+
1404
+ :param string: 源字符串
1405
+ :return: 首字母大写后的字符串
1406
+ """
1407
+ if string is None:
1408
+ return None
1409
+ if len(string) > 0:
1410
+ first_char = string[0]
1411
+ if CharUtil.is_letter_lower(first_char):
1412
+ return first_char.upper() + CharSequenceUtil.sub_suf(string, 1)
1413
+ return string
1414
+
1415
+ @staticmethod
1416
+ def lower_first(string: Optional[str]) -> Optional[str]:
1417
+ """
1418
+ 将 *string* 的首字母小写。
1419
+
1420
+ :param string: 源字符串
1421
+ :return: 首字母小写后的字符串
1422
+ """
1423
+ if string is None:
1424
+ return None
1425
+ if len(string) > 0:
1426
+ first_char = string[0]
1427
+ if CharUtil.is_letter_upper(first_char):
1428
+ return first_char.lower() + CharSequenceUtil.sub_suf(string, 1)
1429
+ return string
1430
+
1431
+ # ------------------------------------------------------------------
1432
+ # Filter
1433
+ # ------------------------------------------------------------------
1434
+
1435
+ @staticmethod
1436
+ def filter(string: Optional[str], filter_func: Callable[[str], bool]) -> Optional[str]:
1437
+ """
1438
+ 使用 *filter_func* 过滤 *string* 中的字符。仅保留
1439
+ *filter_func* 返回 True 的字符。
1440
+
1441
+ :param string: 源字符串
1442
+ :param filter_func: 过滤函数
1443
+ :return: 过滤后的字符串
1444
+ """
1445
+ if string is None or filter_func is None:
1446
+ return string
1447
+ return "".join([char for char in string if filter_func(char)])
1448
+
1449
+ # ------------------------------------------------------------------
1450
+ # Substring helpers (used internally)
1451
+ # ------------------------------------------------------------------
1452
+
1453
+ @staticmethod
1454
+ def sub_suf(string: Optional[str], from_index: int) -> Optional[str]:
1455
+ """
1456
+ 返回从 *from_index* 到末尾的子串。
1457
+
1458
+ :param string: 源字符串
1459
+ :param from_index: 起始索引(包含)
1460
+ :return: 子串,*string* 为 None 时返回 None
1461
+ """
1462
+ if string is None:
1463
+ return None
1464
+ if from_index < 0:
1465
+ from_index = 0
1466
+ if from_index >= len(string):
1467
+ return ""
1468
+ return string[from_index:]
1469
+
1470
+ @staticmethod
1471
+ def sub_pre(string: Optional[str], to_index: int) -> Optional[str]:
1472
+ """
1473
+ 返回从开头到末尾减去 *to_index* 个字符处的子串。
1474
+
1475
+ :param string: 源字符串
1476
+ :param to_index: 从末尾排除的字符数
1477
+ :return: 子串,*string* 为 None 时返回 None
1478
+ """
1479
+ if string is None:
1480
+ return None
1481
+ length = len(string)
1482
+ end = length - to_index
1483
+ if end <= 0:
1484
+ return ""
1485
+ return string[:end]
1486
+
1487
+ # ======================================================================
1488
+ # NEW METHODS ported from Java Hutool
1489
+ # ======================================================================
1490
+
1491
+ # ------------------------------------------------------------------
1492
+ # Substring operations
1493
+ # ------------------------------------------------------------------
1494
+
1495
+ @staticmethod
1496
+ def sub(string: Optional[str], start: int, end: Optional[int] = None) -> Optional[str]:
1497
+ """
1498
+ 提取子串,支持从末尾开始计数的负索引(类似 Python 切片)。
1499
+
1500
+ :param string: 源字符串
1501
+ :param start: 起始索引(包含)。负值表示从末尾开始计数。
1502
+ :param end: 结束索引(不包含)。负值表示从末尾开始计数。
1503
+ 为 None 时返回到字符串末尾。
1504
+ :return: 子串,*string* 为 None 时返回 None
1505
+ """
1506
+ if string is None:
1507
+ return None
1508
+ length = len(string)
1509
+ if start < 0:
1510
+ start += length
1511
+ if start < 0:
1512
+ start = 0
1513
+ if end is None:
1514
+ end = length
1515
+ if end < 0:
1516
+ end += length
1517
+ if end < 0:
1518
+ end = 0
1519
+ if start >= end:
1520
+ return ""
1521
+ return string[start:end]
1522
+
1523
+ @staticmethod
1524
+ def sub_before(string: Optional[str], separator: str, is_last: bool = False) -> Optional[str]:
1525
+ """
1526
+ 返回第一个(或最后一个)*separator* 出现之前的子串。
1527
+
1528
+ 未找到 *separator* 时返回整个字符串。
1529
+
1530
+ :param string: 源字符串
1531
+ :param separator: 分隔符字符串
1532
+ :param is_last: 是否使用最后一个分隔符
1533
+ :return: 分隔符之前的子串
1534
+ """
1535
+ if CharSequenceUtil.is_empty(string):
1536
+ return string
1537
+ if CharSequenceUtil.is_empty(separator):
1538
+ return string
1539
+ if is_last:
1540
+ pos = string.rfind(separator) # type: ignore[union-attr]
1541
+ else:
1542
+ pos = string.find(separator) # type: ignore[union-attr]
1543
+ if pos < 0:
1544
+ return string
1545
+ return string[:pos] # type: ignore[index]
1546
+
1547
+ @staticmethod
1548
+ def sub_after(string: Optional[str], separator: str, is_last: bool = False) -> Optional[str]:
1549
+ """
1550
+ 返回第一个(或最后一个)*separator* 出现之后的子串。
1551
+
1552
+ 未找到 *separator* 时返回整个字符串。
1553
+
1554
+ :param string: 源字符串
1555
+ :param separator: 分隔符字符串
1556
+ :param is_last: 是否使用最后一个分隔符
1557
+ :return: 分隔符之后的子串
1558
+ """
1559
+ if CharSequenceUtil.is_empty(string):
1560
+ return string
1561
+ if CharSequenceUtil.is_empty(separator):
1562
+ return string
1563
+ if is_last:
1564
+ pos = string.rfind(separator) # type: ignore[union-attr]
1565
+ else:
1566
+ pos = string.find(separator) # type: ignore[union-attr]
1567
+ if pos < 0:
1568
+ return string
1569
+ return string[pos + len(separator) :] # type: ignore[index]
1570
+
1571
+ @staticmethod
1572
+ def sub_between(string: Optional[str], prefix: str, suffix: str) -> Optional[str]:
1573
+ """
1574
+ 返回 *string* 中第一个 *prefix* 之后、下一个 *suffix* 之前的文本。
1575
+
1576
+ *string* 为 None/空,或找不到 *prefix*/*suffix* 时返回 None。
1577
+
1578
+ :param string: 源字符串
1579
+ :param prefix: 前标记
1580
+ :param suffix: 后标记
1581
+ :return: *prefix* 和 *suffix* 之间的文本,或 None
1582
+ """
1583
+ if CharSequenceUtil.is_empty(string) or CharSequenceUtil.is_empty(prefix) or CharSequenceUtil.is_empty(suffix):
1584
+ return None
1585
+
1586
+ start = string.find(prefix) # type: ignore[union-attr]
1587
+ if start < 0:
1588
+ return None
1589
+ start += len(prefix)
1590
+ end = string.find(suffix, start) # type: ignore[union-attr]
1591
+ if end < 0:
1592
+ return None
1593
+ return string[start:end] # type: ignore[index]
1594
+
1595
+ @staticmethod
1596
+ def sub_between_all(string: Optional[str], prefix: str, suffix: str) -> List[str]:
1597
+ """
1598
+ 返回 *string* 中所有 *prefix* 和 *suffix* 之间的不重叠子串。
1599
+
1600
+ :param string: 源字符串
1601
+ :param prefix: 前标记
1602
+ :param suffix: 后标记
1603
+ :return: *prefix* 和 *suffix* 之间的子串列表
1604
+ """
1605
+ if CharSequenceUtil.is_empty(string) or CharSequenceUtil.is_empty(prefix) or CharSequenceUtil.is_empty(suffix):
1606
+ return []
1607
+
1608
+ result: List[str] = []
1609
+ remaining = string
1610
+ while True:
1611
+ piece = CharSequenceUtil.sub_between(remaining, prefix, suffix)
1612
+ if piece is None:
1613
+ break
1614
+ result.append(piece)
1615
+ # advance past this match
1616
+ idx = remaining.find(prefix + piece + suffix) # type: ignore[union-attr]
1617
+ if idx < 0:
1618
+ break
1619
+ remaining = remaining[idx + len(prefix) + len(piece) + len(suffix) :] # type: ignore[index]
1620
+ return result
1621
+
1622
+ # ------------------------------------------------------------------
1623
+ # Count
1624
+ # ------------------------------------------------------------------
1625
+
1626
+ @staticmethod
1627
+ def count(
1628
+ string: Optional[str],
1629
+ char_or_start: str,
1630
+ end: Optional[str] = None,
1631
+ ) -> int:
1632
+ """
1633
+ 统计字符或子串的出现次数。
1634
+
1635
+ 两种调用方式(与 Java Hutool 对齐):
1636
+
1637
+ - ``count(str, char)`` -- 统计 *char* 在 *str* 中出现的次数。
1638
+ - ``count(str, start, end)`` -- 统计 *str* 中 *start* 和 *end* 标记之间的子串数量。
1639
+
1640
+ :param string: 源字符串
1641
+ :param char_or_start: 待统计的字符/子串,或当提供 *end* 时作为起始标记
1642
+ :param end: 结束标记。提供时 *char_or_start* 视为起始标记
1643
+ :return: 统计次数
1644
+ """
1645
+ if CharSequenceUtil.is_empty(string):
1646
+ return 0
1647
+ if end is not None:
1648
+ # count between markers
1649
+ count_val = 0
1650
+ remaining = string
1651
+ while True:
1652
+ sub_piece = CharSequenceUtil.sub_between(remaining, char_or_start, end)
1653
+ if sub_piece is None:
1654
+ break
1655
+ count_val += 1
1656
+ idx = remaining.find(char_or_start + sub_piece + end) # type: ignore[union-attr]
1657
+ if idx < 0:
1658
+ break
1659
+ remaining = remaining[idx + len(char_or_start) + len(sub_piece) + len(end) :] # type: ignore[index]
1660
+ return count_val
1661
+ return string.count(char_or_start) # type: ignore[union-attr]
1662
+
1663
+ # ------------------------------------------------------------------
1664
+ # Replace
1665
+ # ------------------------------------------------------------------
1666
+
1667
+ @staticmethod
1668
+ def replace(string: Optional[str], search_str: str, replacement: str, count: int = -1) -> Optional[str]:
1669
+ """
1670
+ 将 *string* 中的 *search_str* 替换为 *replacement*。
1671
+
1672
+ :param string: 源字符串
1673
+ :param search_str: 待查找的字符串
1674
+ :param replacement: 替换字符串
1675
+ :param count: 最大替换次数。``-1`` 表示全部替换(默认)
1676
+ :return: 替换后的字符串
1677
+ """
1678
+ if CharSequenceUtil.is_empty(string) or CharSequenceUtil.is_empty(search_str):
1679
+ return string
1680
+ if count == -1:
1681
+ return string.replace(search_str, replacement) # type: ignore[union-attr]
1682
+ return string.replace(search_str, replacement, count) # type: ignore[union-attr]
1683
+
1684
+ @staticmethod
1685
+ def replace_chars(string: Optional[str], chars: str, replacement: str) -> Optional[str]:
1686
+ """
1687
+ 将 *string* 中出现在 *chars* 里的每个字符替换为 *replacement*。
1688
+
1689
+ :param string: 源字符串
1690
+ :param chars: 包含待替换字符的字符串
1691
+ :param replacement: 每个匹配字符的替换字符串
1692
+ :return: 替换后的字符串
1693
+ """
1694
+ if CharSequenceUtil.is_empty(string):
1695
+ return string
1696
+ result = string
1697
+ for ch in chars:
1698
+ result = result.replace(ch, replacement) # type: ignore[union-attr]
1699
+ return result
1700
+
1701
+ @staticmethod
1702
+ def replace_first(string: Optional[str], regex: str, replacement: str) -> Optional[str]:
1703
+ """
1704
+ 替换 *string* 中 *regex* 的第一个匹配项为 *replacement*。
1705
+
1706
+ :param string: 源字符串
1707
+ :param regex: 正则表达式模式
1708
+ :param replacement: 替换字符串(可包含反向引用如 ``\\1``)
1709
+ :return: 替换第一个正则匹配后的字符串
1710
+ """
1711
+ if CharSequenceUtil.is_empty(string):
1712
+ return string
1713
+ return re.sub(regex, replacement, string, count=1) # type: ignore[arg-type]
1714
+
1715
+ @staticmethod
1716
+ def replace_all(string: Optional[str], regex: str, replacement: str) -> Optional[str]:
1717
+ """
1718
+ 替换 *string* 中 *regex* 的所有匹配项为 *replacement*。
1719
+
1720
+ :param string: 源字符串
1721
+ :param regex: 正则表达式模式
1722
+ :param replacement: 替换字符串(可包含反向引用如 ``\\1``)
1723
+ :return: 替换所有正则匹配后的字符串
1724
+ """
1725
+ if CharSequenceUtil.is_empty(string):
1726
+ return string
1727
+ return re.sub(regex, replacement, string) # type: ignore[arg-type]
1728
+
1729
+ # ------------------------------------------------------------------
1730
+ # Join
1731
+ # ------------------------------------------------------------------
1732
+
1733
+ @staticmethod
1734
+ def join(sep: str, *args: Any) -> str:
1735
+ """
1736
+ 将 *args* 用 *sep* 连接成一个字符串。
1737
+
1738
+ 每个元素通过 ``str()`` 转换为字符串。None 值表示为 ``""``。
1739
+
1740
+ :param sep: 分隔符
1741
+ :param args: 待连接的值
1742
+ :return: 连接后的字符串
1743
+ """
1744
+ parts = []
1745
+ for arg in args:
1746
+ if arg is None:
1747
+ parts.append("")
1748
+ elif isinstance(arg, (list, tuple)):
1749
+ for item in arg:
1750
+ parts.append("" if item is None else str(item))
1751
+ else:
1752
+ parts.append(str(arg))
1753
+ return sep.join(parts)
1754
+
1755
+ @staticmethod
1756
+ def join_array(array: list, conjunction: str) -> str:
1757
+ """
1758
+ 将 *array* 的元素用 *conjunction* 连接成一个字符串。
1759
+
1760
+ :param array: 待连接的值列表
1761
+ :param conjunction: 分隔符
1762
+ :return: 连接后的字符串
1763
+ """
1764
+ if not array:
1765
+ return ""
1766
+ return conjunction.join(str(item) for item in array)
1767
+
1768
+ # ------------------------------------------------------------------
1769
+ # Split
1770
+ # ------------------------------------------------------------------
1771
+
1772
+ @staticmethod
1773
+ def split(
1774
+ string: Optional[str],
1775
+ separator: str,
1776
+ limit: int = -1,
1777
+ is_trim: bool = False,
1778
+ ignore_empty: bool = False,
1779
+ ) -> List[str]:
1780
+ """
1781
+ 按 *separator* 分割 *string*,支持可选的裁剪和空元素移除。
1782
+
1783
+ :param string: 源字符串
1784
+ :param separator: 分隔符字符串
1785
+ :param limit: 最大分割份数(类似 ``str.split(sep, n)``)。``-1`` 表示不限制
1786
+ :param is_trim: 是否裁剪每个部分
1787
+ :param ignore_empty: 是否移除空部分
1788
+ :return: 分割后的字符串列表
1789
+ """
1790
+ if CharSequenceUtil.is_empty(string):
1791
+ return []
1792
+
1793
+ if limit == -1:
1794
+ parts = string.split(separator) # type: ignore[union-attr]
1795
+ else:
1796
+ parts = string.split(separator, limit) # type: ignore[union-attr]
1797
+
1798
+ if is_trim:
1799
+ parts = [p.strip() for p in parts]
1800
+ if ignore_empty:
1801
+ parts = [p for p in parts if p != ""]
1802
+ return parts
1803
+
1804
+ # ------------------------------------------------------------------
1805
+ # Pad
1806
+ # ------------------------------------------------------------------
1807
+
1808
+ @staticmethod
1809
+ def pad(string: Optional[str], size: int, pad_str: str = " ", is_right: bool = True) -> str:
1810
+ """
1811
+ 使用 *pad_str* 将 *string* 填充到 *size* 个字符。
1812
+
1813
+ 如果字符串已经至少 *size* 个字符,则原样返回。
1814
+
1815
+ :param string: 源字符串
1816
+ :param size: 目标长度
1817
+ :param pad_str: 填充字符串(默认 ``" "``)
1818
+ :param is_right: True = 右侧填充(左对齐),False = 左侧填充(右对齐)
1819
+ :return: 填充后的字符串
1820
+ """
1821
+ if string is None:
1822
+ string = ""
1823
+ if len(pad_str) == 0:
1824
+ pad_str = " "
1825
+ if is_right:
1826
+ return string.ljust(size, pad_str)
1827
+ return string.rjust(size, pad_str)
1828
+
1829
+ @staticmethod
1830
+ def center(string: Optional[str], size: int, pad_str: str = " ") -> str:
1831
+ """
1832
+ 使用 *pad_str* 将 *string* 居中对齐到 *size* 个字符。
1833
+
1834
+ :param string: 源字符串
1835
+ :param size: 目标长度
1836
+ :param pad_str: 填充字符串(默认 ``" "``)
1837
+ :return: 居中对齐后的字符串
1838
+ """
1839
+ if string is None:
1840
+ string = ""
1841
+ if len(pad_str) == 0:
1842
+ pad_str = " "
1843
+ return string.center(size, pad_str)
1844
+
1845
+ # ------------------------------------------------------------------
1846
+ # Repeat
1847
+ # ------------------------------------------------------------------
1848
+
1849
+ @staticmethod
1850
+ def repeat(string: Optional[str], count: int) -> str:
1851
+ """
1852
+ 将 *string* 重复 *count* 次。
1853
+
1854
+ :param string: 待重复的字符串
1855
+ :param count: 重复次数
1856
+ :return: 重复后的字符串
1857
+ """
1858
+ if CharSequenceUtil.is_empty(string) or count <= 0:
1859
+ return ""
1860
+ return string * count # type: ignore[operator]
1861
+
1862
+ @staticmethod
1863
+ def repeat_and_join(string: Optional[str], count: int, conjunction: str) -> str:
1864
+ """
1865
+ 将 *string* 重复 *count* 次,每次重复之间用 *conjunction* 连接。
1866
+
1867
+ :param string: 待重复的字符串
1868
+ :param count: 重复次数
1869
+ :param conjunction: 重复之间的分隔符
1870
+ :return: 重复并连接后的字符串
1871
+ """
1872
+ if CharSequenceUtil.is_empty(string) or count <= 0:
1873
+ return ""
1874
+ return conjunction.join([string] * count) # type: ignore[list-item]
1875
+
1876
+ # ------------------------------------------------------------------
1877
+ # Naming conversions
1878
+ # ------------------------------------------------------------------
1879
+
1880
+ @staticmethod
1881
+ def to_camel_case(string: Optional[str]) -> Optional[str]:
1882
+ """
1883
+ 将字符串转换为 camelCase(驼峰命名法)。
1884
+
1885
+ 支持 ``snake_case``、``kebab-case`` 和空格分隔的输入。
1886
+
1887
+ Examples::
1888
+
1889
+ "hello_world" -> "helloWorld"
1890
+ "hello-world" -> "helloWorld"
1891
+ "HELLO_WORLD" -> "helloWorld"
1892
+
1893
+ :param string: 源字符串
1894
+ :return: 驼峰命名的字符串
1895
+ """
1896
+ if CharSequenceUtil.is_empty(string):
1897
+ return string
1898
+
1899
+ # split on underscores, hyphens, and spaces
1900
+ words = re.split(r"[_\- ]+", string) # type: ignore[arg-type]
1901
+ if not words:
1902
+ return string
1903
+
1904
+ result = words[0].lower()
1905
+ for word in words[1:]:
1906
+ if word:
1907
+ result += word[0].upper() + word[1:].lower()
1908
+ return result
1909
+
1910
+ @staticmethod
1911
+ def to_snake_case(string: Optional[str]) -> Optional[str]:
1912
+ """
1913
+ 将字符串转换为 snake_case(蛇形命名法)。
1914
+
1915
+ 支持 ``camelCase``、``PascalCase``、``kebab-case`` 和空格分隔的输入。
1916
+
1917
+ Examples::
1918
+
1919
+ "helloWorld" -> "hello_world"
1920
+ "HelloWorld" -> "hello_world"
1921
+ "hello-world" -> "hello_world"
1922
+
1923
+ :param string: 源字符串
1924
+ :return: 蛇形命名的字符串
1925
+ """
1926
+ if CharSequenceUtil.is_empty(string):
1927
+ return string
1928
+
1929
+ # Replace hyphens and spaces with underscores
1930
+ s = re.sub(r"[\- ]+", "_", string) # type: ignore[arg-type]
1931
+
1932
+ # Insert underscore before uppercase letters preceded by lowercase
1933
+ s = re.sub(r"([a-z])([A-Z])", r"\1_\2", s)
1934
+ # Insert underscore between consecutive uppercase and following
1935
+ # lowercase (e.g. "HTMLParser" -> "HTML_Parser")
1936
+ s = re.sub(r"([A-Z]+)([A-Z][a-z])", r"\1_\2", s)
1937
+
1938
+ return s.lower()
1939
+
1940
+ @staticmethod
1941
+ def to_under_score_case(string: Optional[str]) -> Optional[str]:
1942
+ """
1943
+ :meth:`to_snake_case` 的别名。
1944
+
1945
+ :param string: 源字符串
1946
+ :return: 蛇形命名的字符串
1947
+ """
1948
+ return CharSequenceUtil.to_snake_case(string)
1949
+
1950
+ # ------------------------------------------------------------------
1951
+ # Similarity
1952
+ # ------------------------------------------------------------------
1953
+
1954
+ @staticmethod
1955
+ def similar(str1: Optional[str], str2: Optional[str], is_ignore_case: bool = False) -> float:
1956
+ """
1957
+ 使用 Python 的 ``difflib.SequenceMatcher`` 计算两个字符串的相似度。
1958
+ 返回 [0.0, 1.0] 之间的浮点数。
1959
+
1960
+ :param str1: 第一个字符串
1961
+ :param str2: 第二个字符串
1962
+ :param is_ignore_case: 是否忽略大小写
1963
+ :return: 相似度比率(0.0 到 1.0)
1964
+ """
1965
+ if str1 is None and str2 is None:
1966
+ return 1.0
1967
+ if str1 is None or str2 is None:
1968
+ return 0.0
1969
+ s1 = str1.lower() if is_ignore_case else str1
1970
+ s2 = str2.lower() if is_ignore_case else str2
1971
+ return SequenceMatcher(None, s1, s2).ratio()
1972
+
1973
+ # ------------------------------------------------------------------
1974
+ # Format with map
1975
+ # ------------------------------------------------------------------
1976
+
1977
+ @staticmethod
1978
+ def format_with_map(template: str, map_: dict, ignore_null: bool = False) -> str:
1979
+ """
1980
+ 通过将 ``{key}`` 占位符替换为 *map_* 中的值来格式化 *template*。
1981
+
1982
+ 如果 *ignore_null* 为 False 且键不存在,占位符保持不变。
1983
+ 如果 *ignore_null* 为 True,缺失的键将被替换为空字符串。
1984
+
1985
+ :param template: 包含 ``{key}`` 占位符的模板字符串
1986
+ :param map_: 映射键到替换值的字典
1987
+ :param ignore_null: 为 True 时,缺失的键替换为 ``""``
1988
+ :return: 格式化后的字符串
1989
+ """
1990
+
1991
+ def _replacer(match: re.Match) -> str:
1992
+ key = match.group(1)
1993
+ if key in map_:
1994
+ return str(map_[key])
1995
+ if ignore_null:
1996
+ return ""
1997
+ return match.group(0)
1998
+
1999
+ return re.sub(r"\{(\w+)\}", _replacer, template)
2000
+
2001
+ # ------------------------------------------------------------------
2002
+ # Type checks
2003
+ # ------------------------------------------------------------------
2004
+
2005
+ @staticmethod
2006
+ def is_numeric(string: Optional[str]) -> bool:
2007
+ """
2008
+ 判断 *string* 是否全部由数字字符组成。
2009
+ 允许前导 ``-`` 号(与 Java Hutool 的 ``StrUtil.isNumeric`` 对齐)。
2010
+
2011
+ :param string: 待检查的字符串
2012
+ :return: 是否为数字字符串
2013
+ """
2014
+ if CharSequenceUtil.is_empty(string):
2015
+ return False
2016
+ s = string.lstrip("-") # type: ignore[union-attr]
2017
+ if len(s) == 0:
2018
+ return False
2019
+ return s.isdigit()
2020
+
2021
+ @staticmethod
2022
+ def is_number(string: Optional[str]) -> bool:
2023
+ """
2024
+ 判断 *string* 是否表示有效的数字(整数或浮点数)。
2025
+
2026
+ :param string: 待检查的字符串
2027
+ :return: 是否为有效数字
2028
+ """
2029
+ if CharSequenceUtil.is_empty(string):
2030
+ return False
2031
+ try:
2032
+ float(string)
2033
+ return True
2034
+ except (ValueError, TypeError):
2035
+ return False
2036
+
2037
+ @staticmethod
2038
+ def is_alpha(string: Optional[str]) -> bool:
2039
+ """
2040
+ 判断 *string* 是否全部由字母组成。
2041
+
2042
+ :param string: 待检查的字符串
2043
+ :return: 是否全部为字母
2044
+ """
2045
+ if CharSequenceUtil.is_empty(string):
2046
+ return False
2047
+ return string.isalpha() # type: ignore[union-attr]
2048
+
2049
+ @staticmethod
2050
+ def is_alpha_upper(string: Optional[str]) -> bool:
2051
+ """
2052
+ 判断 *string* 是否全部由大写字母组成。
2053
+
2054
+ :param string: 待检查的字符串
2055
+ :return: 是否全部为大写字母
2056
+ """
2057
+ if CharSequenceUtil.is_empty(string):
2058
+ return False
2059
+ return string.isalpha() and string.isupper() # type: ignore[union-attr]
2060
+
2061
+ @staticmethod
2062
+ def is_alpha_lower(string: Optional[str]) -> bool:
2063
+ """
2064
+ 判断 *string* 是否全部由小写字母组成。
2065
+
2066
+ :param string: 待检查的字符串
2067
+ :return: 是否全部为小写字母
2068
+ """
2069
+ if CharSequenceUtil.is_empty(string):
2070
+ return False
2071
+ return string.isalpha() and string.islower() # type: ignore[union-attr]
2072
+
2073
+
2074
+ # ---------------------------------------------------------------------------
2075
+ # StrPool
2076
+ # ---------------------------------------------------------------------------
2077
+ class StrPool(CharPool):
2078
+ """常用字符串常量。"""
2079
+
2080
+ # 字符串常量:'None'
2081
+ NONE: str = "None"
2082
+ # 字符串常量:双点 "..",
2083
+ # 用途:作为指向上级文件夹的路径,如:"../path"
2084
+ DOUBLE_DOT: str = ".."
2085
+ # 字符串常量:Windows 换行 "\r\n"
2086
+ CRLF: str = "\r\n"
2087
+ # 字符串常量:HTML 不间断空格转义 "&nbsp;"
2088
+ HTML_NBSP: str = "&nbsp;"
2089
+ # 字符串常量:HTML And 符转义 "&amp;"
2090
+ HTML_AMP: str = "&amp;"
2091
+ # 字符串常量:HTML 双引号转义 "&quot;"
2092
+ HTML_QUOTE: str = "&quot;"
2093
+ # 字符串常量:HTML 单引号转义 "&apos;"
2094
+ HTML_APOS: str = "&apos;"
2095
+ # 字符串常量:HTML 小于号转义 "&lt;"
2096
+ HTML_LT: str = "&lt;"
2097
+ # 字符串常量:HTML 大于号转义 "&gt;"
2098
+ HTML_GT: str = "&gt;"
2099
+ # 字符串常量:空 JSON "{}"
2100
+ EMPTY_JSON: str = "{}"
2101
+
2102
+
2103
+ # ---------------------------------------------------------------------------
2104
+ # StrUtil
2105
+ # ---------------------------------------------------------------------------
2106
+ class StrUtil(CharSequenceUtil, StrPool):
2107
+ """
2108
+ 主字符串工具类。继承 :class:`CharSequenceUtil` 和 :class:`StrPool`,
2109
+ 提供所有字符串操作的统一入口。
2110
+ """
2111
+
2112
+ @staticmethod
2113
+ def is_blank_if_str(obj: Any) -> bool:
2114
+ """
2115
+ 如果 *obj* 是字符串,判断其是否为空白。非字符串对象返回 False(None 返回 True)。
2116
+
2117
+ :param obj: 待检查的对象
2118
+ :return: *obj* 为 None 或为空白字符串时返回 True
2119
+ """
2120
+ if obj is None:
2121
+ return True
2122
+ elif isinstance(obj, str):
2123
+ return CharSequenceUtil.is_blank(obj)
2124
+ return False
2125
+
2126
+ @staticmethod
2127
+ def is_empty_if_str(obj: Any) -> bool:
2128
+ """
2129
+ 如果 *obj* 是字符串,判断其是否为空。非字符串对象返回 False(None 返回 True)。
2130
+
2131
+ :param obj: 待检查的对象
2132
+ :return: *obj* 为 None 或为空字符串时返回 True
2133
+ """
2134
+ if obj is None:
2135
+ return True
2136
+ elif isinstance(obj, str):
2137
+ return CharSequenceUtil.is_empty(obj)
2138
+ return False
2139
+
2140
+ @staticmethod
2141
+ def trim(
2142
+ string: Union[List[str], str, None],
2143
+ mode: int = 0,
2144
+ predicate: Callable[[str], bool] = CharUtil.is_blank_char,
2145
+ ) -> Union[str, None]:
2146
+ """
2147
+ 裁剪字符串,或就地裁剪字符串列表中的每个元素。
2148
+
2149
+ :param string: 字符串或字符串列表
2150
+ :param mode: ``-1`` = 仅裁剪头部,``0`` = 两端裁剪,``1`` = 仅裁剪尾部
2151
+ :param predicate: 返回 True 时表示该字符应被移除的函数
2152
+ :return: 裁剪后的字符串或 None
2153
+ """
2154
+ if isinstance(string, str):
2155
+ return CharSequenceUtil.trim(string, mode=mode, predicate=predicate)
2156
+
2157
+ if string is None:
2158
+ return None
2159
+ for i in range(len(string)):
2160
+ s = string[i]
2161
+ if s is not None:
2162
+ string[i] = CharSequenceUtil.trim(s) # type: ignore[index]
2163
+ return None
2164
+
2165
+ @staticmethod
2166
+ def to_str(obj: Any, charset: str = "utf-8") -> Optional[str]:
2167
+ """
2168
+ 将 *obj* 转换为字符串。
2169
+
2170
+ ``bytes``/``bytearray``/``array.array`` 使用 *charset* 解码。列表会递归转换。
2171
+
2172
+ :param obj: 待转换的对象
2173
+ :param charset: 字符编码
2174
+ :return: 字符串表示
2175
+ """
2176
+ if obj is None:
2177
+ return None
2178
+
2179
+ if isinstance(obj, str):
2180
+ return obj
2181
+ elif isinstance(obj, (bytes, bytearray, array.array)):
2182
+ return obj.decode(charset)
2183
+ elif isinstance(obj, memoryview):
2184
+ return obj.tobytes().decode(charset)
2185
+ elif isinstance(obj, list):
2186
+ return str(StrUtil.to_str(item, charset=charset) for item in obj)
2187
+ return str(obj)
2188
+
2189
+ @staticmethod
2190
+ def to_str_or_none(obj: Any) -> Optional[str]:
2191
+ """
2192
+ 将 *obj* 转换为字符串,None 输入返回 None。
2193
+
2194
+ :param obj: 待转换的对象
2195
+ :return: 字符串表示或 None
2196
+ """
2197
+ return None if obj is None else str(obj)
2198
+
2199
+ @staticmethod
2200
+ def reverse(string: Optional[str]) -> Optional[str]:
2201
+ """
2202
+ 反转 *string*。
2203
+
2204
+ :param string: 待反转的字符串
2205
+ :return: 反转后的字符串
2206
+ """
2207
+ if CharSequenceUtil.is_empty(string):
2208
+ return string
2209
+ return string[::-1] # type: ignore[index]
2210
+
2211
+ @staticmethod
2212
+ def fill_before(string: Optional[str], filled_char: str, length: int) -> Optional[str]:
2213
+ """
2214
+ 使用 *filled_char* 将 *string* 左侧填充到 *length*。
2215
+
2216
+ :param string: 待填充的字符串
2217
+ :param filled_char: 单个填充字符
2218
+ :param length: 目标长度
2219
+ :return: 填充后的字符串
2220
+ """
2221
+ return StrUtil.fill(string, filled_char, length, True)
2222
+
2223
+ @staticmethod
2224
+ def fill_after(string: Optional[str], filled_char: str, length: int) -> Optional[str]:
2225
+ """
2226
+ 使用 *filled_char* 将 *string* 右侧填充到 *length*。
2227
+
2228
+ :param string: 待填充的字符串
2229
+ :param filled_char: 单个填充字符
2230
+ :param length: 目标长度
2231
+ :return: 填充后的字符串
2232
+ """
2233
+ return StrUtil.fill(string, filled_char, length, False)
2234
+
2235
+ @staticmethod
2236
+ def fill(string: Optional[str], filled_char: str, length: int, is_pre: bool) -> Optional[str]:
2237
+ """
2238
+ 使用 *filled_char* 将 *string* 填充到 *length*,
2239
+ 可选择左侧填充(``is_pre=True``)或右侧填充(``is_pre=False``)。
2240
+
2241
+ :param string: 待填充的字符串
2242
+ :param filled_char: 单个填充字符
2243
+ :param length: 目标长度
2244
+ :param is_pre: 是否左侧填充
2245
+ :return: 填充后的字符串
2246
+ :raises TypeError: *filled_char* 不是单个字符时抛出异常
2247
+ """
2248
+ if CharSequenceUtil.is_empty(filled_char):
2249
+ return string
2250
+ if len(filled_char) > 1:
2251
+ raise TypeError("The fill character must be exactly one character long")
2252
+ if string is None:
2253
+ return filled_char * length
2254
+ if is_pre:
2255
+ return string.rjust(length, filled_char)
2256
+ return string.ljust(length, filled_char)
2257
+
2258
+ @staticmethod
2259
+ def format(template: str, *args: Any, **kwargs: Any) -> str:
2260
+ """
2261
+ 使用 Python 的 ``str.format()`` 格式化 *template*。
2262
+
2263
+ :param template: 模板字符串
2264
+ :param args: 位置参数
2265
+ :param kwargs: 关键字参数
2266
+ :return: 格式化后的字符串
2267
+ """
2268
+ return template.format(*args, **kwargs)
2269
+
2270
+ @staticmethod
2271
+ def truncate_utf8(string: Optional[str], max_bytes: int) -> Optional[str]:
2272
+ """
2273
+ 截断 *string* 使其 UTF-8 编码不超过 *max_bytes*。
2274
+ 截断时会附加省略号(``...``)。
2275
+
2276
+ :param string: 待截断的字符串
2277
+ :param max_bytes: 最大字节长度
2278
+ :return: 截断后的字符串
2279
+ """
2280
+ return StrUtil.truncate_by_byte_length(string, "utf-8", max_bytes, 4, True)
2281
+
2282
+ @staticmethod
2283
+ def truncate_by_byte_length(
2284
+ string: Optional[str],
2285
+ charset: str,
2286
+ max_bytes: int,
2287
+ factor: int,
2288
+ append_dots: bool,
2289
+ ) -> Optional[str]:
2290
+ """
2291
+ 截断 *string* 使其编码字节长度不超过 *max_bytes*。
2292
+
2293
+ *factor* 是速度估计除数(编码中单个字符的最大字节长度)。
2294
+
2295
+ :param string: 待截断的字符串
2296
+ :param charset: 字符编码
2297
+ :param max_bytes: 最大字节长度
2298
+ :param factor: 速度估计除数
2299
+ :param append_dots: 截断后是否附加 ``...``
2300
+ :return: 截断后的字符串
2301
+ """
2302
+ if string is None or len(string) * factor <= max_bytes:
2303
+ return string
2304
+
2305
+ sba = string.encode(charset)
2306
+ if len(sba) <= max_bytes:
2307
+ return string
2308
+
2309
+ limit_bytes = max_bytes
2310
+ if append_dots:
2311
+ limit_bytes -= len("...".encode(charset))
2312
+
2313
+ bb = sba[:limit_bytes]
2314
+ decoder = codecs.getincrementaldecoder(charset)(errors="ignore")
2315
+ result = decoder.decode(bb, final=True)
2316
+
2317
+ if append_dots:
2318
+ return result + "..."
2319
+
2320
+ return result