diff --git a/hutool-core/src/main/java/org/dromara/hutool/core/text/CharSequenceUtil.java b/hutool-core/src/main/java/org/dromara/hutool/core/text/CharSequenceUtil.java index 31b17caf9..2d87c7913 100644 --- a/hutool-core/src/main/java/org/dromara/hutool/core/text/CharSequenceUtil.java +++ b/hutool-core/src/main/java/org/dromara/hutool/core/text/CharSequenceUtil.java @@ -1419,7 +1419,7 @@ public class CharSequenceUtil extends StrValidator { * } * * - * @param str 被处理的字符串 + * @param str 被处理的字符串,{@code null}忽略 * @param prefixOrSuffix 前缀或后缀 * @return 处理后的字符串 * @since 3.1.2 @@ -1449,8 +1449,8 @@ public class CharSequenceUtil extends StrValidator { * * * @param str 被处理的字符串 - * @param prefix 前缀 - * @param suffix 后缀 + * @param prefix 前缀,{@code null}忽略 + * @param suffix 后缀,{@code null}忽略 * @return 处理后的字符串 * @since 3.1.2 */ @@ -1475,8 +1475,8 @@ public class CharSequenceUtil extends StrValidator { * * * @param str 被处理的字符串 - * @param prefix 前缀 - * @param suffix 后缀 + * @param prefix 前缀,{@code null}忽略 + * @param suffix 后缀,{@code null}忽略 * @param ignoreCase 是否忽略大小写 * @return 处理后的字符串 * @since 3.1.2 @@ -1486,29 +1486,8 @@ public class CharSequenceUtil extends StrValidator { return toStringOrNull(str); } - final String str2 = str.toString(); - int from = 0; - int to = str2.length(); - - if (startWith(str2, prefix, ignoreCase)) { - from = prefix.length(); - if (from == to) { - // "a", "a", "a" -> "" - return EMPTY; - } - } - if (endWith(str2, suffix, ignoreCase)) { - to -= suffix.length(); - if (from == to) { - // "a", "a", "a" -> "" - return EMPTY; - } else if (to < from) { - // pre去除后和suffix有重叠,如 ("aba", "ab", "ba") -> "a" - to += suffix.length(); - } - } - - return str2.substring(from, to); + return new StrStripper(prefix, suffix, ignoreCase, false) + .apply(str); } /** @@ -1522,7 +1501,7 @@ public class CharSequenceUtil extends StrValidator { * * * @param str 被处理的字符串 - * @param prefixOrSuffix 前缀或后缀 + * @param prefixOrSuffix 前缀或后缀,{@code null}忽略 * @return 处理后的字符串 * @since 5.8.30 */ @@ -1558,44 +1537,47 @@ public class CharSequenceUtil extends StrValidator { * @param prefix 前缀 * @param suffix 后缀 * @return 处理后的字符串 - * @since 5.8.30 + * @since 6.0.0 */ public static String stripAll(final CharSequence str, final CharSequence prefix, final CharSequence suffix) { + return stripAll(str, prefix, suffix, false); + } + + /** + * 去除两边所有的指定字符串 + * + *
{@code
+	 * "aaa_STRIPPED_bbb", "a", "b"  -> "_STRIPPED_"
+	 * "aaa_STRIPPED_bbb", null, null  -> "aaa_STRIPPED_bbb"
+	 * "aaa_STRIPPED_bbb", "", ""  -> "aaa_STRIPPED_bbb"
+	 * "aaa_STRIPPED_bbb", "", "b"  -> "aaa_STRIPPED_"
+	 * "aaa_STRIPPED_bbb", null, "b"  -> "aaa_STRIPPED_"
+	 * "aaa_STRIPPED_bbb", "a", ""  -> "_STRIPPED_bbb"
+	 * "aaa_STRIPPED_bbb", "a", null  -> "_STRIPPED_bbb"
+	 *
+	 * // special test
+	 * "aaaaaabbb", "aaa", null  -> "bbb"
+	 * "aaaaaaabbb", "aa", null  -> "abbb"
+	 *
+	 * "aaaaaaaaa", "aaa", "aa"  -> ""
+	 * "a", "a", "a"  -> ""
+	 * }
+	 * 
+ * + * @param str 被处理的字符串 + * @param prefix 前缀 + * @param suffix 后缀 + * @param ignoreCase 是否忽略大小写 + * @return 处理后的字符串 + * @since 6.0.0 + */ + public static String stripAll(final CharSequence str, final CharSequence prefix, final CharSequence suffix, final boolean ignoreCase) { if (isEmpty(str)) { return toStringOrNull(str); } - final String prefixStr = toStringOrEmpty(prefix); - final String suffixStr = toStringOrEmpty(suffix); - - final String str2 = str.toString(); - int from = 0; - int to = str2.length(); - - if (!prefixStr.isEmpty()) { - while (str2.startsWith(prefixStr, from)) { - from += prefix.length(); - if (from == to) { - // "a", "a", "a" -> "" - return EMPTY; - } - } - } - if (!suffixStr.isEmpty()) { - while (str2.startsWith(suffixStr, to - suffixStr.length())) { - to -= suffixStr.length(); - if (from == to) { - // "a", "a", "a" -> "" - return EMPTY; - } else if (to < from) { - // pre去除后和suffix有重叠,如 ("aba", "ab", "ba") -> "a" - to += suffixStr.length(); - break; - } - } - } - - return str2.substring(from, to); + return new StrStripper(prefix, suffix, ignoreCase, true) + .apply(str); } // endregion diff --git a/hutool-core/src/main/java/org/dromara/hutool/core/text/StrStripper.java b/hutool-core/src/main/java/org/dromara/hutool/core/text/StrStripper.java new file mode 100644 index 000000000..13ab1a6ec --- /dev/null +++ b/hutool-core/src/main/java/org/dromara/hutool/core/text/StrStripper.java @@ -0,0 +1,186 @@ +/* + * Copyright (c) 2024 Hutool Team and hutool.cn + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.dromara.hutool.core.text; + +import java.io.Serializable; +import java.util.function.UnaryOperator; + +/** + * 字符串裁剪器,用于裁剪字符串前后缀
+ * 强调去除两边或某一边的指定字符串,如果一边不存在,另一边不影响去除 + * + * @author Looly + * @since 5.8.0 + */ +public class StrStripper implements UnaryOperator, Serializable { + private static final long serialVersionUID = 1L; + + private final CharSequence prefix; + private final CharSequence suffix; + private final boolean ignoreCase; + private final boolean stripAll; + + /** + * 构造 + * + * @param prefix 前缀,{@code null}忽略 + * @param suffix 后缀,{@code null}忽略 + * @param ignoreCase 是否忽略大小写 + * @param stripAll 是否去除全部 + */ + public StrStripper(final CharSequence prefix, final CharSequence suffix, final boolean ignoreCase, final boolean stripAll) { + this.prefix = prefix; + this.suffix = suffix; + this.ignoreCase = ignoreCase; + this.stripAll = stripAll; + } + + @Override + public String apply(final CharSequence charSequence) { + return this.stripAll ? stripAll(charSequence) : stripOnce(charSequence); + } + + /** + * 去除两边的指定字符串
+ * 两边字符如果存在,则去除,不存在不做处理 + *
{@code
+	 * "aaa_STRIPPED_bbb", "a", "b"  -> "aa_STRIPPED_bb"
+	 * "aaa_STRIPPED_bbb", null, null  -> "aaa_STRIPPED_bbb"
+	 * "aaa_STRIPPED_bbb", "", ""  -> "aaa_STRIPPED_bbb"
+	 * "aaa_STRIPPED_bbb", "", "b"  -> "aaa_STRIPPED_bb"
+	 * "aaa_STRIPPED_bbb", null, "b"  -> "aaa_STRIPPED_bb"
+	 * "aaa_STRIPPED_bbb", "a", ""  -> "aa_STRIPPED_bbb"
+	 * "aaa_STRIPPED_bbb", "a", null  -> "aa_STRIPPED_bbb"
+	 *
+	 * "a", "a", "a"  -> ""
+	 * }
+ * + * @param charSequence 被处理的字符串 + * @return 处理后的字符串 + */ + private String stripOnce(final CharSequence charSequence) { + if (StrUtil.isEmpty(charSequence)) { + return StrUtil.toStringOrNull(charSequence); + } + + final String str = charSequence.toString(); + int from = 0; + int to = str.length(); + + if (StrUtil.isNotEmpty(this.prefix) && startWith(str, this.prefix, 0)) { + from = this.prefix.length(); + if (from == to) { + // "a", "a", "a" -> "" + return StrUtil.EMPTY; + } + } + if (endWithSuffix(str)) { + to -= this.suffix.length(); + if (from == to) { + // "a", "a", "a" -> "" + return StrUtil.EMPTY; + } else if (to < from) { + // pre去除后和suffix有重叠,如 ("aba", "ab", "ba") -> "a" + to += this.suffix.length(); + } + } + + return str.substring(from, to); + } + + /** + * 去除两边所有的指定字符串 + * + *
{@code
+	 * "aaa_STRIPPED_bbb", "a", "b"  -> "_STRIPPED_"
+	 * "aaa_STRIPPED_bbb", null, null  -> "aaa_STRIPPED_bbb"
+	 * "aaa_STRIPPED_bbb", "", ""  -> "aaa_STRIPPED_bbb"
+	 * "aaa_STRIPPED_bbb", "", "b"  -> "aaa_STRIPPED_"
+	 * "aaa_STRIPPED_bbb", null, "b"  -> "aaa_STRIPPED_"
+	 * "aaa_STRIPPED_bbb", "a", ""  -> "_STRIPPED_bbb"
+	 * "aaa_STRIPPED_bbb", "a", null  -> "_STRIPPED_bbb"
+	 *
+	 * // special test
+	 * "aaaaaabbb", "aaa", null  -> "bbb"
+	 * "aaaaaaabbb", "aa", null  -> "abbb"
+	 *
+	 * "aaaaaaaaa", "aaa", "aa"  -> ""
+	 * "a", "a", "a"  -> ""
+	 * }
+ * + * @param charSequence 被处理的字符串 + * @return 处理后的字符串 + * @since 5.8.30 + */ + private String stripAll(final CharSequence charSequence) { + if (StrUtil.isEmpty(charSequence)) { + return StrUtil.toStringOrNull(charSequence); + } + + final String str = charSequence.toString(); + int from = 0; + int to = str.length(); + + if (StrUtil.isNotEmpty(this.prefix)) { + while (startWith(str, this.prefix, from)) { + from += this.prefix.length(); + if (from == to) { + // "a", "a", "a" -> "" + return StrUtil.EMPTY; + } + } + } + if (StrUtil.isNotEmpty(suffix)) { + final int suffixLength = this.suffix.length(); + while (startWith(str, suffix, to - suffixLength)) { + to -= suffixLength; + if (from == to) { + // "a", "a", "a" -> "" + return StrUtil.EMPTY; + } else if (to < from) { + // pre去除后和suffix有重叠,如 ("aba", "ab", "ba") -> "a" + to += suffixLength; + break; + } + } + } + + return str.substring(from, to); + } + + /** + * 判断是否以指定前缀开头 + * + * @param charSequence 被检查的字符串 + * @param from 开始位置 + * @return 是否以指定前缀开头 + */ + private boolean startWith(final CharSequence charSequence, final CharSequence strToCheck, final int from) { + return new StrRegionMatcher(this.ignoreCase, false, from) + .test(charSequence, strToCheck); + } + + /** + * 判断是否以指定后缀结尾 + * + * @param charSequence 被检查的字符串 + * @return 是否以指定后缀结尾 + */ + private boolean endWithSuffix(final CharSequence charSequence) { + return StrUtil.isNotEmpty(suffix) && StrUtil.endWith(charSequence, suffix, ignoreCase); + } +} diff --git a/hutool-core/src/test/java/org/dromara/hutool/core/text/StrStripperTest.java b/hutool-core/src/test/java/org/dromara/hutool/core/text/StrStripperTest.java new file mode 100644 index 000000000..42c848a63 --- /dev/null +++ b/hutool-core/src/test/java/org/dromara/hutool/core/text/StrStripperTest.java @@ -0,0 +1,59 @@ +/* + * Copyright (c) 2024 Hutool Team and hutool.cn + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.dromara.hutool.core.text; + +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +public class StrStripperTest { + + @Test + void stripAllPrefixTest() { + final StrStripper prefixStripper = new StrStripper("a", null, false, true); + assertEquals("_STRIPPED_bbb", prefixStripper.apply("aaa_STRIPPED_bbb")); + // 不忽略大小写则不替换 + assertEquals("AAA_STRIPPED_bbb", prefixStripper.apply("AAA_STRIPPED_bbb")); + } + + @Test + void stripAllSuffixTest() { + final StrStripper suffixStripper = new StrStripper(null, "b", false, true); + assertEquals("aaa_STRIPPED_", suffixStripper.apply("aaa_STRIPPED_bbb")); + // 不忽略大小写则不替换 + assertEquals("aaa_STRIPPED_BBB", suffixStripper.apply("aaa_STRIPPED_BBB")); + } + + @Test + void stripAllPrefixIgnoreCaseTest() { + final StrStripper prefixStripper = new StrStripper("A", null, true, true); + assertEquals("_STRIPPED_bbb", prefixStripper.apply("aaa_STRIPPED_bbb")); + assertEquals("_STRIPPED_bbb", prefixStripper.apply("Aaa_STRIPPED_bbb")); + assertEquals("_STRIPPED_bbb", prefixStripper.apply("AAA_STRIPPED_bbb")); + assertEquals("_STRIPPED_bbb", prefixStripper.apply("aaA_STRIPPED_bbb")); + } + + @Test + void stripAllSuffixIgnoreCaseTest() { + final StrStripper prefixStripper = new StrStripper(null, "B", true, true); + assertEquals("aaa_STRIPPED_", prefixStripper.apply("aaa_STRIPPED_bbb")); + assertEquals("aaa_STRIPPED_", prefixStripper.apply("aaa_STRIPPED_Bbb")); + assertEquals("aaa_STRIPPED_", prefixStripper.apply("aaa_STRIPPED_BBB")); + assertEquals("aaa_STRIPPED_", prefixStripper.apply("aaa_STRIPPED_bbB")); + } +} +