提出问题
通过对之前java解析excel的研究,发现无法满足不了某些复杂格式excel的解析,例如
该格式的excel用之前的方法就无法解析,那么针对这种复杂格式的excel我们应如何解析呢?
分析问题
我们通过debug测试研究该复杂格式row的关系,发现在虽然像用户名、密码、角色合并了前四行。但是在解析的时候他们是默认被放在第一行的,像wk25也是默认在第一行,但是像周一,周二,你可能会觉得它应该是默认在第二行吧,但是并不是,因为wk25我们是合并了前两行的结果,所以周一,周二是默认在第三行。说到这里你应该懂了他的规则是什么了,因此我们用过对配置文件的完善和部分解析代码的修改来解决这个问题。
解决问题
首先我们来看一下配置文件作出了哪些修改
图片中有详细的注释我们在这里就不一一解释了,只是将普通版resolveEntity标签就是对应的普通版中的entity标签,这里换名字是为了区别是用来解析还是用来导出的
下面我们依然是直接看代码,代码中有详细的注释
/**
* 解析excel
*
* @param multipartFile 文件流
* @param savePath 文件在项目中存放的路径
* @param t 指返回的集合中的entity的类型
* @param jsonResult 结果信息
* @return 解析集合
*/
private List<T> resolveExcel(MultipartFile multipartFile, String savePath, T t, JsonResult jsonResult) throws Exception {
List<T> list = new ArrayList<>();
//1.创建Reader对象
SAXReader reader = new SAXReader();
//2.加载xml
Document document=reader.read(Objects.requireNonNull(ExcelUtils.class.getClassLoader().getResource("ExcelUtils.xml")).getPath());
//获得根元素,entitys
Element rootElement = document.getRootElement();
//获得根元素的子元素,entity
List<Element> elements = rootElement.elements();
Class classes = t.getClass();
for (Element element : elements) {
if ("resolveEntity".equals(element.getName()) && classes.getSimpleName().equals(element.attributeValue("value"))) {
//获得entity的子元素 column
List<Element> elementList = element.elements();
//创建本地测试文件
File file = new File("E:\\用户.xls");
//下面注释的为如何使用实际项目中接收的MultipartFile 文件来使用该Utils
// String[] split = multipartFile.getName().split("\\.");
String[] split = file.getName().split("\\.");
Workbook wb = null;
if ("xls".equals(split[1])) {
//下面注释的为如何使用实际项目中接收的MultipartFile 文件来使用该Utils
// InputStream fis = multipartFile.getInputStream();
InputStream fis = new FileInputStream(file);
wb = new HSSFWorkbook(fis);
} else if ("xlsx".endsWith(file.getName())) {
//下面注释的为如何使用实际项目中接收的MultipartFile 文件来使用该Utils
// wb = new XSSFWorkbook((File) multipartFile);
wb = new XSSFWorkbook(file);
} else {
jsonResult.setFailReason("文件格式错误");
}
Sheet sheet = wb.getSheetAt(0);
//这里的第一行是我们在resolveEntity标签中配置的startRow
int fistRowNumber = Integer.parseInt(element.attributeValue("startRow"));
//得到最后一行
int lastRowNumber = sheet.getLastRowNum();
//遍历所有行
for (int rIndex = fistRowNumber; rIndex <= lastRowNumber; rIndex++) {
//实例化一个entity用来存储遍历excel得到的数据
t = (T) classes.newInstance();
//获得行
Row row = sheet.getRow(rIndex);
int rowCount = 0;
//创建map用来暂存数据,赋给entity中的map字段
Map<String, String> map = new HashMap<>();
Field mapFiled = null;
if (row != null) {
//默认从第一列开始
int cel = 0;
while (true) {
//判断第几行第几列的数据是否为空
if (row.getCell(cel) != null) {
//遍历xml中的column中的标签
for (Element element1 : elementList) {
//如果对应的cel和当前正在解析的列 相等,并且行数大于等于我们在配置文件中column标签中配置的startRow,即当前行数大于等于该字段开始赋值的开始行数时
if ("column".equals(element1.getName()) && Integer.parseInt(element1.attributeValue("cel")) - 1 == cel && Integer.parseInt(element1.attributeValue("startRow"))-1 <= rIndex) {
//得到这个类的全部属性
Field[] fields = classes.getDeclaredFields();
//遍历属性
for (Field field : fields) {
//如果cel对应的value存在该entity中 就赋值
if (element1.attributeValue("value").equals(field.getName()) ) {
field.setAccessible(true);
//将值赋到entity中,如果是日期类型得到对应的日期格式
conversionType(field,row.getCell(cel).toString(),t,element.attributeValue("dateForMate"));
rowCount++;
}
}
//得到传入entity的父类
Class clazz = classes.getSuperclass();
//如果有父类
while(clazz != null){
//得到父类的所有属性
Field[] supFiles = clazz.getDeclaredFields();
for (Field field : supFiles) {
//如果cel对应的value存在该entity中 就赋值
if (element1.attributeValue("value").equals(field.getName())) {
field.setAccessible(true);
//将值赋到entity中
conversionType(field,row.getCell(cel).toString(),t, element.attributeValue("dateForMate"));
rowCount++;
break;
}
}
//继续得到父类
clazz = clazz.getSuperclass();
}
//如果当前标签是map标签
} else if ("map".equals(element1.getName())) {
//得到entity中对应的属性名称
String value = element1.attributeValue("name");
//得到map标签的字标签
List<Element> mapElements = element1.elements();
//通过反射得到该属性
mapFiled = classes.getDeclaredField(value);
//如果有子标签
if(mapElements.size() > 0){
for (Element element2 : mapElements) {
if(element2.attributeValue("cel")!=null&&element2.attributeValue("startRow")!=null){
//判断当前列是否和配置的列相同并且当前行数是否大于等于配置的开始行数
if("column".equals(element2.getName())&&Integer.parseInt(element2.attributeValue("cel")) - 1 == cel && Integer.parseInt(element2.attributeValue("startRow"))-1 <= rIndex) {
//符合条件,将值建立在map中
map.put(element2.attributeValue("value"), row.getCell(cel).toString());
rowCount++;
}
}
}
}
}
}
//列数加一
cel++;
//如果当前列为空并且已经大于最后一列,并且rowCount > 0代表有map的赋值
} else if (cel >= row.getLastCellNum() && rowCount > 0) {
if(map.size() > 0 && mapFiled != null){
mapFiled.setAccessible(true);
mapFiled.set(t, map);
}
//将entity加到list中
list.add(t);
break;
//如果当前列为空并且已经大于最后一列,并且rowCount = 0 没有map的赋值
}else if(cel >= row.getLastCellNum() && rowCount == 0){
break;
}
}
}
}
}
}
return list;
}
/**
* 判断entity属性是什么类型(只能判断基本类型)
*
* @param field 当前属性
* @param value 当前属性的值
* @param t 所在的entity
* @param dateFormat 如果有日期类型所对应的日期格式
*/
private void conversionType(Field field, String value, T t, String dateFormat) throws Exception {
//或得到该属性的类型
Type genericType = field.getGenericType();
if (genericType.toString().endsWith("String")) {
field.set(t, value);
} else if (genericType.toString().endsWith("int") || "java.lang.Integer".equals(genericType.getTypeName())) {
field.set(t, Integer.parseInt(value));
} else if ("Long".endsWith(genericType.toString()) || "long".endsWith(genericType.toString())) {
field.set(t, new Long(value));
} else if ("char".endsWith(genericType.toString())) {
field.set(t, value.charAt(0));
} else if ("boolean".endsWith(genericType.toString()) || "java.lang.Boolean".equals(genericType.getTypeName())) {
field.set(t, Boolean.valueOf(value));
} else if ("double".endsWith(genericType.toString()) || "java.lang.Double".equals(genericType.getTypeName())) {
field.set(t, Double.valueOf(value));
} else if ("float".endsWith(genericType.toString()) || "java.lang.Float".equals(genericType.getTypeName())) {
field.set(t, Float.valueOf(value));
} else if ("java.util.Date".equals(genericType.getTypeName())) {
field.set(t, new SimpleDateFormat(dateFormat).parse(value));
}
}
接下来我们对之前提出的excel来进行解析一下
下面是测试代码
下面是我们控制台的输出结果
在这里我们只配置了周一和周二进行测试,大家可以都配置一下进行测试
总结
1、修改entity标签为resolveEntity
2、增加map标签来映射entity中的map类型字段
3、增加标签属性startRow来判断要从第几行开始解析和赋值
通过以上解答相信大家已经清楚的明白了面对复杂的excel我们如何用一个通用的utils去解析它,当然可能还是存在某些复杂excel无法解析,小编发现之后也会持续研究和更新,如果该文没有看懂,请先阅读普通版然后再回来看进阶版就会清楚明了,下面我们贴出普通版的链接,