feat(用例管理): 新增xmind导入
This commit is contained in:
parent
192a82fff5
commit
8307bf3020
|
@ -155,6 +155,18 @@
|
||||||
</exclusions>
|
</exclusions>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
|
<!-- jsonNode xml 格式转换 -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.fasterxml.jackson.dataformat</groupId>
|
||||||
|
<artifactId>jackson-dataformat-xml</artifactId>
|
||||||
|
<version>${jackson-dataformat-xml.version}</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.fasterxml.jackson.core</groupId>
|
||||||
|
<artifactId>jackson-core</artifactId>
|
||||||
|
<version>${jackson-core.version}</version> <!-- 替换为你的版本 -->
|
||||||
|
</dependency>
|
||||||
|
|
||||||
<!-- minio -->
|
<!-- minio -->
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>io.minio</groupId>
|
<groupId>io.minio</groupId>
|
||||||
|
|
|
@ -3,7 +3,6 @@ package io.metersphere.sdk.util;
|
||||||
|
|
||||||
import com.fasterxml.jackson.annotation.JsonAutoDetect;
|
import com.fasterxml.jackson.annotation.JsonAutoDetect;
|
||||||
import com.fasterxml.jackson.annotation.PropertyAccessor;
|
import com.fasterxml.jackson.annotation.PropertyAccessor;
|
||||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
|
||||||
import com.fasterxml.jackson.core.StreamReadConstraints;
|
import com.fasterxml.jackson.core.StreamReadConstraints;
|
||||||
import com.fasterxml.jackson.core.json.JsonReadFeature;
|
import com.fasterxml.jackson.core.json.JsonReadFeature;
|
||||||
import com.fasterxml.jackson.databind.DeserializationFeature;
|
import com.fasterxml.jackson.databind.DeserializationFeature;
|
||||||
|
@ -11,10 +10,13 @@ import com.fasterxml.jackson.databind.JsonNode;
|
||||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
import com.fasterxml.jackson.databind.SerializationFeature;
|
import com.fasterxml.jackson.databind.SerializationFeature;
|
||||||
import com.fasterxml.jackson.databind.json.JsonMapper;
|
import com.fasterxml.jackson.databind.json.JsonMapper;
|
||||||
import com.fasterxml.jackson.databind.node.ObjectNode;
|
import com.fasterxml.jackson.dataformat.xml.XmlMapper;
|
||||||
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
|
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
|
||||||
import org.apache.commons.lang3.StringUtils;
|
import org.apache.commons.lang3.StringUtils;
|
||||||
import org.dom4j.*;
|
import org.dom4j.Document;
|
||||||
|
import org.dom4j.DocumentException;
|
||||||
|
import org.dom4j.Element;
|
||||||
|
import org.dom4j.Node;
|
||||||
import org.dom4j.io.OutputFormat;
|
import org.dom4j.io.OutputFormat;
|
||||||
import org.dom4j.io.SAXReader;
|
import org.dom4j.io.SAXReader;
|
||||||
import org.dom4j.io.XMLWriter;
|
import org.dom4j.io.XMLWriter;
|
||||||
|
@ -206,90 +208,17 @@ public class XMLUtils {
|
||||||
return result.toString();
|
return result.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static String delXmlHeader(String xml) {
|
|
||||||
int begin = xml.indexOf("?>");
|
|
||||||
if (begin != -1) {
|
|
||||||
if (begin + 2 >= xml.length()) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
xml = xml.substring(begin + 2);
|
|
||||||
} // <?xml version="1.0" encoding="utf-8"?> 若存在,则去除
|
|
||||||
String rgex = ">";
|
|
||||||
Pattern pattern = Pattern.compile(rgex);
|
|
||||||
Matcher m = pattern.matcher(xml);
|
|
||||||
xml = m.replaceAll("> ");
|
|
||||||
rgex = "\\s*</";
|
|
||||||
pattern = Pattern.compile(rgex);
|
|
||||||
m = pattern.matcher(xml);
|
|
||||||
xml = m.replaceAll(" </");
|
|
||||||
return xml;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 传入完整的 xml 文本,转换成 json 对象
|
// 传入完整的 xml 文本,转换成 json 对象
|
||||||
public static JsonNode xmlConvertJson(String xml) {
|
public static JsonNode xmlConvertJson(String xml) {
|
||||||
if (StringUtils.isBlank(xml)) return null;
|
if (StringUtils.isBlank(xml)) return null;
|
||||||
xml = delXmlHeader(xml);
|
// 创建一个XmlMapper对象
|
||||||
if (xml == null) return null;
|
ObjectMapper xmlMapper = new XmlMapper();
|
||||||
|
// 将XML字符串转换为JsonNode对象
|
||||||
try {
|
try {
|
||||||
if (stringToDocument(xml) == null) {
|
return xmlMapper.readTree(xml.getBytes());
|
||||||
LogUtils.error("xml内容转换失败!");
|
} catch (IOException e) {
|
||||||
return null;
|
|
||||||
}
|
|
||||||
} catch (Exception e) {
|
|
||||||
throw new RuntimeException(e);
|
throw new RuntimeException(e);
|
||||||
}
|
}
|
||||||
Element node = null;
|
|
||||||
try {
|
|
||||||
node = stringToDocument(xml).getRootElement();
|
|
||||||
} catch (Exception e) {
|
|
||||||
throw new RuntimeException(e);
|
|
||||||
}
|
|
||||||
return getJsonObjectByDC(node);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static JsonNode getJsonObjectByDC(Element node) {
|
|
||||||
ObjectNode result = objectMapper.createObjectNode();;
|
|
||||||
List<Element> listElement = node.elements();// 所有一级子节点的list
|
|
||||||
if (!listElement.isEmpty()) {
|
|
||||||
List<JsonNode> list = new LinkedList<>();
|
|
||||||
for (Element e : listElement) {// 遍历所有一级子节点
|
|
||||||
JsonNode jsonObject = getJsonObjectByDC(e);
|
|
||||||
//加xml标签上的属性 eg: <field length="2" scale="0" type="string">RB</field>
|
|
||||||
//这里添加 length scale type
|
|
||||||
if (!e.attributes().isEmpty()) {
|
|
||||||
ObjectNode attributeJson = objectMapper.createObjectNode();;
|
|
||||||
for (Attribute attribute : e.attributes()) {
|
|
||||||
try {
|
|
||||||
attributeJson.putIfAbsent(attribute.getName(), objectMapper.readTree(attribute.getValue()));
|
|
||||||
} catch (JsonProcessingException ex) {
|
|
||||||
throw new RuntimeException(ex);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
ObjectNode jsonObjectNode = (ObjectNode) jsonObject;
|
|
||||||
jsonObjectNode.putIfAbsent("attribute", attributeJson);
|
|
||||||
}
|
|
||||||
list.add(jsonObject);
|
|
||||||
}
|
|
||||||
if (list.size() == 1) {
|
|
||||||
result.putIfAbsent(node.getName(), list.get(0));
|
|
||||||
} else {
|
|
||||||
try {
|
|
||||||
String s = objectMapper.writeValueAsString(list);
|
|
||||||
result.putIfAbsent(node.getName(), objectMapper.readTree(s));
|
|
||||||
} catch (JsonProcessingException e) {
|
|
||||||
throw new RuntimeException(e);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (!StringUtils.isAllBlank(node.getName(), node.getText())) {
|
|
||||||
try {
|
|
||||||
result.putIfAbsent(node.getName(), objectMapper.readTree(node.getText()));
|
|
||||||
} catch (JsonProcessingException e) {
|
|
||||||
throw new RuntimeException(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -41,6 +41,32 @@
|
||||||
<artifactId>metersphere-provider</artifactId>
|
<artifactId>metersphere-provider</artifactId>
|
||||||
<version>${revision}</version>
|
<version>${revision}</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>javax.xml.bind</groupId>
|
||||||
|
<artifactId>jaxb-api</artifactId>
|
||||||
|
<version>${jaxb-api.revision}</version>
|
||||||
|
<scope>provided</scope><!-- only required for compilation with Java 11 or higher, but only used by Java 7 or below -->
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.sun.xml.bind</groupId>
|
||||||
|
<artifactId>jaxb-impl</artifactId>
|
||||||
|
<version>${jaxb-impl.revision}</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.sun.xml.bind</groupId>
|
||||||
|
<artifactId>jaxb-core</artifactId>
|
||||||
|
<version>${jaxb-core.revision}</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>javax.activation</groupId>
|
||||||
|
<artifactId>activation</artifactId>
|
||||||
|
<version>${activation.revision}</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>jaxen</groupId>
|
||||||
|
<artifactId>jaxen</artifactId>
|
||||||
|
<version>${jaxen.revision}</version>
|
||||||
|
</dependency>
|
||||||
</dependencies>
|
</dependencies>
|
||||||
|
|
||||||
<build>
|
<build>
|
||||||
|
|
|
@ -230,6 +230,15 @@ public class FunctionalCaseController {
|
||||||
return functionalCaseFileService.preCheckExcel(request, file);
|
return functionalCaseFileService.preCheckExcel(request, file);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@PostMapping("/pre-check/xmind")
|
||||||
|
@Operation(summary = "用例管理-功能用例-xmind导入检查")
|
||||||
|
@RequiresPermissions(PermissionConstants.FUNCTIONAL_CASE_READ_IMPORT)
|
||||||
|
@CheckOwner(resourceId = "#request.getProjectId()", resourceType = "project")
|
||||||
|
public FunctionalCaseImportResponse preCheckXMind(@RequestPart("request") FunctionalCaseImportRequest request, @RequestPart(value = "file", required = false) MultipartFile file) {
|
||||||
|
SessionUser user = SessionUtils.getUser();
|
||||||
|
return functionalCaseFileService.preCheckXMind(request,user, file);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
@PostMapping("/import/excel")
|
@PostMapping("/import/excel")
|
||||||
@Operation(summary = "用例管理-功能用例-excel导入")
|
@Operation(summary = "用例管理-功能用例-excel导入")
|
||||||
|
@ -240,6 +249,15 @@ public class FunctionalCaseController {
|
||||||
return functionalCaseFileService.importExcel(request, user, file);
|
return functionalCaseFileService.importExcel(request, user, file);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@PostMapping("/import/xmind")
|
||||||
|
@Operation(summary = "用例管理-功能用例-xmind导入")
|
||||||
|
@RequiresPermissions(PermissionConstants.FUNCTIONAL_CASE_READ_IMPORT)
|
||||||
|
@CheckOwner(resourceId = "#request.getProjectId()", resourceType = "project")
|
||||||
|
public FunctionalCaseImportResponse importXMind(@RequestPart("request") FunctionalCaseImportRequest request, @RequestPart(value = "file", required = false) MultipartFile file) {
|
||||||
|
SessionUser user = SessionUtils.getUser();
|
||||||
|
return functionalCaseFileService.importXMind(request, user, file);
|
||||||
|
}
|
||||||
|
|
||||||
@PostMapping("/operation-history")
|
@PostMapping("/operation-history")
|
||||||
@Operation(summary = "用例管理-功能用例-变更历史")
|
@Operation(summary = "用例管理-功能用例-变更历史")
|
||||||
@RequiresPermissions(PermissionConstants.FUNCTIONAL_CASE_READ)
|
@RequiresPermissions(PermissionConstants.FUNCTIONAL_CASE_READ)
|
||||||
|
|
|
@ -27,6 +27,7 @@ import io.metersphere.functional.mapper.ExtFunctionalCaseCommentMapper;
|
||||||
import io.metersphere.functional.request.FunctionalCaseExportRequest;
|
import io.metersphere.functional.request.FunctionalCaseExportRequest;
|
||||||
import io.metersphere.functional.request.FunctionalCaseImportRequest;
|
import io.metersphere.functional.request.FunctionalCaseImportRequest;
|
||||||
import io.metersphere.functional.socket.ExportWebSocketHandler;
|
import io.metersphere.functional.socket.ExportWebSocketHandler;
|
||||||
|
import io.metersphere.functional.xmind.parser.XMindCaseParser;
|
||||||
import io.metersphere.plan.domain.TestPlanCaseExecuteHistory;
|
import io.metersphere.plan.domain.TestPlanCaseExecuteHistory;
|
||||||
import io.metersphere.project.domain.Project;
|
import io.metersphere.project.domain.Project;
|
||||||
import io.metersphere.project.mapper.ExtBaseProjectVersionMapper;
|
import io.metersphere.project.mapper.ExtBaseProjectVersionMapper;
|
||||||
|
@ -44,6 +45,7 @@ import io.metersphere.system.dto.sdk.BaseTreeNode;
|
||||||
import io.metersphere.system.dto.sdk.SessionUser;
|
import io.metersphere.system.dto.sdk.SessionUser;
|
||||||
import io.metersphere.system.dto.sdk.TemplateCustomFieldDTO;
|
import io.metersphere.system.dto.sdk.TemplateCustomFieldDTO;
|
||||||
import io.metersphere.system.dto.sdk.TemplateDTO;
|
import io.metersphere.system.dto.sdk.TemplateDTO;
|
||||||
|
import io.metersphere.system.excel.domain.ExcelErrData;
|
||||||
import io.metersphere.system.excel.utils.EasyExcelExporter;
|
import io.metersphere.system.excel.utils.EasyExcelExporter;
|
||||||
import io.metersphere.system.manager.ExportTaskManager;
|
import io.metersphere.system.manager.ExportTaskManager;
|
||||||
import io.metersphere.system.mapper.SystemParameterMapper;
|
import io.metersphere.system.mapper.SystemParameterMapper;
|
||||||
|
@ -288,8 +290,40 @@ public class FunctionalCaseFileService {
|
||||||
|
|
||||||
public List<TemplateCustomFieldDTO> getCustomFields(String projectId) {
|
public List<TemplateCustomFieldDTO> getCustomFields(String projectId) {
|
||||||
TemplateDTO defaultTemplateDTO = projectTemplateService.getDefaultTemplateDTO(projectId, TemplateScene.FUNCTIONAL.name());
|
TemplateDTO defaultTemplateDTO = projectTemplateService.getDefaultTemplateDTO(projectId, TemplateScene.FUNCTIONAL.name());
|
||||||
List<TemplateCustomFieldDTO> customFields = Optional.ofNullable(defaultTemplateDTO.getCustomFields()).orElse(new ArrayList<>());
|
return Optional.ofNullable(defaultTemplateDTO.getCustomFields()).orElse(new ArrayList<>());
|
||||||
return customFields;
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 导入前校验xmind格式
|
||||||
|
*
|
||||||
|
* @return FunctionalCaseImportResponse
|
||||||
|
*/
|
||||||
|
public FunctionalCaseImportResponse preCheckXMind(FunctionalCaseImportRequest request,SessionUser user, MultipartFile multipartFile) {
|
||||||
|
if (multipartFile == null) {
|
||||||
|
throw new MSException(Translator.get("file_cannot_be_null"));
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
List<ExcelErrData<FunctionalCaseExcelData>> errList;
|
||||||
|
FunctionalCaseImportResponse response = new FunctionalCaseImportResponse();
|
||||||
|
//设置默认版本
|
||||||
|
if (StringUtils.isEmpty(request.getVersionId())) {
|
||||||
|
request.setVersionId(extBaseProjectVersionMapper.getDefaultVersion(request.getProjectId()));
|
||||||
|
}
|
||||||
|
Long nextPos = functionalCaseService.getNextOrder(request.getProjectId());
|
||||||
|
Long lasePos = nextPos + ((long) ServiceUtils.POS_STEP * Integer.parseInt(request.getCount()));
|
||||||
|
//获取当前项目默认模板的自定义字段
|
||||||
|
List<TemplateCustomFieldDTO> customFields = getCustomFields(request.getProjectId());
|
||||||
|
XMindCaseParser xMindParser = new XMindCaseParser(request, customFields, user,lasePos);
|
||||||
|
errList = xMindParser.parse(multipartFile);
|
||||||
|
xMindParser.clear();
|
||||||
|
response.setErrorMessages(errList);
|
||||||
|
response.setSuccessCount(xMindParser.getList().size()+xMindParser.getUpdateList().size());
|
||||||
|
response.setFailCount(errList.size());
|
||||||
|
return response;
|
||||||
|
} catch (Exception e) {
|
||||||
|
LogUtils.error("checkImportExcel error", e);
|
||||||
|
throw new MSException(Translator.get("check_import_excel_error"));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -332,6 +366,43 @@ public class FunctionalCaseFileService {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public FunctionalCaseImportResponse importXMind(FunctionalCaseImportRequest request, SessionUser user, MultipartFile multipartFile) {
|
||||||
|
if (multipartFile == null) {
|
||||||
|
throw new MSException(Translator.get("file_cannot_be_null"));
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
List<ExcelErrData<FunctionalCaseExcelData>> errList;
|
||||||
|
FunctionalCaseImportResponse response = new FunctionalCaseImportResponse();
|
||||||
|
//设置默认版本
|
||||||
|
if (StringUtils.isEmpty(request.getVersionId())) {
|
||||||
|
request.setVersionId(extBaseProjectVersionMapper.getDefaultVersion(request.getProjectId()));
|
||||||
|
}
|
||||||
|
Long nextPos = functionalCaseService.getNextOrder(request.getProjectId());
|
||||||
|
Long lasePos = nextPos + ((long) ServiceUtils.POS_STEP * Integer.parseInt(request.getCount()));
|
||||||
|
//获取当前项目默认模板的自定义字段
|
||||||
|
List<TemplateCustomFieldDTO> customFields = getCustomFields(request.getProjectId());
|
||||||
|
XMindCaseParser xmindParser = new XMindCaseParser(request, customFields, user,lasePos);
|
||||||
|
errList = xmindParser.parse(multipartFile);
|
||||||
|
if (CollectionUtils.isEmpty(xmindParser.getList())
|
||||||
|
&& CollectionUtils.isEmpty(xmindParser.getUpdateList())) {
|
||||||
|
if (errList == null) {
|
||||||
|
errList = new ArrayList<>();
|
||||||
|
}
|
||||||
|
ExcelErrData excelErrData = new ExcelErrData(1, Translator.get("upload_fail") + ":" + Translator.get("upload_content_is_null"));
|
||||||
|
errList.add(excelErrData);
|
||||||
|
}
|
||||||
|
xmindParser.saveData();
|
||||||
|
xmindParser.clear();
|
||||||
|
response.setErrorMessages(errList);
|
||||||
|
response.setSuccessCount(xmindParser.getList().size()+xmindParser.getUpdateList().size());
|
||||||
|
response.setFailCount(errList.size());
|
||||||
|
return response;
|
||||||
|
} catch (Exception e) {
|
||||||
|
LogUtils.error("checkImportExcel error", e);
|
||||||
|
throw new MSException(Translator.get("check_import_excel_error"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public void export(String userId, FunctionalCaseExportRequest request) {
|
public void export(String userId, FunctionalCaseExportRequest request) {
|
||||||
try {
|
try {
|
||||||
ExportTaskExample exportTaskExample = new ExportTaskExample();
|
ExportTaskExample exportTaskExample = new ExportTaskExample();
|
||||||
|
|
|
@ -1273,11 +1273,11 @@ public class FunctionalCaseService {
|
||||||
}
|
}
|
||||||
|
|
||||||
private void setCustomFieldValue(Object value, FunctionalCaseCustomField caseCustomField) {
|
private void setCustomFieldValue(Object value, FunctionalCaseCustomField caseCustomField) {
|
||||||
if (StringUtils.equalsIgnoreCase(value.toString(), "[]") || value instanceof List) {
|
if (value !=null && (StringUtils.equalsIgnoreCase(value.toString(), "[]") || value instanceof List)) {
|
||||||
//数组类型
|
//数组类型
|
||||||
caseCustomField.setValue(JSON.toJSONString(value));
|
caseCustomField.setValue(JSON.toJSONString(value));
|
||||||
} else {
|
} else {
|
||||||
caseCustomField.setValue(value.toString());
|
caseCustomField.setValue(value == null ? StringUtils.EMPTY : value.toString());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,477 @@
|
||||||
|
package io.metersphere.functional.xmind.parser;
|
||||||
|
|
||||||
|
import io.metersphere.functional.dto.FunctionalCaseStepDTO;
|
||||||
|
import io.metersphere.functional.excel.domain.FunctionalCaseExcelData;
|
||||||
|
import io.metersphere.functional.excel.exception.CustomFieldValidateException;
|
||||||
|
import io.metersphere.functional.excel.validate.AbstractCustomFieldValidator;
|
||||||
|
import io.metersphere.functional.excel.validate.CustomFieldValidatorFactory;
|
||||||
|
import io.metersphere.functional.request.FunctionalCaseImportRequest;
|
||||||
|
import io.metersphere.functional.service.FunctionalCaseModuleService;
|
||||||
|
import io.metersphere.functional.service.FunctionalCaseService;
|
||||||
|
import io.metersphere.functional.xmind.pojo.Attached;
|
||||||
|
import io.metersphere.functional.xmind.pojo.JsonRootBean;
|
||||||
|
import io.metersphere.functional.xmind.utils.DetailUtil;
|
||||||
|
import io.metersphere.sdk.util.CommonBeanFactory;
|
||||||
|
import io.metersphere.sdk.util.JSON;
|
||||||
|
import io.metersphere.sdk.util.LogUtils;
|
||||||
|
import io.metersphere.sdk.util.Translator;
|
||||||
|
import io.metersphere.system.dto.sdk.BaseTreeNode;
|
||||||
|
import io.metersphere.system.dto.sdk.SessionUser;
|
||||||
|
import io.metersphere.system.dto.sdk.TemplateCustomFieldDTO;
|
||||||
|
import io.metersphere.system.excel.domain.ExcelErrData;
|
||||||
|
import lombok.Getter;
|
||||||
|
import org.apache.commons.collections.CollectionUtils;
|
||||||
|
import org.apache.commons.lang3.StringUtils;
|
||||||
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
import org.springframework.web.multipart.MultipartFile;
|
||||||
|
|
||||||
|
import java.util.*;
|
||||||
|
import java.util.concurrent.atomic.AtomicBoolean;
|
||||||
|
import java.util.concurrent.atomic.AtomicLong;
|
||||||
|
import java.util.regex.Matcher;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 数据转换
|
||||||
|
* 1 解析Xmind文件 XmindParser.parseObject
|
||||||
|
* 2 解析后的JSON this.parse 转成测试用例
|
||||||
|
*/
|
||||||
|
public class XMindCaseParser {
|
||||||
|
|
||||||
|
Map<String, TemplateCustomFieldDTO> customFieldsMap;
|
||||||
|
@Getter
|
||||||
|
protected List<FunctionalCaseExcelData> list = new ArrayList<>();
|
||||||
|
@Getter
|
||||||
|
protected List<FunctionalCaseExcelData> updateList = new ArrayList<>();
|
||||||
|
|
||||||
|
private FunctionalCaseService functionalCaseService;
|
||||||
|
|
||||||
|
private FunctionalCaseImportRequest request;
|
||||||
|
/**
|
||||||
|
* 所有的模块集合
|
||||||
|
*/
|
||||||
|
private List<BaseTreeNode> moduleTree;
|
||||||
|
private SessionUser user;
|
||||||
|
private Map<String, String> pathMap = new HashMap<>();
|
||||||
|
protected static final int TAGS_COUNT = 15;
|
||||||
|
protected static final int TAG_LENGTH = 64;
|
||||||
|
protected static final int STEP_LENGTH = 1000;
|
||||||
|
private AtomicLong lastPos;
|
||||||
|
private HashMap<String, AbstractCustomFieldValidator> customFieldValidatorMap;
|
||||||
|
/**
|
||||||
|
* 过程校验记录
|
||||||
|
*/
|
||||||
|
private DetailUtil process;
|
||||||
|
|
||||||
|
public XMindCaseParser(FunctionalCaseImportRequest request, List<TemplateCustomFieldDTO> customFields, SessionUser user, Long pos) {
|
||||||
|
//当前项目模板的自定义字段
|
||||||
|
this.request = request;
|
||||||
|
customFieldsMap = customFields.stream().collect(Collectors.toMap(TemplateCustomFieldDTO::getFieldName, i -> i));
|
||||||
|
functionalCaseService = CommonBeanFactory.getBean(FunctionalCaseService.class);
|
||||||
|
moduleTree = CommonBeanFactory.getBean(FunctionalCaseModuleService.class).getTree(request.getProjectId());
|
||||||
|
customFieldValidatorMap = CustomFieldValidatorFactory.getValidatorMap();
|
||||||
|
lastPos = new AtomicLong(pos);
|
||||||
|
this.user = user;
|
||||||
|
process = new DetailUtil();
|
||||||
|
}
|
||||||
|
|
||||||
|
private final List<String> priorityList = Arrays.asList("P0", "P1", "P2", "P3");
|
||||||
|
|
||||||
|
private static final String ID = "(?:id:|id:)";
|
||||||
|
private static final String CASE = "(?:CASE-|case-)";
|
||||||
|
private static final String PREREQUISITE = "(?:" + Translator.get("xmind_prerequisite") + ":|" + Translator.get("xmind_prerequisite") + ":)";
|
||||||
|
private static final String STEP = "(?:" + Translator.get("xmind_step") + ":|" + Translator.get("xmind_step") + ":)";
|
||||||
|
private static final String STEP_DESCRIPTION = "(?:" + Translator.get("xmind_stepDescription") + ":|" + Translator.get("xmind_stepDescription") + ":)";
|
||||||
|
private static final String TEXT_DESCRIPTION = "(?:" + Translator.get("xmind_textDescription") + ":|" + Translator.get("xmind_textDescription") + ":)";
|
||||||
|
private static final String EXPECTED_RESULT = "(?:" + Translator.get("xmind_expectedResult") + ":|" + Translator.get("xmind_expectedResult") + ":)";
|
||||||
|
private static final String DESCRIPTION = "(?:" + Translator.get("xmind_description") + ":|" + Translator.get("xmind_description") + ":)";
|
||||||
|
private static final String TAGS = "(?:" + Translator.get("xmind_tags") + ":|" + Translator.get("xmind_tags") + ":)";
|
||||||
|
|
||||||
|
|
||||||
|
public void clear() {
|
||||||
|
list.clear();
|
||||||
|
updateList.clear();
|
||||||
|
pathMap.clear();
|
||||||
|
moduleTree = new ArrayList<>();
|
||||||
|
customFieldValidatorMap = new HashMap<>();
|
||||||
|
lastPos = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isAvailable(String str, String regex) {
|
||||||
|
if (StringUtils.isEmpty(str) || StringUtils.isEmpty(regex)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
Pattern pattern = Pattern.compile(regex, Pattern.CASE_INSENSITIVE);
|
||||||
|
Matcher result = pattern.matcher(str);
|
||||||
|
return result.find();
|
||||||
|
}
|
||||||
|
|
||||||
|
private String replace(String str, String regex) {
|
||||||
|
if (StringUtils.isEmpty(str) || StringUtils.isEmpty(regex)) {
|
||||||
|
return str;
|
||||||
|
}
|
||||||
|
Pattern pattern = Pattern.compile(regex, Pattern.CASE_INSENSITIVE);
|
||||||
|
Matcher result = pattern.matcher(str);
|
||||||
|
str = result.replaceAll(StringUtils.EMPTY);
|
||||||
|
return str;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 递归处理案例数据
|
||||||
|
*/
|
||||||
|
private void recursion(Attached parent, int level, List<Attached> attachedList) {
|
||||||
|
for (Attached item : attachedList) {
|
||||||
|
if (isAvailable(item.getTitle(), CASE)) {
|
||||||
|
item.setParent(parent);
|
||||||
|
// 格式化一个用例
|
||||||
|
this.formatTestCase(item.getTitle(), parent.getPath(), item.getChildren() != null ? item.getChildren().getAttached() : null);
|
||||||
|
} else {
|
||||||
|
String nodePath = parent.getPath().trim() + "/" + item.getTitle().trim();
|
||||||
|
item.setPath(nodePath);
|
||||||
|
item.setParent(parent);
|
||||||
|
if (item.getChildren() != null && CollectionUtils.isNotEmpty(item.getChildren().getAttached())) {
|
||||||
|
recursion(item, level + 1, item.getChildren().getAttached());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 验证用例的合规性
|
||||||
|
*/
|
||||||
|
public boolean validate(FunctionalCaseExcelData data) {
|
||||||
|
//模块校验
|
||||||
|
boolean validate;
|
||||||
|
validate = validateModule(data);
|
||||||
|
if (!validate) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
//校验自定义字段
|
||||||
|
validate = validateCustomField(data);
|
||||||
|
if (!validate) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
//校验id
|
||||||
|
validate = validateIdExist(data);
|
||||||
|
if (!validate) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
//标签长度校验
|
||||||
|
validate = validateTags(data);
|
||||||
|
return validate;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 校验标签长度 个数
|
||||||
|
*/
|
||||||
|
private boolean validateTags(FunctionalCaseExcelData data) {
|
||||||
|
AtomicBoolean validate = new AtomicBoolean(true);
|
||||||
|
List<String> tags = functionalCaseService.handleImportTags(data.getTags());
|
||||||
|
if (tags.size() > TAGS_COUNT) {
|
||||||
|
process.add(data.getName(), Translator.get("tags_count"));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
tags.forEach(tag -> {
|
||||||
|
if (tag.length() > TAG_LENGTH) {
|
||||||
|
process.add(data.getName(), Translator.get("tag_length"));
|
||||||
|
validate.set(false);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return validate.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 校验模块
|
||||||
|
*/
|
||||||
|
private boolean validateModule(FunctionalCaseExcelData data) {
|
||||||
|
boolean validate = true;
|
||||||
|
String module = data.getModule();
|
||||||
|
if (StringUtils.isNotEmpty(module)) {
|
||||||
|
String[] nodes = module.split("/");
|
||||||
|
//模块名不能为空
|
||||||
|
for (int i = 0; i < nodes.length; i++) {
|
||||||
|
if (i != 0 && StringUtils.equals(nodes[i].trim(), StringUtils.EMPTY)) {
|
||||||
|
validate = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//增加字数校验,每一层不能超过100个字
|
||||||
|
for (String nodeStr : nodes) {
|
||||||
|
if (StringUtils.isNotEmpty(nodeStr)) {
|
||||||
|
if (nodeStr.trim().length() > 100) {
|
||||||
|
validate = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return validate;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 校验Excel中是否有ID
|
||||||
|
* 是否覆盖:
|
||||||
|
* 1.覆盖:id存在则更新,id不存在则新增
|
||||||
|
* 2.不覆盖:id存在不处理,id不存在新增
|
||||||
|
*/
|
||||||
|
@Nullable
|
||||||
|
private boolean validateIdExist(FunctionalCaseExcelData data) {
|
||||||
|
boolean validate = true;
|
||||||
|
//当前读取的数据有ID
|
||||||
|
if (StringUtils.isNotEmpty(data.getNum())) {
|
||||||
|
Integer num = -1;
|
||||||
|
try {
|
||||||
|
num = Integer.parseInt(data.getNum());
|
||||||
|
} catch (Exception e) {
|
||||||
|
validate = false;
|
||||||
|
process.add(data.getName(), Translator.get("id_not_rightful") + "[" + data.getNum() + "]");
|
||||||
|
}
|
||||||
|
if (num < 0) {
|
||||||
|
validate = false;
|
||||||
|
process.add(data.getName(), Translator.get("id_not_rightful") + "[" + data.getNum() + "]");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return validate;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 校验自定义字段,并记录错误提示
|
||||||
|
* 如果填写的是自定义字段的选项值,则转换成ID保存
|
||||||
|
*/
|
||||||
|
private boolean validateCustomField(FunctionalCaseExcelData data) {
|
||||||
|
boolean validate = true;
|
||||||
|
Map<String, Object> customData = data.getCustomData();
|
||||||
|
for (String fieldName : customData.keySet()) {
|
||||||
|
Object value = customData.get(fieldName);
|
||||||
|
TemplateCustomFieldDTO templateCustomFieldDTO = customFieldsMap.get(fieldName);
|
||||||
|
if (templateCustomFieldDTO == null) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
AbstractCustomFieldValidator customFieldValidator = customFieldValidatorMap.get(templateCustomFieldDTO.getType());
|
||||||
|
try {
|
||||||
|
customFieldValidator.validate(templateCustomFieldDTO, value.toString());
|
||||||
|
if (customFieldValidator.isKVOption) {
|
||||||
|
// 这里如果填的是选项值,替换成选项ID,保存
|
||||||
|
customData.put(fieldName, customFieldValidator.parse2Key(value.toString(), templateCustomFieldDTO));
|
||||||
|
}
|
||||||
|
} catch (CustomFieldValidateException e) {
|
||||||
|
validate = false;
|
||||||
|
process.add(data.getName(), e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return validate;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 处理新增数据集合还是更新数据集合
|
||||||
|
*
|
||||||
|
* @param functionalCaseExcelData
|
||||||
|
*/
|
||||||
|
private void handleId(FunctionalCaseExcelData functionalCaseExcelData) {
|
||||||
|
if (StringUtils.isNotEmpty(functionalCaseExcelData.getNum())) {
|
||||||
|
String checkResult = functionalCaseService.checkNumExist(functionalCaseExcelData.getNum(), request.getProjectId());
|
||||||
|
if (StringUtils.isNotEmpty(checkResult)) {
|
||||||
|
if (request.isCover()) {
|
||||||
|
//如果是覆盖,那么有id的需要更新
|
||||||
|
functionalCaseExcelData.setNum(checkResult);
|
||||||
|
updateList.add(functionalCaseExcelData);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
list.add(functionalCaseExcelData);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
list.add(functionalCaseExcelData);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 格式化一个用例
|
||||||
|
*/
|
||||||
|
private void formatTestCase(String title, String nodePath, List<Attached> attacheds) {
|
||||||
|
FunctionalCaseExcelData testCase = new FunctionalCaseExcelData();
|
||||||
|
String tc = title.replace(":", ":");
|
||||||
|
String[] tcArrs = tc.split(":");
|
||||||
|
if (tcArrs.length <= 1) {
|
||||||
|
process.add(Translator.get("test_case_name") + Translator.get("incorrect_format"), title);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// 用例名称
|
||||||
|
String name = title.replace(tcArrs[0] + ":", StringUtils.EMPTY).replace(tcArrs[0] + ":", StringUtils.EMPTY);
|
||||||
|
testCase.setName(name);
|
||||||
|
nodePath = nodePath.trim();
|
||||||
|
if (!nodePath.startsWith("/")) {
|
||||||
|
nodePath = "/" + nodePath;
|
||||||
|
}
|
||||||
|
if (nodePath.endsWith("/")) {
|
||||||
|
nodePath = nodePath.substring(0, nodePath.length() - 1);
|
||||||
|
}
|
||||||
|
testCase.setModule(nodePath);
|
||||||
|
// 用例等级和用例性质处理
|
||||||
|
if (tcArrs[0].contains("-")) {
|
||||||
|
for (String item : tcArrs[0].split("-")) {
|
||||||
|
if (item.toUpperCase().startsWith("P")) {
|
||||||
|
Map<String, Object> customData = new LinkedHashMap<>();
|
||||||
|
// 用例等级和用例性质处理
|
||||||
|
if (!priorityList.contains(item.toUpperCase())) {
|
||||||
|
process.add(title, Translator.get("test_case_priority") + Translator.get("incorrect_format"));
|
||||||
|
customData.put("priority", "P0");
|
||||||
|
} else {
|
||||||
|
customData.put("priority", item.toUpperCase());
|
||||||
|
}
|
||||||
|
testCase.setCustomData(customData);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 用例id, blobs, tags, 自定义字段处理
|
||||||
|
StringBuilder customId = new StringBuilder();
|
||||||
|
if (attacheds != null && !attacheds.isEmpty()) {
|
||||||
|
attacheds.forEach(item -> {
|
||||||
|
//id
|
||||||
|
if (isAvailable(item.getTitle(), ID)) {
|
||||||
|
customId.append(replace(item.getTitle(), ID));
|
||||||
|
testCase.setNum(customId.toString());
|
||||||
|
} else if (isAvailable(item.getTitle(), PREREQUISITE)) {
|
||||||
|
testCase.setPrerequisite(replace(item.getTitle(), PREREQUISITE));
|
||||||
|
} else if (isAvailable(item.getTitle(), TEXT_DESCRIPTION)) {
|
||||||
|
testCase.setTextDescription(replace(item.getTitle(), TEXT_DESCRIPTION));
|
||||||
|
} else if (isAvailable(item.getTitle(), DESCRIPTION)) {
|
||||||
|
testCase.setTextDescription(replace(item.getTitle(), DESCRIPTION));
|
||||||
|
} else if (isAvailable(item.getTitle(), TAGS)) {
|
||||||
|
String tag = replace(item.getTitle(), TAGS);
|
||||||
|
String[] tagArr = tag.split("\\|");
|
||||||
|
if (CollectionUtils.isNotEmpty(Arrays.asList(tagArr))) {
|
||||||
|
String tags = StringUtils.joinWith(",", Arrays.asList(tagArr));
|
||||||
|
testCase.setTags(tags);
|
||||||
|
}
|
||||||
|
} else if (isAvailable(item.getTitle(), STEP_DESCRIPTION)) {
|
||||||
|
if (item.getChildren() != null) {
|
||||||
|
testCase.setSteps(this.getSteps(item.getChildren().getAttached(), title));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
//自定义字段
|
||||||
|
String[] customFiled = item.getTitle().split("(?::|:)");
|
||||||
|
Map<String, Object> stringObjectMap = testCase.getCustomData();
|
||||||
|
if (customFiled.length > 1) {
|
||||||
|
TemplateCustomFieldDTO templateCustomFieldDTO = customFieldsMap.get(customFiled[0]);
|
||||||
|
if (templateCustomFieldDTO == null) {
|
||||||
|
stringObjectMap.put(customFiled[0], customFiled[1]);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
TemplateCustomFieldDTO templateCustomFieldDTO = customFieldsMap.get(customFiled[0]);
|
||||||
|
if (templateCustomFieldDTO == null) {
|
||||||
|
stringObjectMap.put(customFiled[0], StringUtils.EMPTY);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
testCase.setCustomData(stringObjectMap);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
boolean validate = validate(testCase);
|
||||||
|
if (validate) {
|
||||||
|
handleId(testCase);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 执行保存数据
|
||||||
|
*/
|
||||||
|
public void saveData() {
|
||||||
|
if (CollectionUtils.isNotEmpty(list)) {
|
||||||
|
functionalCaseService.saveImportData(list, request, moduleTree, customFieldsMap, pathMap, user, lastPos);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (CollectionUtils.isNotEmpty(updateList)) {
|
||||||
|
functionalCaseService.updateImportData(updateList, request, moduleTree, customFieldsMap, pathMap, user);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取步骤数据
|
||||||
|
*/
|
||||||
|
private String getSteps(List<Attached> attacheds, String caseName) {
|
||||||
|
List<FunctionalCaseStepDTO> functionalCaseStepDTOS = new ArrayList<>();
|
||||||
|
if (!attacheds.isEmpty()) {
|
||||||
|
for (int i = 0; i < attacheds.size(); i++) {
|
||||||
|
// 保持插入顺序,判断用例是否有相同的steps
|
||||||
|
FunctionalCaseStepDTO functionalCaseStepDTO = new FunctionalCaseStepDTO();
|
||||||
|
functionalCaseStepDTO.setId(UUID.randomUUID().toString());
|
||||||
|
functionalCaseStepDTO.setNum(i + 1);
|
||||||
|
if (isAvailable(attacheds.get(i).getTitle(), STEP)) {
|
||||||
|
String stepDesc = attacheds.get(i).getTitle().replace(":", ":");
|
||||||
|
String[] stepDescArrs = stepDesc.split(":");
|
||||||
|
functionalCaseStepDTO.setDesc(StringUtils.isNotBlank(stepDescArrs[1]) ? stepDescArrs[1] : StringUtils.EMPTY);
|
||||||
|
} else {
|
||||||
|
functionalCaseStepDTO.setDesc(StringUtils.EMPTY);
|
||||||
|
}
|
||||||
|
if (functionalCaseStepDTO.getDesc().length() > STEP_LENGTH) {
|
||||||
|
process.add(caseName, Translator.get("step_length"));
|
||||||
|
return JSON.toJSONString(functionalCaseStepDTOS);
|
||||||
|
}
|
||||||
|
if (attacheds.get(i) != null && attacheds.get(i).getChildren() != null && attacheds.get(i).getChildren().getAttached() != null) {
|
||||||
|
String title = attacheds.get(i).getChildren().getAttached().get(0).getTitle();
|
||||||
|
if (isAvailable(title, EXPECTED_RESULT)) {
|
||||||
|
String stepDesc = title.replace(":", ":");
|
||||||
|
String[] stepDescArrs = stepDesc.split(":");
|
||||||
|
functionalCaseStepDTO.setResult(StringUtils.isNotBlank(stepDescArrs[1]) ? stepDescArrs[1] : StringUtils.EMPTY);
|
||||||
|
} else {
|
||||||
|
functionalCaseStepDTO.setResult(StringUtils.EMPTY);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (functionalCaseStepDTO.getResult().length() > STEP_LENGTH) {
|
||||||
|
process.add(caseName, Translator.get("result_length"));
|
||||||
|
return JSON.toJSONString(functionalCaseStepDTOS);
|
||||||
|
}
|
||||||
|
functionalCaseStepDTOS.add(functionalCaseStepDTO);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// 保持插入顺序,判断用例是否有相同的steps
|
||||||
|
FunctionalCaseStepDTO functionalCaseStepDTO = new FunctionalCaseStepDTO();
|
||||||
|
functionalCaseStepDTO.setId(UUID.randomUUID().toString());
|
||||||
|
functionalCaseStepDTO.setNum(1);
|
||||||
|
functionalCaseStepDTO.setDesc(StringUtils.EMPTY);
|
||||||
|
functionalCaseStepDTO.setResult(StringUtils.EMPTY);
|
||||||
|
functionalCaseStepDTOS.add(functionalCaseStepDTO);
|
||||||
|
}
|
||||||
|
return JSON.toJSONString(functionalCaseStepDTOS);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 导入思维导图处理
|
||||||
|
*/
|
||||||
|
public List<ExcelErrData<FunctionalCaseExcelData>> parse(MultipartFile multipartFile) {
|
||||||
|
try {
|
||||||
|
// 获取思维导图内容
|
||||||
|
List<JsonRootBean> roots = XMindParser.parseObject(multipartFile);
|
||||||
|
for (JsonRootBean root : roots) {
|
||||||
|
if (root != null && root.getRootTopic() != null && root.getRootTopic().getChildren() != null) {
|
||||||
|
// 判断是模块还是用例
|
||||||
|
for (Attached item : root.getRootTopic().getChildren().getAttached()) {
|
||||||
|
// 用例
|
||||||
|
if (isAvailable(item.getTitle(), CASE)) {
|
||||||
|
return process.parse(replace(item.getTitle(), CASE) + ":" + Translator.get("test_case_create_module_fail"));
|
||||||
|
} else {
|
||||||
|
String modulePath = item.getTitle();
|
||||||
|
item.setPath(modulePath);
|
||||||
|
if (item.getChildren() != null && !item.getChildren().getAttached().isEmpty()) {
|
||||||
|
// 递归处理案例数据
|
||||||
|
recursion(item, 1, item.getChildren().getAttached());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (Exception ex) {
|
||||||
|
LogUtils.info(ex.getMessage());
|
||||||
|
return process.parse(ex.getMessage());
|
||||||
|
}
|
||||||
|
return process.parse();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -1,6 +1,7 @@
|
||||||
package io.metersphere.functional.xmind.parser;
|
package io.metersphere.functional.xmind.parser;
|
||||||
|
|
||||||
import com.fasterxml.jackson.databind.JsonNode;
|
import com.fasterxml.jackson.databind.JsonNode;
|
||||||
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
import io.metersphere.sdk.util.LogUtils;
|
import io.metersphere.sdk.util.LogUtils;
|
||||||
import io.metersphere.sdk.util.XMLUtils;
|
import io.metersphere.sdk.util.XMLUtils;
|
||||||
import org.apache.commons.lang3.StringUtils;
|
import org.apache.commons.lang3.StringUtils;
|
||||||
|
@ -73,8 +74,8 @@ public class XMindLegacy {
|
||||||
String res = sheet.asXML();
|
String res = sheet.asXML();
|
||||||
// 将xml转为json
|
// 将xml转为json
|
||||||
JsonNode xmlJSONObj = XMLUtils.xmlConvertJson(res);
|
JsonNode xmlJSONObj = XMLUtils.xmlConvertJson(res);
|
||||||
JsonNode jsonNode = xmlJSONObj.get("sheet");
|
ObjectMapper objectMapper = new ObjectMapper();
|
||||||
sheets.add(jsonNode.toString());
|
sheets.add(objectMapper.writeValueAsString(xmlJSONObj));
|
||||||
}
|
}
|
||||||
// 设置缩进
|
// 设置缩进
|
||||||
return sheets;
|
return sheets;
|
||||||
|
|
|
@ -66,7 +66,7 @@ public class XMindParser {
|
||||||
List<JsonRootBean> jsonRootBeans = new ArrayList<>();
|
List<JsonRootBean> jsonRootBeans = new ArrayList<>();
|
||||||
if (contents != null) {
|
if (contents != null) {
|
||||||
for (String content : contents) {
|
for (String content : contents) {
|
||||||
caseCount += content.split("(case-:)").length;
|
caseCount += content.split("(?:case-|CASE-)").length;
|
||||||
JsonRootBean jsonRootBean = JSON.parseObject(content, JsonRootBean.class);
|
JsonRootBean jsonRootBean = JSON.parseObject(content, JsonRootBean.class);
|
||||||
jsonRootBeans.add(jsonRootBean);
|
jsonRootBeans.add(jsonRootBean);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,14 +1,16 @@
|
||||||
|
|
||||||
package io.metersphere.functional.xmind.pojo;
|
package io.metersphere.functional.xmind.pojo;
|
||||||
|
|
||||||
import lombok.Data;
|
import lombok.Getter;
|
||||||
|
import lombok.Setter;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* XMind 节点对象
|
* XMind 节点对象
|
||||||
*/
|
*/
|
||||||
@Data
|
@Setter
|
||||||
|
@Getter
|
||||||
public class Attached {
|
public class Attached {
|
||||||
|
|
||||||
private String id;
|
private String id;
|
||||||
|
|
|
@ -0,0 +1,70 @@
|
||||||
|
package io.metersphere.functional.xmind.utils;
|
||||||
|
|
||||||
|
|
||||||
|
import io.metersphere.functional.excel.domain.FunctionalCaseExcelData;
|
||||||
|
import io.metersphere.sdk.util.Translator;
|
||||||
|
import io.metersphere.system.excel.domain.ExcelErrData;
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.Setter;
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.LinkedHashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
@Setter
|
||||||
|
public class DetailUtil implements Serializable {
|
||||||
|
|
||||||
|
private Map<String, StringBuilder> process;
|
||||||
|
|
||||||
|
public DetailUtil() {
|
||||||
|
process = new LinkedHashMap<>();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void add(String type, String msContent) {
|
||||||
|
if (process.containsKey(type)) {
|
||||||
|
process.get(type).append(msContent + ";");
|
||||||
|
} else {
|
||||||
|
process.put(type, new StringBuilder(msContent + ";"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<ExcelErrData<FunctionalCaseExcelData>> parse(String content) {
|
||||||
|
List<ExcelErrData<FunctionalCaseExcelData>> errList = new ArrayList<>();
|
||||||
|
ExcelErrData excelErrData = new ExcelErrData(1, content);
|
||||||
|
errList.add(excelErrData);
|
||||||
|
return errList;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<ExcelErrData<FunctionalCaseExcelData>> parse() {
|
||||||
|
List<ExcelErrData<FunctionalCaseExcelData>> errList = new ArrayList<>();
|
||||||
|
List<String> result = new ArrayList<>();
|
||||||
|
process.entrySet().parallelStream().reduce(result, (first, second) -> {
|
||||||
|
first.add(second.getKey() + ":" + second.getValue());
|
||||||
|
return first;
|
||||||
|
}, (first, second) -> {
|
||||||
|
if (first == second) {
|
||||||
|
return first;
|
||||||
|
}
|
||||||
|
first.addAll(second);
|
||||||
|
return first;
|
||||||
|
});
|
||||||
|
|
||||||
|
int emptyName = 0;
|
||||||
|
for (int i = 0; i < result.size(); i++) {
|
||||||
|
if (result.get(i).contains(Translator.get("test_case_name") + Translator.get("incorrect_format"))) {
|
||||||
|
emptyName +=1;
|
||||||
|
} else {
|
||||||
|
ExcelErrData excelErrData = new ExcelErrData(i, result.get(i));
|
||||||
|
errList.add(excelErrData);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (emptyName>0) {
|
||||||
|
ExcelErrData excelErrData = new ExcelErrData(result.size(), "部分用例名称为空,校验失败;");
|
||||||
|
errList.add(excelErrData);
|
||||||
|
}
|
||||||
|
return errList;
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,9 +1,6 @@
|
||||||
package io.metersphere.functional.controller;
|
package io.metersphere.functional.controller;
|
||||||
|
|
||||||
import io.metersphere.functional.domain.FunctionalCase;
|
import io.metersphere.functional.domain.*;
|
||||||
import io.metersphere.functional.domain.FunctionalCaseAttachment;
|
|
||||||
import io.metersphere.functional.domain.FunctionalCaseAttachmentExample;
|
|
||||||
import io.metersphere.functional.domain.FunctionalCaseCustomField;
|
|
||||||
import io.metersphere.functional.dto.CaseCustomFieldDTO;
|
import io.metersphere.functional.dto.CaseCustomFieldDTO;
|
||||||
import io.metersphere.functional.dto.FunctionalCaseAttachmentDTO;
|
import io.metersphere.functional.dto.FunctionalCaseAttachmentDTO;
|
||||||
import io.metersphere.functional.dto.FunctionalCasePageDTO;
|
import io.metersphere.functional.dto.FunctionalCasePageDTO;
|
||||||
|
@ -11,6 +8,7 @@ import io.metersphere.functional.dto.response.FunctionalCaseImportResponse;
|
||||||
import io.metersphere.functional.excel.domain.FunctionalCaseHeader;
|
import io.metersphere.functional.excel.domain.FunctionalCaseHeader;
|
||||||
import io.metersphere.functional.mapper.FunctionalCaseAttachmentMapper;
|
import io.metersphere.functional.mapper.FunctionalCaseAttachmentMapper;
|
||||||
import io.metersphere.functional.mapper.FunctionalCaseCustomFieldMapper;
|
import io.metersphere.functional.mapper.FunctionalCaseCustomFieldMapper;
|
||||||
|
import io.metersphere.functional.mapper.FunctionalCaseMapper;
|
||||||
import io.metersphere.functional.request.*;
|
import io.metersphere.functional.request.*;
|
||||||
import io.metersphere.functional.result.CaseManagementResultCode;
|
import io.metersphere.functional.result.CaseManagementResultCode;
|
||||||
import io.metersphere.functional.utils.FileBaseUtils;
|
import io.metersphere.functional.utils.FileBaseUtils;
|
||||||
|
@ -84,7 +82,9 @@ public class FunctionalCaseControllerTests extends BaseTest {
|
||||||
public static final String FUNCTIONAL_CASE_POS_URL = "/functional/case/edit/pos";
|
public static final String FUNCTIONAL_CASE_POS_URL = "/functional/case/edit/pos";
|
||||||
public static final String DOWNLOAD_EXCEL_TEMPLATE_URL = "/functional/case/download/excel/template/";
|
public static final String DOWNLOAD_EXCEL_TEMPLATE_URL = "/functional/case/download/excel/template/";
|
||||||
public static final String CHECK_EXCEL_URL = "/functional/case/pre-check/excel";
|
public static final String CHECK_EXCEL_URL = "/functional/case/pre-check/excel";
|
||||||
|
public static final String CHECK_XMIND_URL = "/functional/case/pre-check/xmind";
|
||||||
public static final String IMPORT_EXCEL_URL = "/functional/case/import/excel";
|
public static final String IMPORT_EXCEL_URL = "/functional/case/import/excel";
|
||||||
|
public static final String IMPORT_XMIND_URL = "/functional/case/import/xmind";
|
||||||
public static final String OPERATION_HISTORY_URL = "/functional/case/operation-history";
|
public static final String OPERATION_HISTORY_URL = "/functional/case/operation-history";
|
||||||
public static final String EXPORT_EXCEL_URL = "/functional/case/export/excel";
|
public static final String EXPORT_EXCEL_URL = "/functional/case/export/excel";
|
||||||
public static final String DOWNLOAD_XMIND_TEMPLATE_URL = "/functional/case/download/xmind/template/";
|
public static final String DOWNLOAD_XMIND_TEMPLATE_URL = "/functional/case/download/xmind/template/";
|
||||||
|
@ -109,6 +109,9 @@ public class FunctionalCaseControllerTests extends BaseTest {
|
||||||
@Resource
|
@Resource
|
||||||
private FunctionalCaseAttachmentMapper functionalCaseAttachmentMapper;
|
private FunctionalCaseAttachmentMapper functionalCaseAttachmentMapper;
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private FunctionalCaseMapper functionalCaseMapper;
|
||||||
|
|
||||||
protected static String functionalCaseId;
|
protected static String functionalCaseId;
|
||||||
|
|
||||||
|
|
||||||
|
@ -774,6 +777,136 @@ public class FunctionalCaseControllerTests extends BaseTest {
|
||||||
this.requestMultipart(IMPORT_EXCEL_URL, paramMap);
|
this.requestMultipart(IMPORT_EXCEL_URL, paramMap);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@Order(19)
|
||||||
|
public void testImportXmind() throws Exception {
|
||||||
|
String filePath = Objects.requireNonNull(this.getClass().getClassLoader().getResource("file/1xml.xmind")).getPath();
|
||||||
|
MockMultipartFile file = new MockMultipartFile("file", "11.xmind", MediaType.APPLICATION_OCTET_STREAM_VALUE, FileBaseUtils.getFileBytes(filePath));
|
||||||
|
FunctionalCaseImportRequest request = new FunctionalCaseImportRequest();
|
||||||
|
request.setCover(true);
|
||||||
|
request.setProjectId("100001100001");
|
||||||
|
request.setCount("1");
|
||||||
|
LinkedMultiValueMap<String, Object> paramMap = new LinkedMultiValueMap<>();
|
||||||
|
paramMap.add("request", JSON.toJSONString(request));
|
||||||
|
paramMap.add("file", file);
|
||||||
|
MvcResult functionalCaseMvcResult = this.requestMultipartWithOkAndReturn(IMPORT_XMIND_URL, paramMap);
|
||||||
|
|
||||||
|
String functionalCaseImportResponseData = functionalCaseMvcResult.getResponse().getContentAsString(StandardCharsets.UTF_8);
|
||||||
|
ResultHolder functionalCaseResultHolder = JSON.parseObject(functionalCaseImportResponseData, ResultHolder.class);
|
||||||
|
FunctionalCaseImportResponse functionalCaseImportResponse = JSON.parseObject(JSON.toJSONString(functionalCaseResultHolder.getData()), FunctionalCaseImportResponse.class);
|
||||||
|
Assertions.assertNotNull(functionalCaseImportResponse);
|
||||||
|
System.out.println(JSON.toJSONString(functionalCaseImportResponse));
|
||||||
|
|
||||||
|
FunctionalCaseExample functionalCaseExample = new FunctionalCaseExample();
|
||||||
|
functionalCaseExample.createCriteria().andNameEqualTo("用例名称");
|
||||||
|
List<FunctionalCase> functionalCases = functionalCaseMapper.selectByExample(functionalCaseExample);
|
||||||
|
System.out.println(JSON.toJSONString(functionalCases));
|
||||||
|
|
||||||
|
String filePath5 = Objects.requireNonNull(this.getClass().getClassLoader().getResource("file/2module.xmind")).getPath();
|
||||||
|
MockMultipartFile file5 = new MockMultipartFile("file", "15.xmind", MediaType.APPLICATION_OCTET_STREAM_VALUE, FileBaseUtils.getFileBytes(filePath5));
|
||||||
|
paramMap = new LinkedMultiValueMap<>();
|
||||||
|
paramMap.add("request", JSON.toJSONString(request));
|
||||||
|
paramMap.add("file", file5);
|
||||||
|
functionalCaseMvcResult = this.requestMultipartWithOkAndReturn(IMPORT_XMIND_URL, paramMap);
|
||||||
|
functionalCaseImportResponseData = functionalCaseMvcResult.getResponse().getContentAsString(StandardCharsets.UTF_8);
|
||||||
|
functionalCaseResultHolder = JSON.parseObject(functionalCaseImportResponseData, ResultHolder.class);
|
||||||
|
functionalCaseImportResponse = JSON.parseObject(JSON.toJSONString(functionalCaseResultHolder.getData()), FunctionalCaseImportResponse.class);
|
||||||
|
Assertions.assertNotNull(functionalCaseImportResponse);
|
||||||
|
System.out.println(JSON.toJSONString(functionalCaseImportResponse));
|
||||||
|
functionalCaseExample = new FunctionalCaseExample();
|
||||||
|
functionalCaseExample.createCriteria().andNameEqualTo("用例名称");
|
||||||
|
functionalCases = functionalCaseMapper.selectByExample(functionalCaseExample);
|
||||||
|
System.out.println(JSON.toJSONString(functionalCases));
|
||||||
|
|
||||||
|
String filePath1 = Objects.requireNonNull(this.getClass().getClassLoader().getResource("file/3erro.xmind")).getPath();
|
||||||
|
MockMultipartFile file1 = new MockMultipartFile("file", "14.xmind", MediaType.APPLICATION_OCTET_STREAM_VALUE, FileBaseUtils.getFileBytes(filePath1));
|
||||||
|
paramMap = new LinkedMultiValueMap<>();
|
||||||
|
paramMap.add("request", JSON.toJSONString(request));
|
||||||
|
paramMap.add("file", file1);
|
||||||
|
functionalCaseMvcResult = this.requestMultipartWithOkAndReturn(IMPORT_XMIND_URL, paramMap);
|
||||||
|
functionalCaseImportResponseData = functionalCaseMvcResult.getResponse().getContentAsString(StandardCharsets.UTF_8);
|
||||||
|
functionalCaseResultHolder = JSON.parseObject(functionalCaseImportResponseData, ResultHolder.class);
|
||||||
|
functionalCaseImportResponse = JSON.parseObject(JSON.toJSONString(functionalCaseResultHolder.getData()), FunctionalCaseImportResponse.class);
|
||||||
|
Assertions.assertNotNull(functionalCaseImportResponse);
|
||||||
|
System.out.println(JSON.toJSONString(functionalCaseImportResponse));
|
||||||
|
|
||||||
|
String filePath4 = Objects.requireNonNull(this.getClass().getClassLoader().getResource("file/empty.xmind")).getPath();
|
||||||
|
MockMultipartFile file2 = new MockMultipartFile("file", "18.xmind", MediaType.APPLICATION_OCTET_STREAM_VALUE, FileBaseUtils.getFileBytes(filePath4));
|
||||||
|
paramMap = new LinkedMultiValueMap<>();
|
||||||
|
paramMap.add("request", JSON.toJSONString(request));
|
||||||
|
paramMap.add("file", file2);
|
||||||
|
functionalCaseMvcResult = this.requestMultipartWithOkAndReturn(IMPORT_XMIND_URL, paramMap);
|
||||||
|
functionalCaseImportResponseData = functionalCaseMvcResult.getResponse().getContentAsString(StandardCharsets.UTF_8);
|
||||||
|
functionalCaseResultHolder = JSON.parseObject(functionalCaseImportResponseData, ResultHolder.class);
|
||||||
|
functionalCaseImportResponse = JSON.parseObject(JSON.toJSONString(functionalCaseResultHolder.getData()), FunctionalCaseImportResponse.class);
|
||||||
|
Assertions.assertNotNull(functionalCaseImportResponse);
|
||||||
|
System.out.println(JSON.toJSONString(functionalCaseImportResponse));
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@Order(20)
|
||||||
|
public void testPreCheckImportXmind() throws Exception {
|
||||||
|
FunctionalCaseImportRequest request = new FunctionalCaseImportRequest();
|
||||||
|
request.setCover(true);
|
||||||
|
request.setProjectId("100001100001");
|
||||||
|
request.setCount("1");
|
||||||
|
LinkedMultiValueMap<String, Object> paramMap = new LinkedMultiValueMap<>();
|
||||||
|
|
||||||
|
String filePath = Objects.requireNonNull(this.getClass().getClassLoader().getResource("file/1xml.xmind")).getPath();
|
||||||
|
MockMultipartFile file = new MockMultipartFile("file", "11.xmind", MediaType.APPLICATION_OCTET_STREAM_VALUE, FileBaseUtils.getFileBytes(filePath));
|
||||||
|
paramMap.add("request", JSON.toJSONString(request));
|
||||||
|
paramMap.add("file", file);
|
||||||
|
MvcResult functionalCaseMvcResult = this.requestMultipartWithOkAndReturn(CHECK_XMIND_URL, paramMap);
|
||||||
|
|
||||||
|
String functionalCaseImportResponseData = functionalCaseMvcResult.getResponse().getContentAsString(StandardCharsets.UTF_8);
|
||||||
|
ResultHolder functionalCaseResultHolder = JSON.parseObject(functionalCaseImportResponseData, ResultHolder.class);
|
||||||
|
FunctionalCaseImportResponse functionalCaseImportResponse = JSON.parseObject(JSON.toJSONString(functionalCaseResultHolder.getData()), FunctionalCaseImportResponse.class);
|
||||||
|
Assertions.assertNotNull(functionalCaseImportResponse);
|
||||||
|
System.out.println(JSON.toJSONString(functionalCaseImportResponse));
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
String filePath5 = Objects.requireNonNull(this.getClass().getClassLoader().getResource("file/2module.xmind")).getPath();
|
||||||
|
MockMultipartFile file5 = new MockMultipartFile("file", "15.xmind", MediaType.APPLICATION_OCTET_STREAM_VALUE, FileBaseUtils.getFileBytes(filePath5));
|
||||||
|
paramMap = new LinkedMultiValueMap<>();
|
||||||
|
paramMap.add("request", JSON.toJSONString(request));
|
||||||
|
paramMap.add("file", file5);
|
||||||
|
functionalCaseMvcResult = this.requestMultipartWithOkAndReturn(CHECK_XMIND_URL, paramMap);
|
||||||
|
functionalCaseImportResponseData = functionalCaseMvcResult.getResponse().getContentAsString(StandardCharsets.UTF_8);
|
||||||
|
functionalCaseResultHolder = JSON.parseObject(functionalCaseImportResponseData, ResultHolder.class);
|
||||||
|
functionalCaseImportResponse = JSON.parseObject(JSON.toJSONString(functionalCaseResultHolder.getData()), FunctionalCaseImportResponse.class);
|
||||||
|
Assertions.assertNotNull(functionalCaseImportResponse);
|
||||||
|
System.out.println(JSON.toJSONString(functionalCaseImportResponse));
|
||||||
|
|
||||||
|
|
||||||
|
String filePath1 = Objects.requireNonNull(this.getClass().getClassLoader().getResource("file/3erro.xmind")).getPath();
|
||||||
|
MockMultipartFile file1 = new MockMultipartFile("file", "14.xmind", MediaType.APPLICATION_OCTET_STREAM_VALUE, FileBaseUtils.getFileBytes(filePath1));
|
||||||
|
paramMap = new LinkedMultiValueMap<>();
|
||||||
|
paramMap.add("request", JSON.toJSONString(request));
|
||||||
|
paramMap.add("file", file1);
|
||||||
|
functionalCaseMvcResult = this.requestMultipartWithOkAndReturn(CHECK_XMIND_URL, paramMap);
|
||||||
|
functionalCaseImportResponseData = functionalCaseMvcResult.getResponse().getContentAsString(StandardCharsets.UTF_8);
|
||||||
|
functionalCaseResultHolder = JSON.parseObject(functionalCaseImportResponseData, ResultHolder.class);
|
||||||
|
functionalCaseImportResponse = JSON.parseObject(JSON.toJSONString(functionalCaseResultHolder.getData()), FunctionalCaseImportResponse.class);
|
||||||
|
Assertions.assertNotNull(functionalCaseImportResponse);
|
||||||
|
System.out.println(JSON.toJSONString(functionalCaseImportResponse.getErrorMessages()));
|
||||||
|
|
||||||
|
String filePath4 = Objects.requireNonNull(this.getClass().getClassLoader().getResource("file/empty.xmind")).getPath();
|
||||||
|
MockMultipartFile file2 = new MockMultipartFile("file", "18.xmind", MediaType.APPLICATION_OCTET_STREAM_VALUE, FileBaseUtils.getFileBytes(filePath4));
|
||||||
|
paramMap = new LinkedMultiValueMap<>();
|
||||||
|
paramMap.add("request", JSON.toJSONString(request));
|
||||||
|
paramMap.add("file", file2);
|
||||||
|
functionalCaseMvcResult = this.requestMultipartWithOkAndReturn(CHECK_XMIND_URL, paramMap);
|
||||||
|
functionalCaseImportResponseData = functionalCaseMvcResult.getResponse().getContentAsString(StandardCharsets.UTF_8);
|
||||||
|
functionalCaseResultHolder = JSON.parseObject(functionalCaseImportResponseData, ResultHolder.class);
|
||||||
|
functionalCaseImportResponse = JSON.parseObject(JSON.toJSONString(functionalCaseResultHolder.getData()), FunctionalCaseImportResponse.class);
|
||||||
|
Assertions.assertNotNull(functionalCaseImportResponse);
|
||||||
|
System.out.println(JSON.toJSONString(functionalCaseImportResponse));
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@Order(22)
|
@Order(22)
|
||||||
public void operationHistoryList() throws Exception {
|
public void operationHistoryList() throws Exception {
|
||||||
|
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
10
pom.xml
10
pom.xml
|
@ -85,6 +85,16 @@
|
||||||
<commons-jexl3.version>3.3</commons-jexl3.version>
|
<commons-jexl3.version>3.3</commons-jexl3.version>
|
||||||
<revision>3.x</revision>
|
<revision>3.x</revision>
|
||||||
<monitoring-engine.revision>3.0</monitoring-engine.revision>
|
<monitoring-engine.revision>3.0</monitoring-engine.revision>
|
||||||
|
|
||||||
|
<!-- 用于解析xml -->
|
||||||
|
<jaxb-api.revision>2.4.0-b180830.0359</jaxb-api.revision>
|
||||||
|
<jaxb-impl.revision>2.3.0</jaxb-impl.revision>
|
||||||
|
<jaxb-core.revision>2.3.0</jaxb-core.revision>
|
||||||
|
<activation.revision>1.1.1</activation.revision>
|
||||||
|
<jaxen.revision>1.2.0</jaxen.revision>
|
||||||
|
<jackson-dataformat-xml.version>2.17.2</jackson-dataformat-xml.version>
|
||||||
|
<jackson-core.version>2.17.2</jackson-core.version>
|
||||||
|
|
||||||
</properties>
|
</properties>
|
||||||
|
|
||||||
<modules>
|
<modules>
|
||||||
|
|
Loading…
Reference in New Issue