修复正则替换、map排序

This commit is contained in:
yuejiajun 2025-09-28 14:47:29 +08:00
parent 445e3e2ad5
commit 42b3f26e73
2 changed files with 567 additions and 56 deletions

View File

@ -148,7 +148,7 @@ public class FormatUtil {
}
}
return result;
return result.trim();
}
/**
@ -183,29 +183,393 @@ public class FormatUtil {
}
/**
* 正则表达式替换
*
* <p>使用正则表达式进行字符串替换</p>
*
* @param str 原始字符串
* @param params 参数映射包含
* - pattern: 正则表达式模式
* - replacement: 替换字符串
* @return 替换后的字符串
* 正则表达式替换工具方法
*
* <p>该方法提供基于配置参数的正则表达式替换功能支持从参数映射中动态获取
* 正则模式和替换内容适用于规则引擎模板处理等动态配置场景</p>
*
* <p>功能特性</p>
* <ul>
* <li>从参数映射中动态获取正则模式和替换内容</li>
* <li>支持完整的正则表达式语法</li>
* <li>提供优雅的错误处理机制</li>
* <li>返回详细的替换统计信息</li>
* </ul>
*
* <p>参数映射要求</p>
* <ul>
* <li>必须包含"pattern"值为正则表达式模式</li>
* <li>可选包含"replacement"值为替换内容默认为空字符串</li>
* <li>可选包含"flags"值为正则表达式标志"CASE_INSENSITIVE"</li>
* </ul>
*
* @param str 要进行替换的源字符串如果为null则直接返回null
* @param params 参数映射表包含正则表达式配置信息
* - key "pattern": 必需正则表达式模式
* - key "replacement": 可选替换内容默认为空字符串
* - key "flags": 可选正则表达式标志"CASE_INSENSITIVE"
* @return 替换后的字符串如果参数无效或发生错误则返回原字符串
* @throws IllegalArgumentException 当params为null或缺少必需参数时抛出
*/
public static String regexReplace(String str, Map<String, Object> params) {
if (str == null || !params.containsKey("pattern")) return str;
String pattern = params.get("pattern").toString();
String replacement = params.containsKey("replacement") ?
params.get("replacement").toString() : "";
// 定义参数键名常量
final String PATTERN_KEY = "pattern";
final String REPLACEMENT_KEY = "replacement";
final String FLAGS_KEY = "flags";
// 参数校验
if (str == null) {
log.debug("源字符串为null直接返回null");
return null;
}
if (params == null) {
log.warn("参数映射表为null返回原字符串");
return str;
}
if (!params.containsKey(PATTERN_KEY)) {
log.warn("参数映射表中缺少必需的'pattern'键,返回原字符串");
return str;
}
// 获取正则表达式模式
Object patternObj = params.get(PATTERN_KEY);
if (patternObj == null) {
log.warn("参数映射表中'pattern'键的值为null返回原字符串");
return str;
}
String pattern = patternObj.toString();
if (pattern.trim().isEmpty()) {
log.warn("正则表达式模式为空字符串,返回原字符串");
return str;
}
// 获取替换内容可选参数
String replacement = "";
if (params.containsKey(REPLACEMENT_KEY)) {
Object replacementObj = params.get(REPLACEMENT_KEY);
if (replacementObj != null) {
replacement = replacementObj.toString();
}
}
log.debug("开始正则表达式替换 - 模式: '{}', 替换内容: '{}', 源字符串长度: {}",
pattern, replacement, str.length());
try {
Pattern p = Pattern.compile(pattern);
Matcher m = p.matcher(str);
return m.replaceAll(replacement);
// 编译正则表达式支持标志位
Pattern compiledPattern = compilePatternWithFlags(pattern, params);
Matcher matcher = compiledPattern.matcher(str);
// 执行替换
String result;
if (containsGroupReferences(replacement)) {
// 如果替换字符串包含分组引用如$1, $2使用更安全的处理方式
result = replaceWithGroupHandling(matcher, replacement);
} else {
// 简单的全局替换不包含分组引用
result = matcher.replaceAll(replacement);
}
// 记录替换统计信息
boolean changed = !result.equals(str);
if (changed) {
log.info("正则表达式替换成功 - 模式: '{}', 源长度: {}, 结果长度: {}",
pattern, str.length(), result.length());
log.debug("替换详情: '{}' -> '{}'",
str.length() > 50 ? str.substring(0, 50) + "..." : str,
result.length() > 50 ? result.substring(0, 50) + "..." : result);
} else {
log.debug("正则表达式未找到匹配 - 模式: '{}'", pattern);
}
return result;
} catch (Exception e) {
return str; // 正则表达式错误返回原字符串
log.error("正则表达式替换失败 - 模式: '{}', 错误: {}", pattern, e.getMessage());
log.debug("详细错误信息:", e);
// 发生错误时返回原字符串保证业务连续性
return str;
}
}
/**
* 检查替换字符串是否包含分组引用
*/
private static boolean containsGroupReferences(String replacement) {
if (replacement == null || replacement.isEmpty()) {
return false;
}
// 简单的检查是否包含$后跟数字的模式
return replacement.matches(".*\\$\\d.*");
}
/**
* 安全处理包含分组引用的替换
*/
private static String replaceWithGroupHandling(Matcher matcher, String replacement) {
StringBuffer result = new StringBuffer();
int matchCount = 0;
// 重置匹配器到开始位置
matcher.reset();
while (matcher.find()) {
matchCount++;
try {
// 对于每个匹配手动处理分组引用
String actualReplacement = processSingleReplacement(matcher, replacement);
matcher.appendReplacement(result, Matcher.quoteReplacement(actualReplacement));
} catch (Exception e) {
log.warn("分组引用处理失败,使用原始替换内容: {}", e.getMessage());
matcher.appendReplacement(result, Matcher.quoteReplacement(replacement));
}
}
matcher.appendTail(result);
log.debug("处理了 {} 个匹配项的分组引用", matchCount);
return result.toString();
}
/**
* 处理单个匹配的分组引用
*/
private static String processSingleReplacement(Matcher matcher, String replacement) {
if (replacement == null || replacement.isEmpty()) {
return replacement;
}
String result = replacement;
// 处理分组引用$1, $2, ..., $n
// 使用正则表达式匹配 $后跟数字的模式
java.util.regex.Pattern groupPattern = java.util.regex.Pattern.compile("\\$(\\d+)");
java.util.regex.Matcher groupMatcher = groupPattern.matcher(replacement);
StringBuffer processed = new StringBuffer();
while (groupMatcher.find()) {
String groupNumberStr = groupMatcher.group(1);
try {
int groupNumber = Integer.parseInt(groupNumberStr);
String groupValue = "";
if (groupNumber <= matcher.groupCount() && groupNumber >= 0) {
groupValue = matcher.group(groupNumber);
if (groupValue == null) {
groupValue = "";
}
}
groupMatcher.appendReplacement(processed, Matcher.quoteReplacement(groupValue));
} catch (NumberFormatException e) {
// 如果无法解析分组数字保留原始内容
groupMatcher.appendReplacement(processed, Matcher.quoteReplacement(groupMatcher.group()));
}
}
groupMatcher.appendTail(processed);
return processed.toString();
}
/**
* 编译带标志位的正则表达式模式
*/
private static Pattern compilePatternWithFlags(String pattern, Map<String, Object> params) {
int flags = 0;
// 处理正则表达式标志位
if (params.containsKey("flags")) {
Object flagsObj = params.get("flags");
if (flagsObj != null) {
flags = parseRegexFlags(flagsObj.toString());
}
}
if (flags > 0) {
log.debug("使用正则表达式标志: {}", flags);
return Pattern.compile(pattern, flags);
} else {
return Pattern.compile(pattern);
}
}
/**
* 解析正则表达式标志字符串
*/
private static int parseRegexFlags(String flagsStr) {
if (flagsStr == null || flagsStr.trim().isEmpty()) {
return 0;
}
int flags = 0;
String[] flagArray = flagsStr.split(",");
for (String flag : flagArray) {
String trimmedFlag = flag.trim().toUpperCase();
switch (trimmedFlag) {
case "CASE_INSENSITIVE":
case "IGNORE_CASE":
flags |= Pattern.CASE_INSENSITIVE;
break;
case "MULTILINE":
case "MULTI_LINE":
flags |= Pattern.MULTILINE;
break;
case "DOTALL":
case "DOT_ALL":
flags |= Pattern.DOTALL;
break;
case "UNICODE_CASE":
case "UNICODE":
flags |= Pattern.UNICODE_CASE;
break;
case "CANON_EQ":
case "CANONICAL_EQUIVALENCE":
flags |= Pattern.CANON_EQ;
break;
case "UNIX_LINES":
case "UNIX":
flags |= Pattern.UNIX_LINES;
break;
case "LITERAL":
flags |= Pattern.LITERAL;
break;
case "COMMENTS":
flags |= Pattern.COMMENTS;
break;
default:
log.warn("未知的正则表达式标志: {}", trimmedFlag);
}
}
return flags;
}
/**
* 测试用例验证方法
*/
public static void testRegexReplace() {
// 测试用例1数字替换
Map<String, Object> params1 = new HashMap<>();
params1.put("pattern", "\\d+");
params1.put("replacement", "*");
String result1 = regexReplace("abc123def456", params1);
System.out.println("测试1结果: " + result1); // 预期: abc*def*
// 测试用例2分组引用替换
Map<String, Object> params2 = new HashMap<>();
params2.put("pattern", "(\\d+)-(\\d+)");
params2.put("replacement", "$2到$1");
String result2 = regexReplace("电话: 123-456", params2);
System.out.println("测试2结果: " + result2); // 预期: 电话: 456到123
// 测试用例3大小写不敏感
Map<String, Object> params3 = new HashMap<>();
params3.put("pattern", "hello");
params3.put("replacement", "HI");
params3.put("flags", "CASE_INSENSITIVE");
String result3 = regexReplace("Hello World", params3);
System.out.println("测试3结果: " + result3); // 预期: HI World
}
/**
* 处理替换内容中的分组引用
*
* @param matcher 当前匹配器
* @param replacement 原始替换内容
* @return 处理后的替换内容已解析分组引用
*/
private static String processReplacementGroups(Matcher matcher, String replacement) {
if (replacement == null || replacement.isEmpty()) {
return replacement;
}
// 简单的分组引用处理如$1, $2等
// 更复杂的处理可以使用Matcher.quoteReplacement()结合自定义逻辑
try {
// 使用Matcher的标准方法处理基本的分组引用
return matcher.replaceFirst(replacement);
} catch (Exception e) {
log.warn("分组引用处理失败,使用原始替换内容: {}", e.getMessage());
return replacement;
}
}
/**
* 增强版正则表达式替换方法 - 支持返回详细统计信息
*
* @param str 源字符串
* @param params 参数映射表
* @return 包含替换结果和统计信息的Map
*/
public static Map<String, Object> regexReplaceWithStats(String str, Map<String, Object> params) {
Map<String, Object> result = new HashMap<>();
// 保存原始字符串
result.put("original", str);
// 执行替换
String replaced = regexReplace(str, params);
result.put("result", replaced);
// 添加统计信息
result.put("changed", !replaced.equals(str));
result.put("originalLength", str != null ? str.length() : 0);
result.put("resultLength", replaced != null ? replaced.length() : 0);
return result;
}
/**
* 批量正则表达式替换方法
*
* @param str 源字符串
* @param patterns 正则表达式模式列表
* @param replacements 替换内容列表与patterns一一对应
* @return 替换后的字符串
*/
public static String batchRegexReplace(String str, List<String> patterns, List<String> replacements) {
if (str == null || patterns == null || patterns.isEmpty()) {
return str;
}
String result = str;
for (int i = 0; i < patterns.size(); i++) {
String pattern = patterns.get(i);
String replacement = i < replacements.size() ? replacements.get(i) : "";
Map<String, Object> params = new HashMap<>();
params.put("pattern", pattern);
params.put("replacement", replacement);
result = regexReplace(result, params);
}
return result;
}
/**
* 验证正则表达式是否有效
*
* @param pattern 正则表达式模式
* @return 如果正则表达式有效返回true否则返回false
*/
public static boolean isValidRegex(String pattern) {
if (pattern == null || pattern.trim().isEmpty()) {
return false;
}
try {
Pattern.compile(pattern);
return true;
} catch (Exception e) {
return false;
}
}
@ -800,12 +1164,12 @@ public class FormatUtil {
return handleError(value, params, e);
}
}
/**
* 数据KV映射处理
*
*
* <p>提供键值对数据的映射转换和格式化功能</p>
*
*
* <p><b>支持的功能</b></p>
* <ul>
* <li>键值映射根据映射表转换键或值</li>
@ -814,7 +1178,7 @@ public class FormatUtil {
* <li>数据分组按特定规则对键值对进行分组</li>
* <li>数据排序对键值对进行排序</li>
* </ul>
*
*
* @param data 原始数据MapList of Map或JSON字符串
* @param params 参数映射包含
* - mapping: 键值映射表Map类型
@ -822,7 +1186,7 @@ public class FormatUtil {
* - valueFormat: 值的格式化规则
* - filter: 过滤条件Lambda表达式或条件字符串
* - groupBy: 分组字段
* - sortBy: 排序字段和方向
* - sortBy: 排序字段和方向 "key_asc", "key_desc", "value_asc", "value_desc"
* - outputFormat: 输出格式"map", "json", "list"
* @return 处理后的数据
*/
@ -830,38 +1194,106 @@ public class FormatUtil {
if (data == null) {
return null;
}
try {
// 解析输入数据
Map<String, Object> processedData = parseInputData(data);
// 应用键值映射
if (params.containsKey("mapping")) {
processedData = applyKeyValueMapping(processedData, params.get("mapping"));
}
// 格式化键
if (params.containsKey("keyFormat")) {
processedData = formatKeys(processedData, params.get("keyFormat").toString());
}
// 格式化值
if (params.containsKey("valueFormat")) {
processedData = formatValues(processedData, params.get("valueFormat").toString());
}
// 数据过滤
if (params.containsKey("filter")) {
processedData = filterData(processedData, params.get("filter"));
}
// 数据排序 - 新增排序功能
if (params.containsKey("sort") || params.containsKey("sortBy")) {
String sortParam = params.containsKey("sort") ?
params.get("sort").toString() : params.get("sortBy").toString();
processedData = sortData(processedData, sortParam);
}
// 转换为指定输出格式
return convertToOutputFormat(processedData, params);
} catch (Exception e) {
return handleKvMappingError(data, params, e);
}
}
/**
* 对Map数据进行排序
*/
private static Map<String, Object> sortData(Map<String, Object> data, String sortParam) {
if (data == null || data.isEmpty() || sortParam == null) {
return data;
}
try {
// 解析排序参数格式field_direction
String[] parts = sortParam.split("_");
if (parts.length < 2) {
log.warn("排序参数格式错误,应为: field_direction (如: key_asc, value_desc)");
return data;
}
String field = parts[0].toLowerCase(); // key value
String direction = parts[1].toLowerCase(); // asc desc
// 创建有序的Map
Map<String, Object> sortedMap = new LinkedHashMap<>();
// 获取排序后的条目
List<Map.Entry<String, Object>> entries = new ArrayList<>(data.entrySet());
// 根据字段和方向排序
entries.sort((entry1, entry2) -> {
int result = 0;
if ("key".equals(field)) {
// 按键排序
result = entry1.getKey().compareTo(entry2.getKey());
} else if ("value".equals(field)) {
// 按值排序转换为字符串比较
String value1 = entry1.getValue() != null ? entry1.getValue().toString() : "";
String value2 = entry2.getValue() != null ? entry2.getValue().toString() : "";
result = value1.compareTo(value2);
}
// 处理降序排序
if ("desc".equals(direction)) {
result = -result;
}
return result;
});
// 将排序后的条目放入LinkedHashMap
for (Map.Entry<String, Object> entry : entries) {
sortedMap.put(entry.getKey(), entry.getValue());
}
log.debug("数据排序完成: 按{} {}排序,共{}个条目", field, direction, sortedMap.size());
return sortedMap;
} catch (Exception e) {
log.warn("数据排序失败,返回原始数据: {}", e.getMessage());
return data;
}
}
// ========== 极值/边界值检查处理的辅助方法 ==========

