背景
EXCEL导入时,若文件中含有嵌入式图片,使用poi自带读取图片方法获取图片失败。
当Excel中使用的图片是嵌入式图片时,导入数据写入数据库中数据变成了excel函数=DISPIMG(“ID_52896892776A4E68B6F11189E703C07B”,1)
实现思路
通过Excel中图片单元格中函数匹配到对应的图片,将图片上传至服务器,返回图片地址进行保存,从而完成WPS嵌入式图片导入功能
将excel文件后缀名改为zip压缩包,解压后得到如下文件
查询网上资料发现,根据excel图片单元格中函数ID定位到对应关系文件在xl/cellimages.xml和xl/_rels/cellimages.xml.rels这两个文件中
首先我们看到cellimages.xml这个文件中包含了第一段关系
然后我们看到cellimages.xml.rels这这个文件中包含了第二段关系
根据这两段关系,我们可以通过excel单元格中函数ID值获取到图片信息。至此,前期逻辑梳理完成,接下来就是代码实现。
代码实现
ExcelUtil工具类中添加wps嵌入式图片解析逻辑
下面展示一些 核心代码。
第一步:申明变量,用于存放excel文件和图片关系信息
/**
* 文件对象
*/
private File file;
/**
* key 函数ID value Rid
*/
private Map<String, String> imageIdMappingMap = new HashMap<>();
/**
* key :Rid value 图片信息
*/
private Map<String, String> imageMap = new HashMap<>();
第二步:处理函数和rid对应关系和处理rid和图片信息关系方法准备
/**
* @description: 解析cellimages.xml文件中excel中单元格函数ID对应的RID对应关系
* @author:
* @date: 2025/2/8 11:30
* @param: []
* @return: void
**/
private void ridWithIDRelationShip() 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_", "");
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) {
log.error("解析cellimages.xml文件中excel中单元格函数ID对应的RID对应关系失败", e);
} finally {
if (inputStream != null) {
inputStream.close();
}
}
}
/**
* @description:解析cellimages.xml.rels文件中cellimages.xml文件中的RID对应图片信息对应关系
* @author:
* @date: 2025/2/8 11:31
* @param: []
* @return: void
**/
private void ridWithImagePathRelationShip() 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) {
log.error("处理xml中rid和图片路径映射关系失败",e);
} finally {
if (inputStream != null) {
inputStream.close();
}
}
}
/**
* @description:打开文件转换为zip流,获取zip中对应关系文件
* @author:
* @date: 2025/2/8 11:33
* @param: [filePath]
* @return: java.io.InputStream
**/
private InputStream openFile(String filePath) {
try {
ZipFile zipFile = new ZipFile(this.file);
ZipInputStream zipInputStream = new ZipInputStream(new FileInputStream(this.file));
ZipEntry nextEntry = null;
while ((nextEntry = zipInputStream.getNextEntry()) != null) {
String name = nextEntry.getName();
if (name.equalsIgnoreCase(filePath)) {
return zipFile.getInputStream(nextEntry);
}
}
} catch (Exception e) {
log.error("解析文件失败", e);
}
return null;
}
第三步: importExcel方法中解析文件,将关系写入申明的变量中
/**
* 对excel表单指定表格索引名转换成list
*
* @param sheetName 表格索引名
* @param titleNum 标题占用行数
* @param is 输入流
* @return 转换后集合
*/
public List<T> importExcel(String sheetName, InputStream is, int titleNum) throws Exception {
this.type = Type.IMPORT;
// 将excel流转换为文件
this.file = new File(VasConfig.getImportPath() + "/" + IdUtils.fastUUID() + ".xlsx");
/*将excel写入创建的文件中 inputStreamToFile方法中使用方法如下Files.copy(in, file.toPath(), StandardCopyOption.REPLACE_EXISTING);*/
FileUtils.inputStreamToFile(is, this.file);
// 解析cellimages.xml文件中excel中单元格函数ID对应的RID对应关系
ridWithIDRelationShip();
// 解析cellimages.xml.rels文件中cellimages.xml文件中的RID对应图片信息对应关系
ridWithImagePathRelationShip();
this.wb = WorkbookFactory.create(new FileInputStream(this.file));
List<T> list = new ArrayList<T>();
// 如果指定sheet名,则取指定sheet中的内容 否则默认指向第1个sheet
Sheet sheet = StringUtils.isNotEmpty(sheetName) ? wb.getSheet(sheetName) : wb.getSheetAt(0);
if (sheet == null) {
throw new IOException("文件sheet不存在");
}
......后续逻辑不变......
}
第四步: importExcel方法中处理每个单元格数据,但单元格对应字段类型为图片时,根据单元格中函数ID匹配到对应图片写入文件,并返回文件路径
else if (ColumnType.IMAGE == attr.cellType())
{
if(StringUtils.isNotEmpty(pictures)){
PictureData image = pictures.get(row.getRowNum() + "_" + entry.getKey());
if (image != null)
{
byte[] data = image.getData();
val = FileUtils.writeImportBytes(data);
}
}
if(val.toString().contains("DISPIMG")){
String imageId = subImageId(val.toString());
String picPath = getImplantPicById(imageId);
InputStream picInputStream = openFile("xl/" + picPath);
val = FileUtils.writeImportBytes(IOUtils.toByteArray(picInputStream));
}
}
根据单元格函数ID获取图片信息
private String subImageId(String imageId) {
return imageId.substring(imageId.indexOf("ID_") + 3, imageId.lastIndexOf("\""));
}
private String getImplantPicById(String imageId) throws IOException {
String imageRid = imageIdMappingMap.get(imageId);
String imagePath = imageMap.get(imageRid);
return imagePath;
}
ps:以上只粘贴了嵌入式图片处理逻辑实现代码,不包含完整代码,需要完整代码的小伙伴们可以自行参考若依框架中自带的excel导入工具
小结
以上是处理嵌入式图片的一种方法,在调试过程中我发现poi工具针对excel解析的流中也包含了zip压缩文件以及relationship等信息,但碍于时间关系,没有深入了解