1 前言
最近碰到一个需求,需要读取excel,本以为使用POI可以轻松搞定了,但是使用后发现,WPS和OFFICE的excel有点不太一样,WPS的内嵌图片使用POI居然读不到!!!很苦逼,网上找了一下,发现有人问,但是回答较少,于是我就搁置了。在前天忽然在网上搜到一篇解答,可惜并没有开源,本着能动手绝不花钱的思想,根据博主的思路自己撸了一个工具类。PS:内部需求根据我的需求做的,但是也能用。
PS :代码解决了使用POI读取不到WPS内嵌图片的问题。但是目前只兼容了XLSX文件,XLS文件目前还在积极解决中!!!!!!
2 问题分析
2.1 问题场景
使用OFFICE的Excel插入图片的时候只能使用的是浮动式图片,如图:
这种图片可以通过POI直接读取图片列表
但是使用WPS插入图片的时候有两种方式,如图:
内嵌图片会直接嵌入在单元格里面,使用的是WPS的内置函数展示的,这种图片直接使用POI是获取不到的,如图:
如果使用OFFICE的话就没办法读取这个图片会显示错误,如图:
2.2 解决思路
不卖关子,直接上干货
首先我们看到嵌入式图片在内容框里面是有一个函数的,并且这个函数中包含了一个ID,例:=DISPIMG("ID_9D6E8C240C8945178DFF238232B217BF",1),如图:
也就是说我们可以通过这个ID来找到对应的图片,接下来我们把这个文件后缀名修改为zip,并解压查看,如图:
内部结构是这个样子的,我们要找的东西大多在“xl”这个目录下
打开xl目录,内容如下:
目录结构很简单,首先有一个media文件夹,这个文件夹里面存放的是Excel中所有使用到的图片,包含浮动式和嵌入式图片,如图:
知道浮动式图片和嵌入式图片的存放位置一样后,大概可以推测出来他们俩种的区别应该不是很大,所以只需要找到嵌入式图片的和ID的映射关系应该就可以解决这个问题了
接下来我们打开另一个文件,xl文件夹下面的cellimage.xml文件,如图:
内容如下:
在一个<xdr:pic>标签中出现两个关键点,name="ID_9D6E8C240C8945178DFF238232B217BF"和 r:embed="rId1",其中name="ID_9D6E8C240C8945178DFF238232B217BF",而这个ID正是我们单元格公式中的ID,如图:
有了第一段映射关系,接下来需要找到第二段映射关系,也就是rId1和图片的映射关系,接下来我们打开xl目录下的_res目录的cellimage.xml.rels文件,如图
内容如下:
这里的一个<Relationship>标签下出现了一个Id="rId1"和一个Target="media/image3.jpeg",我们在去到media文件夹下,发现这个image3.jpeg就是嵌入式图片的名称
也就是说我们现在有了完整的映射关系,已经可以从单元格中的ID获取到图片的文件路径了,关系如下:
单元格中的=DISPIMG("ID_9D6E8C240C8945178DFF238232B217BF",1) ===> cellimage.xml中的rid ===> cellimage.xml.rels中的Target="media/image3.jpeg"
3 问题解决
3.1 前置准备
有了上述的映射关系,我们已经可以去进行代码实现了,我们只需要在读取内容时读取到DISPIMG这个函数时,获取到该函数的ID,再通过ID的映射关系去找到对应图片的位置就OK了
话不多说,开干!!!
首先我们需要映入的依赖 PS:依赖可能有没用到的,可以自行删一下
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-compress</artifactId>
<version>1.19</version>
</dependency>
<dependency>
<groupId>com.github.junrar</groupId>
<artifactId>junrar</artifactId>
<version>3.0.0</version>
</dependency>
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi</artifactId>
<version>4.1.2</version>
</dependency>
<!-- Apache POI - OOXML (for .docx, .pptx, .xlsx) -->
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi-ooxml</artifactId>
<version>4.1.2</version>
</dependency>
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi-ooxml-schemas</artifactId>
<version>4.1.2</version>
</dependency>
<dependency>
<groupId>dom4j</groupId>
<artifactId>dom4j</artifactId>
<version>1.6.1</version>
</dependency>
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.5.6</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.72</version>
</dependency>
3.2 代码实现
我的工具类中只有三个类,main是一个测试类 如图:
3.2.1 ExcelDataEntry
这个类就是一个最单元格内容的实体类,由于业务需求有一个dataType属性用来存放数据的类型
package test;
import java.io.File;
/**
* @className: ExcelDataEntry
* @description: [描述说明该类的功能]
* @author: [姓名]
* @version: [v1.0]
* @createTime: [2023/9/20 14:02]
* @updateUser: [姓名]
* @updateTime: [2023/9/20 14:02]
* @updateRemark: [描述说明本次修改内容]
*/
public class ExcelDataEntry {
private Integer row;
private Integer col;
private Object data;
private String dataType;
public Integer getRow() {
return row;
}
public void setRow(Integer row) {
this.row = row;
}
public Integer getCol() {
return col;
}
public void setCol(Integer col) {
this.col = col;
}
public Object getData() {
return data;
}
public void setData(Object data) {
this.data = data;
if (data instanceof String){
setDataType("String");
}else if (data instanceof Double){
setDataType("Double");
}else if (data instanceof Integer){
setDataType("Integer");
}else if (data instanceof File){
setDataType("File");
}else if (data instanceof Boolean){
setDataType("Boolean");
}else {
setDataType("String");
}
}
public String getDataType() {
return dataType;
}
public void setDataType(String dataType) {
this.dataType = dataType;
}
@Override
public String toString() {
return "ExcelDataEntry{" +
"row=" + row +
", col=" + col +
", data=" + data +
", dataType='" + dataType + '\'' +
'}';
}
}
3.2.2 ExcelUtil
这个类相当于是一个构造类吧,名称起的有点随意
package test;
import cn.hutool.core.collection.CollectionUtil;
import com.alibaba.fastjson.JSONObject;
import org.apache.poi.hssf.usermodel.HSSFWorkbook;
import org.apache.poi.ss.usermodel.Cell;
import org.apache.poi.ss.usermodel.Row;
import org.apache.poi.ss.usermodel.Sheet;
import org.apache.poi.ss.usermodel.Workbook;
import org.apache.poi.xssf.usermodel.XSSFPictureData;
import org.apache.poi.xssf.usermodel.XSSFSheet;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import java.io.File;
import java.io.FileInputStream;
import java.io.InputStream;
import java.util.*;
import java.util.stream.Collectors;
/**
* @className: ExcelUtil
* @description: [描述说明该类的功能]
* @author: [姓名]
* @version: [v1.0]
* @createTime: [2023/9/20 15:50]
* @updateUser: [姓名]
* @updateTime: [2023/9/20 15:50]
* @updateRemark: [描述说明本次修改内容]
*/
public class ExcelUtil {
private List<ExcelSheet> sheetList=new ArrayList<>();
private String rootPath = "";
private String imageTargetPath = "";
private Workbook workbook = null;
public ExcelUtil(String filePath,String imageTargetPath ){
this.rootPath=filePath;
this.imageTargetPath=imageTargetPath;
readExcel(filePath);
}
private void readExcel(String path) {
try {
this.rootPath = path;
// 获取文件输入流
InputStream inputStream = new FileInputStream(path);
// 定义一个org.apache.poi.ss.usermodel.Workbook的变量
// 截取路径名 . 后面的后缀名,判断是xls还是xlsx
String fileType = path.substring(path.lastIndexOf(".") + 1);
if (fileType.equals("xls")) {
workbook = new HSSFWorkbook(inputStream);
} else if (fileType.equals("xlsx")) {
workbook = new XSSFWorkbook(inputStream);
}
} catch (Exception e) {
e.printStackTrace();
}
}
public ExcelSheet getSheet(int index,Integer titleRow){
Sheet sheet = workbook.getSheetAt(index);
return new ExcelSheet(sheet,titleRow,rootPath,imageTargetPath);
}
}
3.2.3 ExcelSheet
这个类就是读取文件的类了,我在这个类里面做了全部的处理,代码注释也不是很多,如果有什么问题的话可以在评论区里面留言
package test;
import cn.hutool.core.collection.CollectionUtil;
import org.apache.poi.hssf.usermodel.*;
import org.apache.poi.ooxml.POIXMLDocumentPart;
import org.apache.poi.ss.usermodel.Cell;
import org.apache.poi.ss.usermodel.CellType;
import org.apache.poi.ss.usermodel.Row;
import org.apache.poi.ss.usermodel.Sheet;
import org.apache.poi.xssf.usermodel.*;
import org.dom4j.Document;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;
import org.openxmlformats.schemas.drawingml.x2006.spreadsheetDrawing.CTMarker;
import org.springframework.util.CollectionUtils;
import java.io.*;
import java.util.*;
import java.util.stream.Collectors;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
import java.util.zip.ZipInputStream;
/**
* @className: Sheet
* @description: [描述说明该类的功能]
* @author: [姓名]
* @version: [v1.0]
* @createTime: [2023/9/20 15:50]
* @updateUser: [姓名]
* @updateTime: [2023/9/20 15:50]
* @updateRemark: [描述说明本次修改内容]
*/
public class ExcelSheet {
/**
* key :Rid value ImagePath
*/
private Map<String, String> imageMap = new HashMap<>();
/**
* key imageId value Rid
*/
private Map<String, String> imageIdMappingMap = new HashMap<>();
private Sheet sheet;
/**
* 标题map key:colIndex ,value: ExcelDataEntry
*/
private Map<Integer, ExcelDataEntry> sheetTitleMap = new HashMap<>();
/**
* 标题所在行
*/
private Integer titleRow;
/**
* 文件跟目录
*/
private String rootPath = "";
/**
* 图片存储目录
*/
private String imageTargetPath = "";
/**
* 行数据
*/
private List<Map<Integer, ExcelDataEntry>> rowDataList;
/**
* title 数据 key:title data colDataList
*/
private Map<Object, List<ExcelDataEntry>> titleDataListMap;
/**
* 自定义ExcelSheet构造方法
*
* @param sheet 真实sheet
* @param rootPath 带解析Excel文件地址
* @param imageTargetPath 图片存放地址
*/
protected ExcelSheet(Sheet sheet, Integer titleRow, String rootPath, String imageTargetPath) {
this.sheet = sheet;
this.rootPath = rootPath;
this.imageTargetPath = imageTargetPath;
this.titleRow = titleRow;
//加载数据
loadData();
loadSheetDataTitle(this.titleRow);
}
/**
* @param null
* @return [test.ExcelSheet]
* @version [V1.0]
* @Title: [loadData]
* @author [小鹿同学]
* @description [加载sheet数据 最终生成rowDataList保存至参数]
* @createTime [2023/9/20 17:46]
* @updateUser: [小鹿同学]
* @updateTime: [2023/9/20 17:46]
* @updateRemark: [描述说明本次修改内容]
*/
private void loadData() {
try {
//数据结果集
List<Map<Integer, ExcelDataEntry>> rowDataList = new ArrayList<>();
ridWithIDRelationShip(this.rootPath);
ridWithImagePathRelationShip(this.rootPath);
Map<String, XSSFPictureData> floatPictureMap = getFloatPictureMap((XSSFSheet) sheet);
Map<Integer, Integer> rowPicMaxColIndexMap = getRowPicMaxColIndex(floatPictureMap);
List<Integer> picRowList = rowPicMaxColIndexMap.keySet().stream().sorted(Comparator.reverseOrder()).collect(Collectors.toList());
// sheet.getPhysicalNumberOfRows()获取总的行数
// 循环读取每一行
int rowIndex = 0;
for (; ; ) {
HashMap<Integer, ExcelDataEntry> rowDataItem = new HashMap<>();
if (rowIndex >= sheet.getPhysicalNumberOfRows() && (CollectionUtil.isEmpty(picRowList) || rowIndex > picRowList.get(0))) {
break;
}
Row row = sheet.getRow(rowIndex);
// row.getPhysicalNumberOfCells()获取总的列数
int colIndex = 0;
Integer rowPicMaxColIndex = rowPicMaxColIndexMap.get(rowIndex);
int rowPhysicalNumberOfCells = 0;
if (row != null) {
rowPhysicalNumberOfCells = row.getPhysicalNumberOfCells();
}
// 循环读取每一个格
for (; ; ) {
if (colIndex > rowPhysicalNumberOfCells && (rowPicMaxColIndex == null || colIndex > rowPicMaxColIndex)) {
break;
}
ExcelDataEntry colDataItem = new ExcelDataEntry();
colDataItem.setRow(rowIndex);
colDataItem.setCol(colIndex);
String RCindex = rowIndex + "-" + colIndex;
if (rowPhysicalNumberOfCells != 0) {
// 获取数据,但是我们获取的cell类型
Cell cell = row.getCell(colIndex, Row.MissingCellPolicy.CREATE_NULL_AS_BLANK);
if (cell == null) {
continue;
}
//获取数据类型
String dataType = getCellDataType(cell);
// 转换为字符串类型
Object currentData = getCellData(cell);
// 获取得到字符串
// String currentData = cell.getStringCellValue();
if ("FORMULA".equals(dataType)) {
if (currentData.toString().contains("DISPIMG")) {
String imageId = subImageId(currentData.toString());
String picPath = getImplantPicById(imageId);
InputStream picInputStream = openFile("xl/" + picPath);
File pic = saveFile(picInputStream, this.imageTargetPath + "/" + getFileRealName(picPath));
colDataItem.setData(pic);
}
} else if (currentData != null) {
colDataItem.setData(currentData);
}
}
XSSFPictureData floatPicture = floatPictureMap.get(RCindex);
if (floatPicture != null) {
InputStream picInputStream = floatPicture.getPackagePart().getInputStream();
File pic = saveFile(picInputStream, this.imageTargetPath + "/" + getFileRealName(floatPicture.getPackagePart().getPartName().toString()));
colDataItem.setData(pic);
}
if (colDataItem.getData() != null) {
rowDataItem.put(colIndex, colDataItem);
}
colIndex++;
}
// if (CollectionUtil.isNotEmpty(rowDataItem)) {
rowDataList.add(rowDataItem);
// }
rowIndex++;
}
this.rowDataList = rowDataList;
} catch (Exception e) {
e.printStackTrace();
}
}
private Object getCellData(Cell cell) {
CellType cellType = cell.getCellType();
Object value = null;
switch (cellType) {
case STRING:
value = cell.getStringCellValue();
break;
case NUMERIC:
value = cell.getNumericCellValue();
break;
case BOOLEAN:
value = cell.getBooleanCellValue();
break;
case FORMULA:
value = cell.getCellFormula();
break;
default:
break;
}
return value;
}
private String getCellDataType(Cell cell) {
CellType cellType = cell.getCellType();
String type = null;
switch (cellType) {
case STRING:
type = "STRING";
break;
case NUMERIC:
type = "NUMERIC";
break;
case BOOLEAN:
type = "BOOLEAN";
break;
case FORMULA:
type = "FORMULA";
break;
default:
type = "STRING";
}
return type;
}
private String getFileRealName(String fileName) {
try {
fileName.replace("\\", "/");
fileName = fileName.substring(fileName.lastIndexOf("/") + 1);
} catch (Exception e) {
return fileName;
}
return fileName;
}
/**
* @param null
* @return [test.ExcelSheet]
* @version [V1.0]
* @Title: [pictureWithIDRelationShip]
* @author [小鹿同学]
* @description [整理图片函数中的ID和Rid的关系]
* @createTime [2023/9/20 17:49]
* @updateUser: [小鹿同学]
* @updateTime: [2023/9/20 17:49]
* @updateRemark: [描述说明本次修改内容]
*/
private void ridWithIDRelationShip(String path) throws IOException {
InputStream inputStream = null;
try {
//读取关系xml文件
inputStream = openFile("xl/cellimages.xml");
// 创建SAXReader对象
SAXReader reader = new SAXReader();
// 加载xml文件
Document dc = reader.read(inputStream);
// 获取根节点
Element rootElement = dc.getRootElement();
//获取子节点 每一个图片节点
List<Element> cellImageList = rootElement.elements();
//循环处理每一个图片
for (Element cellImage : cellImageList) {
Element pic = cellImage.element("pic");
Element nvPicPr = pic.element("nvPicPr");
Element cNvPr = nvPicPr.element("cNvPr");
//图片id
String imageId = cNvPr.attribute("name").getValue().replace("ID_", "");
// imageId = subImageId(imageId);
Element blipFill = pic.element("blipFill");
Element blip = blipFill.element("blip");
//图片Rid
String imageRid = blip.attribute("embed").getValue();
//存入map中
imageIdMappingMap.put(imageId, imageRid);
}
} catch (Exception e) {
e.printStackTrace();
} finally {
if (inputStream != null) {
inputStream.close();
}
}
}
private InputStream openFile(String filePath) {
try {
File file = new File(rootPath);
ZipFile zipFile = new ZipFile(file);
ZipInputStream zipInputStream = new ZipInputStream(new FileInputStream(file));
ZipEntry nextEntry = null;
while ((nextEntry = zipInputStream.getNextEntry()) != null) {
String name = nextEntry.getName();
if (name.equalsIgnoreCase(filePath)) {
return zipFile.getInputStream(nextEntry);
}
}
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
/**
* @param null
* @return [test.ExcelSheet]
* @version [V1.0]
* @Title: [ridWithImagePathRelationShip]
* @author [小鹿同学]
* @description [整理Rid和图片地址的关系]
* @createTime [2023/9/20 17:53]
* @updateUser: [小鹿同学]
* @updateTime: [2023/9/20 17:53]
* @updateRemark: [描述说明本次修改内容]
*/
private void ridWithImagePathRelationShip(String path) throws IOException {
InputStream inputStream = null;
try {
//读取关系文件
inputStream = openFile("xl/_rels/cellimages.xml.rels");
// 创建SAXReader对象
SAXReader reader = new SAXReader();
// 加载xml文件
Document dc = reader.read(inputStream);
// 获取根节点
Element rootElement = dc.getRootElement();
List<Element> imageRelationshipList = rootElement.elements();
//处理每个关系
for (Element imageRelationship : imageRelationshipList) {
String imageRid = imageRelationship.attribute("Id").getValue();
String imagePath = imageRelationship.attribute("Target").getValue();
this.imageMap.put(imageRid, imagePath);
}
} catch (Exception e) {
e.printStackTrace();
} finally {
if (inputStream != null) {
inputStream.close();
}
}
}
/**
* @param null
* @return [test.ExcelSheet]
* @version [V1.0]
* @Title: [saveFile]
* @author [小鹿同学]
* @description [保存文件]
* @createTime [2023/9/20 17:54]
* @updateUser: [小鹿同学]
* @updateTime: [2023/9/20 17:54]
* @updateRemark: [描述说明本次修改内容]
*/
private File saveFile(byte[] bytes, String fileName) throws IOException {
FileOutputStream fos = null;
try {
File outputFile = new File(fileName);
if (!outputFile.getParentFile().exists()) {
outputFile.getParentFile().mkdirs();
}
fos = new FileOutputStream(fileName);
fos.write(bytes);
return outputFile;
} catch (Exception e) {
e.printStackTrace();
} finally {
if (fos != null) {
fos.close();
}
}
return null;
}
/**
* @param null
* @return [test.ExcelSheet]
* @version [V1.0]
* @Title: [subImageId]
* @author [小鹿同学]
* @description [截取函数中图片的ID]
* @createTime [2023/9/20 17:54]
* @updateUser: [小鹿同学]
* @updateTime: [2023/9/20 17:54]
* @updateRemark: [描述说明本次修改内容]
*/
private String subImageId(String imageId) {
return imageId.substring(imageId.indexOf("ID_") + 3, imageId.lastIndexOf("\""));
}
/**
* @param null
* @return [test.ExcelSheet]
* @version [V1.0]
* @Title: [getFloatPictureMap]
* @author [小鹿同学]
* @description [获取sheet中浮动的图片 xls]
* @createTime [2023/9/20 17:55]
* @updateUser: [小鹿同学]
* @updateTime: [2023/9/20 17:55]
* @updateRemark: [描述说明本次修改内容]
*/
private Map<String, HSSFPictureData> getFloatPictureMap(HSSFSheet sheet) throws IOException {
Map<String, HSSFPictureData> map = new HashMap<String, HSSFPictureData>();
List<HSSFShape> list = sheet.getDrawingPatriarch().getChildren();
for (HSSFShape shape : list) {
if (shape instanceof HSSFPicture) {
HSSFPicture picture = (HSSFPicture) shape;
HSSFClientAnchor cAnchor = picture.getClientAnchor();
HSSFPictureData pdata = picture.getPictureData();
String key = cAnchor.getRow1() + "-" + cAnchor.getCol1(); // 行号-列号
map.put(key, pdata);
}
}
return map;
}
/**
* @param null
* @return [test.ExcelSheet]
* @version [V1.0]
* @Title: [getFloatPictureMap]
* @author [小鹿同学]
* @description [获取sheet中浮动的图片 xlsx]
* @createTime [2023/9/20 17:55]
* @updateUser: [小鹿同学]
* @updateTime: [2023/9/20 17:55]
* @updateRemark: [描述说明本次修改内容]
*/
private Map<String, XSSFPictureData> getFloatPictureMap(XSSFSheet sheet) throws IOException {
Map<String, XSSFPictureData> map = new HashMap<String, XSSFPictureData>();
List<POIXMLDocumentPart> list = sheet.getRelations();
for (POIXMLDocumentPart part : list) {
if (part instanceof XSSFDrawing) {
XSSFDrawing drawing = (XSSFDrawing) part;
List<XSSFShape> shapes = drawing.getShapes();
for (XSSFShape shape : shapes) {
XSSFPicture picture = (XSSFPicture) shape;
XSSFClientAnchor anchor = picture.getPreferredSize();
CTMarker marker = anchor.getFrom();
String key = marker.getRow() + "-" + marker.getCol();
map.put(key, picture.getPictureData());
}
}
}
return map;
}
/**
* @param null
* @return [test.ExcelSheet]
* @version [V1.0]
* @Title: [getRowPicMaxColIndex]
* @author [小鹿同学]
* @description [获取每行图片的最大列数 ,浮动图片不能直接被读取,防止数据丢失]
* @createTime [2023/9/20 17:56]
* @updateUser: [小鹿同学]
* @updateTime: [2023/9/20 17:56]
* @updateRemark: [描述说明本次修改内容]
*/
private Map<Integer, Integer> getRowPicMaxColIndex(Map<String, XSSFPictureData> floatPicMap) {
//获取全部图片的行列信息
Set<String> rowWithColStringSet = floatPicMap.keySet();
Map<Integer, Integer> rowPicMaxColMap = new HashMap<>();
for (String rowWithColString : rowWithColStringSet) {
String[] rowWithColArray = rowWithColString.split("-");
//图片所在行
Integer row = Integer.parseInt(rowWithColArray[0]);
//图片所在列
Integer col = Integer.parseInt(rowWithColArray[1]);
//如果小就替换
Integer cacheCol = rowPicMaxColMap.get(row);
if (cacheCol == null || cacheCol < col) {
rowPicMaxColMap.put(row, col);
}
}
return rowPicMaxColMap;
}
/**
* @param null
* @return [test.ExcelSheet]
* @version [V1.0]
* @Title: [getImplantPicById]
* @author [小鹿同学]
* @description [使用嵌入式图片ID获取嵌入式图片地址]
* @createTime [2023/9/20 17:59]
* @updateUser: [小鹿同学]
* @updateTime: [2023/9/20 17:59]
* @updateRemark: [描述说明本次修改内容]
*/
private String getImplantPicById(String imageId) throws IOException {
String imageRid = imageIdMappingMap.get(imageId);
String imagePath = imageMap.get(imageRid);
return imagePath;
}
/**
* @param null
* @return [test.ExcelSheet]
* @version [V1.0]
* @Title: [保存文件]
* @author [小鹿同学]
* @description [描述说明该方法的功能]
* @createTime [2023/9/20 18:00]
* @updateUser: [小鹿同学]
* @updateTime: [2023/9/20 18:00]
* @updateRemark: [描述说明本次修改内容]
*/
private File saveFile(InputStream inputStream, String fileName) throws IOException {
OutputStream outputStream = null;
try {
File outputFile = new File(fileName);
if (!outputFile.getParentFile().exists()) {
outputFile.getParentFile().mkdirs();
}
outputStream = new FileOutputStream(outputFile);
byte[] buffer = new byte[1024];
int length;
while ((length = inputStream.read(buffer)) > 0) {
outputStream.write(buffer, 0, length);
}
return outputFile;
} catch (Exception e) {
e.printStackTrace();
} finally {
if (outputStream != null) {
outputStream.close();
}
if (inputStream != null) {
inputStream.close();
}
}
return null;
}
/**
* 获取行数据 包含空行
* 此方法返回数据索引与Excel 行号一致
*
* @return rowDataList
*/
public List<Map<Integer, ExcelDataEntry>> getRowDataList() {
return this.rowDataList;
}
/**
* 获取行数据
* 此方法只获取不为空的行
*
* @return rowDataList
*/
public List<Map<Integer, ExcelDataEntry>> getNotNullRowDataList() {
return this.rowDataList.stream().filter(CollectionUtil::isNotEmpty).collect(Collectors.toList());
}
/**
* 获取数据 by title 返回一个单元格数据
*
* @param titleRow 标题所在行 传空默认第一行有数据的行
* @param title 标题
* @param rowIndex 行数
* @return
*/
public ExcelDataEntry getDataByTitle(Integer titleRow, Object title, Integer rowIndex) {
if (this.titleDataListMap == null || this.titleRow != titleRow) {
loadDataByTitle(titleRow);
}
List<ExcelDataEntry> colDataList = titleDataListMap.get(title);
// for (ExcelDataEntry dataItem : colDataList) {
// if (dataItem.getRow().equals(rowIndex)) {
// return dataItem;
// }
// if (dataItem.getRow() > rowIndex) {
// break;
// }
//
// }
return colDataList.get(rowIndex);
}
/**
* 通过标题查数据 返回一列数据
*
* @param titleRow 标题所在行 传空默认第一行有数据的行
* @param title 标题
* @return
*/
public List<ExcelDataEntry> getDataByTitle(Integer titleRow, Object title) {
if (this.titleDataListMap == null || this.titleRow != titleRow) {
loadDataByTitle(titleRow);
}
return titleDataListMap.get(title);
}
/**
* @param title
* @param colIndex
* @return
*/
public ExcelDataEntry getDataByTitle(Object title, Integer rowIndex) {
return this.getDataByTitle(null, title, rowIndex);
}
public List<ExcelDataEntry> getDataByTitle(Object title) {
return this.getDataByTitle(null, title);
}
/**
* @param titleRow title所在行 传入null 则默认第一个有数据的行
* @return [test.ExcelSheet]
* @version [V1.0]
* @Title: [loadDataByTitle]
* @author [小鹿同学]
* @description [加载使用title分组数据]
* @createTime [2023/9/20 18:00]
* @updateUser: [小鹿同学]
* @updateTime: [2023/9/20 18:00]
* @updateRemark: [描述说明本次修改内容]
*/
private synchronized void loadDataByTitle(Integer titleRow) {
if (this.titleDataListMap != null) {
return;
}
this.titleRow = titleRow;
//获取title数据
loadSheetDataTitle(titleRow);
Map<Integer, ExcelDataEntry> sheetDataTitle = this.sheetTitleMap;
Map<Object, List<ExcelDataEntry>> titleDataListMap = new HashMap<>();
//循环处理数据
for (Map<Integer, ExcelDataEntry> rowDataItem : rowDataList) {
Set<Integer> titleColIndexList = sheetDataTitle.keySet();
for (Integer titleColIndex : titleColIndexList) {
Object title = sheetDataTitle.get(titleColIndex).getData();
ExcelDataEntry colData = rowDataItem.get(titleColIndex);
List<ExcelDataEntry> titleColDataList = (List<ExcelDataEntry>) titleDataListMap.get(title);
if (CollectionUtils.isEmpty(titleColDataList)) {
titleColDataList = new ArrayList<>();
}
titleColDataList.add(colData);
titleDataListMap.put(title, titleColDataList);
}
}
this.titleDataListMap = titleDataListMap;
}
public List<Map<Object, ExcelDataEntry>> getColDataList() {
List<Map<Object, ExcelDataEntry>> colDataList = new ArrayList<>();
Map<Integer, ExcelDataEntry> sheetTitleMap = this.sheetTitleMap;
for (int rowIndex = 0; rowIndex < this.rowDataList.size(); rowIndex++) {
if (titleRow!=null&&rowIndex == titleRow) {
continue;
}
Map<Integer, ExcelDataEntry> rowData = rowDataList.get(rowIndex);
Map<Object, ExcelDataEntry> colData = new HashMap<>();
for (Integer colIndex : sheetTitleMap.keySet()) {
ExcelDataEntry colTitle = sheetTitleMap.get(colIndex);
ExcelDataEntry colDataItem = rowData.get(colIndex);
colData.put(colTitle.getData(), colDataItem);
}
colDataList.add(colData);
}
return colDataList;
}
/**
* 加载标题
*
* @param row 标题所在行 ,传null 默认第一行有数据的
*/
private void loadSheetDataTitle(Integer row) {
if (row == null) {
this.sheetTitleMap = this.getNotNullRowDataList().get(0);
return;
}
this.sheetTitleMap = this.getRowDataList().get(row);
}
}
3.2.4 mian
最后附上一个测试类,读者可以自己测试一下
package test;
import com.alibaba.fastjson.JSONObject;
import java.util.List;
import java.util.Map;
import java.util.concurrent.*;
/**
* @className: mian
* @description: [描述说明该类的功能]
* @author: [姓名]
* @version: [v1.0]
* @createTime: [2023/9/20 16:09]
* @updateUser: [姓名]
* @updateTime: [2023/9/20 16:09]
* @updateRemark: [描述说明本次修改内容]
*/
public class mian {
public static void main(String[] args) {
String path = "F:\\desktop\\test.xlsx";
String imageTargetPath = "F:/testDemo/testExcel/image";
ExcelUtil excelUtil = new ExcelUtil(path, imageTargetPath);
ExcelSheet sheet = excelUtil.getSheet(0,1);
System.out.println(sheet.getRowDataList());
List<Map<Integer, ExcelDataEntry>> rowDataList = sheet.getRowDataList();
String s = JSONObject.toJSONString(rowDataList);
System.out.println(s);
List<ExcelDataEntry> a = sheet.getDataByTitle("编号" );
System.out.println(a);
ExcelDataEntry c = sheet.getDataByTitle("编号" ,2);
System.out.println(c);
ExcelDataEntry d = sheet.getDataByTitle(1,"编号" ,2);
System.out.println(d);
List<Map<Object, ExcelDataEntry>> colDataList = sheet.getColDataList();
String colDataListString = JSONObject.toJSONString(colDataList);
System.out.println(colDataListString);
}
// public static void main(String[] args) {
// String path = "F:\\desktop\\3.xls";
// String imageTargetPath = "F:/testDemo/testExcel/image";
//
// ExcelUtil excelUtil = new ExcelUtil(path, imageTargetPath);
// ExcelSheet sheet = excelUtil.getSheet(0, null);
// System.out.println(sheet.getRowDataList());
// }
}
4 总结
代码中对图片数据保存至规定路径后返回了一个file对象,用户可以自己对file对象进行别的操作
目前只能兼容XLSX文件,因为XLS文件和XLSX文件的内部结构不一样,于是还在解决中,我这里也有些思路,但是碰到了一些问题,如果大家有什么问题可以在评论区里留言哦。