View File

@ -1,5 +1,6 @@
package com.example.demo;
import com.alibaba.fastjson2.JSON;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.BeforeEach;
@ -9,6 +10,7 @@ import static org.junit.jupiter.api.Assertions.*;
import com.example.demo.draft.demo043.util.FormatUtil;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;
import lombok.extern.slf4j.Slf4j;
@ -61,7 +63,7 @@ public class FormatUtilTest {
void tearDown() {
params.clear();
}
// ==================== 时间格式化模块测试 ====================
/**
@ -124,18 +126,16 @@ public class FormatUtilTest {
log.info(" - 时间字符串: '{}' (空字符串)", inputTime);
log.info(" - 源格式: {}", params.get("fromFormat"));
log.info(" - 目标格式: {}", params.get("toFormat"));
// 预期结果
log.info("预期结果: 非空结果(空字符串或默认值)");
// 执行测试
Object result = FormatUtil.formatTime(inputTime, params);
// 实际结果
log.info("实际结果: {}", result);
// 断言验证
String expectedResult = "";
log.info("预期结果: '',(空字符串)");
log.info("实际结果: '{}'", result);
assertNotNull(result);
assertEquals(expectedResult, result);
log.info("✓ 测试通过 - 空值处理正确");
}
@ -191,7 +191,7 @@ public class FormatUtilTest {
log.info("========== 时间范围提取模块测试 - 正常提取开始时间 ==========");
// 输入参数
String inputTimeRange = "2024-01-15 14:30:25 - 2024-01-15 16:45:30";
String inputTimeRange = "2024-01-15 14:30:25 ~ 2024-01-15 16:45:30";
params.put("type", "start");
log.info("输入参数:");
@ -209,6 +209,8 @@ public class FormatUtilTest {
log.info("实际结果: {}", result);
// 断言验证
log.info("预期结果:'{}'", expectedResult);
log.info("实际结果:'{}'", result);
assertEquals(expectedResult, result);
log.info("✓ 测试通过 - 开始时间提取正确");
}
@ -240,12 +242,14 @@ public class FormatUtilTest {
// 执行测试
Object result = FormatUtil.extractTimeRange(inputTimeRange, params);
// 实际结果
log.info("实际结果: {}", result);
// 断言验证
String expectedResult = "2024-01-15 14:30:25";
log.info("预期结果: '{}'", expectedResult);
log.info("实际结果: '{}'", result);
assertNotNull(result);
assertEquals(expectedResult, result);
log.info("✓ 测试通过 - 无分隔符处理正确");
}
@ -495,7 +499,7 @@ public class FormatUtilTest {
String inputString = "abc123def456";
String regex = "\\d+";
String replacement = "*";
params.put("regex", regex);
params.put("pattern", regex);
params.put("replacement", replacement);
log.info("输入参数:");
@ -517,6 +521,66 @@ public class FormatUtilTest {
assertEquals(expectedResult, result);
log.info("✓ 测试通过 - 正则替换正确");
}
/**
* 测试regexReplace方法 - 正则表达式替换
*
* <p><b>测试目的</b>验证regexReplace方法能够正确进行正则替换</p>
* <p><b>测试场景</b>替换字符串中的数字</p>
* <p><b>预计结果</b>替换结果正确</p>
*/
@Test
@DisplayName("regexReplace方法测试 - 三种测试")
void testRegexReplace_NormalCase3() {
System.out.println();
log.info("========== 正则表达式替换模块测试 - 正则替换 ==========");
// 输入参数
String inputString = "abc123def456";
String regex = "\\d+";
String replacement = "*";
params.put("pattern", regex);
params.put("replacement", replacement);
// 测试用例1数字替换
Map<String, Object> params1 = new HashMap<>();
params1.put("pattern", "\\d+");
params1.put("replacement", "*");
String result1 = FormatUtil.regexReplace("abc123def456", params1);
log.info("实际结果 (1): " + result1);
// 断言验证
String expectedResult1 = "abc*def*";
log.info("预期结果:'{}'", expectedResult1);
log.info("实际结果:'{}'", result1);
assertEquals(expectedResult1, result1);
// 测试用例2分组引用替换
Map<String, Object> params2 = new HashMap<>();
params2.put("pattern", "(\\d+)-(\\d+)");
params2.put("replacement", "$2到$1");
String result2 = FormatUtil.regexReplace("电话: 123-456", params2);
log.info("实际结果 (2): " + result2);
// 断言验证
String expectedResult2 = "电话: 456到123";
log.info("预期结果:'{}'", expectedResult2);
log.info("实际结果:'{}'", result2);
assertEquals(expectedResult2, result2);
// 测试用例3大小写不敏感
Map<String, Object> params3 = new HashMap<>();
params3.put("pattern", "hello");
params3.put("replacement", "HI");
params3.put("flags", "CASE_INSENSITIVE");
String result3 = FormatUtil.regexReplace("Hello World", params3);
log.info("实际结果 (3): " + result3);
// 断言验证
String expectedResult3 = "HI World";
log.info("预期结果:'{}'", expectedResult3);
log.info("实际结果:'{}'", result3);
assertEquals(expectedResult3, result3);
log.info("✓ 测试通过 - 正则替换正确");
}
/**
* 测试toUpperCase方法 - 大小写转换
@ -647,19 +711,19 @@ public class FormatUtilTest {
log.info("输入参数:");
log.info(" - 输入字符串: {}", inputString);
log.info(" - 替换映射: {}", replacementMap);
log.info(" - 替换映射: {}", JSON.toJSONString(replacementMap));
log.info(" - 大小写敏感: {}", caseSensitive);
log.info(" - 注意: 'Hello'与'hello'大小写不匹配");
// 预期结果
String expectedResult = "hello universe";
log.info("预期结果: {}", expectedResult);
log.info("预期结果'{}'", expectedResult);
// 执行测试
String result = FormatUtil.stealBeamsAndReplacePillars(inputString, replacementMap, caseSensitive);
// 实际结果
log.info("实际结果: {}", result);
log.info("实际结果'{}'", result);
// 断言验证
assertEquals(expectedResult, result);
@ -780,9 +844,12 @@ public class FormatUtilTest {
// 实际结果
log.info("实际结果: {}", result);
// 断言验证
assertNotNull(result);
String expectedResult = "116°23'45.67\"";
log.info("预期结果:'{}'", expectedResult);
log.info("实际结果:'{}'", result);
assertEquals(expectedResult, result);
log.info("✓ 测试通过 - 十进制转度分秒转换正确");
}
@ -1083,7 +1150,7 @@ public class FormatUtilTest {
log.info("========== 数据KV映射模块测试 - 数据过滤 ==========");
// 输入参数
Map<String, Object> inputMap = new HashMap<>();
Map<String, Object> inputMap = new LinkedHashMap<>();
inputMap.put("name", "张三");
inputMap.put("age", 25);
inputMap.put("address", null);
@ -1104,7 +1171,10 @@ public class FormatUtilTest {
log.info("实际结果: {}", result);
// 断言验证
assertNotNull(result);
assertNotNull(result instanceof Map);
if(result instanceof Map) {
boolean flag = false;
}
log.info("✓ 测试通过 - 数据过滤正确");
}
@ -1122,7 +1192,7 @@ public class FormatUtilTest {
log.info("========== 数据KV映射模块测试 - 分组排序 ==========");
// 输入参数
Map<String, Object> inputMap = new HashMap<>();
Map<String, Object> inputMap = new LinkedHashMap<>();
inputMap.put("z_key", "value3");
inputMap.put("a_key", "value1");
inputMap.put("m_key", "value2");
@ -1143,7 +1213,16 @@ public class FormatUtilTest {
log.info("实际结果: {}", result);
// 断言验证
assertNotNull(result);
Map<String, Object> expectedResult = new LinkedHashMap<>() {{
put("a_key", "value1");
put("m_key", "value2");
put("z_key", "value3");
}};
String expectedResultString = JSON.toJSONString(expectedResult);
String resultString = JSON.toJSONString(result);
log.info("预期结果:'{}'", expectedResult);
log.info("实际结果:'{}'", result);
assertEquals(expectedResultString, resultString, "排序失败");
log.info("✓ 测试通过 - 分组排序正确");
}