读取从WPS在Excel中嵌入的图片资源
引言
许多数据文件中可能包含嵌入式图片,这些图片对于数据分析和可视化非常重要。然而,从 WPS 在 Excel 中读取这些图片可能会有一些技术挑战。在本文中,我将展示如何从 WPS Excel 文件中读取嵌入的图片,并提供代码示例。
提取图片资源的方法
以下是用于从 WPS Excel 文件中读取图片资源的方法。我们使用了 Apache POI 库来处理 Excel 文件,并使用了 cn.hutool.json 库来处理 XML 数据。这些库可以帮助我们解析 Excel 文件中的嵌入式图片。
主函数
首先,我们定义了主函数 main,用于测试图片提取功能:
public static void main(String[] args) {
PicturesUtils picturesUtils = new PicturesUtils();
byte[] fileData = picturesUtils.getFileStream(new File("你的文件路径"));
Map<String, XSSFPictureData> pictures = picturesUtils.getPictures(fileData);
pictures.forEach((id, xssfPictureData) -> {
System.out.println("id:" + id);
String fileName = xssfPictureData.getPackagePart().getPartName().getName();
System.out.println("fileName:" + fileName);
File file = new File("D:\\" + fileName);
File dir = file.getParentFile();
dir.mkdirs();
try {
Files.write(Path.of(file.getPath()), xssfPictureData.getData());
} catch (IOException e) {
e.printStackTrace();
}
});
}
在这个函数中,我们创建了一个 PicturesUtils 实例,并从文件路径中获取文件数据。然后,我们调用 getPictures 方法来提取图片资源,并遍历结果将图片写入本地文件。
获取浮动图片
接下来,我们展示了如何从 Excel 文件中获取浮动图片:
public static Map<String, XSSFPictureData> getFloatingPictures(XSSFSheet xssfSheet) {
Map<String, XSSFPictureData> mapFloatingPictures = new HashMap<>();
XSSFDrawing drawingPatriarch = xssfSheet.getDrawingPatriarch();
if (drawingPatriarch != null) {
List<XSSFShape> shapes = drawingPatriarch.getShapes();
for (XSSFShape shape : shapes) {
if (shape instanceof XSSFPicture picture) {
XSSFClientAnchor anchor = (XSSFClientAnchor) picture.getAnchor();
XSSFPictureData pictureData = picture.getPictureData();
String key = anchor.getRow1() + "-" + anchor.getCol1();
mapFloatingPictures.put(key, pictureData);
}
}
}
return mapFloatingPictures;
}
该方法接收一个 XSSFSheet 对象,并返回一个包含浮动图片的映射。它遍历工作表中的所有形状,并将图片数据存储在映射中。
处理图片数据
最后,我们展示了如何处理 Excel 文件中的图片数据,包括嵌入式图片和浮动式图片:
public Map<String, XSSFPictureData> getPictures(byte[] data) {
try {
Map<String, String> mapConfig = processZipEntries(new ByteArrayInputStream(data));
Map<String, XSSFPictureData> mapPictures = processPictures(new ByteArrayInputStream(data), mapConfig);
Iterator<Sheet> sheetIterator = WorkbookFactory.create(new ByteArrayInputStream(data)).sheetIterator();
while (sheetIterator.hasNext()) {
mapPictures.putAll(getFloatingPictures((XSSFSheet) sheetIterator.next()));
}
return mapPictures;
} catch (IOException e) {
return new HashedMap<>();
}
}
在该方法中,我们使用 processZipEntries 和 processPictures 方法来处理 Zip 文件中的条目和图片数据。然后,通过遍历 Excel 文件中的所有工作表,获取浮动图片。
完整代码示例
实际在程序中读取的内容为=DISPIMG(“ID_03BC802DDAB24510A9883DB157EAC0F8”,1)公式
将公式中的id提取出来ID_03BC802DDAB24510A9883DB157EAC0F8,最后就可以使用下面方法拿到当前id获取到的图片资源
import cn.hutool.json.JSONArray;
import cn.hutool.json.JSONObject;
import cn.hutool.json.XML;
import com.szjyrs.business.exhibition.product.domain.vo.ProductPlusImportLogVo;
import com.szjyrs.common.core.dev.method.MethodStatus;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.map.HashedMap;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.poi.openxml4j.opc.PackagePartName;
import org.apache.poi.ss.usermodel.Sheet;
import org.apache.poi.ss.usermodel.Workbook;
import org.apache.poi.ss.usermodel.WorkbookFactory;
import org.apache.poi.xssf.usermodel.*;
import org.springframework.web.multipart.MultipartFile;
import java.io.*;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;
/**
* @author bianhl
* @version 1.0
* @description 获取图片资源
* @date 2024年4月28日08:44:42
*/
@Slf4j
public class PicturesUtils {
public static void main(String[] args) {
PicturesUtils picturesUtils = new PicturesUtils();
byte[] fileData = picturesUtils.getFileStream(new File("你的文件路径"));
Map<String, XSSFPictureData> pictures = picturesUtils.getPictures(fileData);
pictures.forEach((id, xssfPictureData) -> {
System.out.println("id:" + id);
String fileName = xssfPictureData.getPackagePart().getPartName().getName();
System.out.println("fileName:" + fileName);
File file = new File("D:\\" + fileName);
File dir = file.getParentFile();
dir.mkdirs();
try {
Files.write(Path.of(file.getPath()), xssfPictureData.getData());
} catch (IOException e) {
e.printStackTrace();
}
});
}
/**
* 获取浮动图片,以 map 形式返回,键为行列格式 x-y。
*
* @param xssfSheet WPS 工作表
* @return 浮动图片的 map
*/
public static Map<String, XSSFPictureData> getFloatingPictures(XSSFSheet xssfSheet) {
Map<String, XSSFPictureData> mapFloatingPictures = new HashMap<>();
XSSFDrawing drawingPatriarch = xssfSheet.getDrawingPatriarch();
if (drawingPatriarch != null) {
List<XSSFShape> shapes = drawingPatriarch.getShapes();
for (XSSFShape shape : shapes) {
if (shape instanceof XSSFPicture picture) {
XSSFClientAnchor anchor = (XSSFClientAnchor) picture.getAnchor();
XSSFPictureData pictureData = picture.getPictureData();
String key = anchor.getRow1() + "-" + anchor.getCol1();
mapFloatingPictures.put(key, pictureData);
}
}
}
return mapFloatingPictures;
}
/**
* 处理 WPS 文件中的图片数据,返回图片信息 map。
*
* @param stream 输入流
* @param mapConfig 配置映射
* @return 图片信息的 map
* @throws IOException
*/
private Map<String, XSSFPictureData> processPictures(ByteArrayInputStream stream, Map<String, String> mapConfig) throws IOException {
Map<String, XSSFPictureData> mapPictures = new HashedMap<>();
Workbook workbook = WorkbookFactory.create(stream);
List<XSSFPictureData> allPictures = (List<XSSFPictureData>) workbook.getAllPictures();
for (XSSFPictureData pictureData : allPictures) {
PackagePartName partName = pictureData.getPackagePart().getPartName();
String uri = partName.getURI().toString();
if (mapConfig.containsKey(uri)) {
String strId = mapConfig.get(uri);
mapPictures.put(strId, pictureData);
}
}
return mapPictures;
}
/**
* 获取 WPS 文档中的图片,包括嵌入式图片和浮动式图片。
*
* @param data 二进制数据
* @return 图片信息的 map
* @throws IOException
*/
public Map<String, XSSFPictureData> getPictures(byte[] data) {
try {
Map<String, String> mapConfig = processZipEntries(new ByteArrayInputStream(data));
Map<String, XSSFPictureData> mapPictures = processPictures(new ByteArrayInputStream(data), mapConfig);
Iterator<Sheet> sheetIterator = WorkbookFactory.create(new ByteArrayInputStream(data)).sheetIterator();
while (sheetIterator.hasNext()) {
mapPictures.putAll(getFloatingPictures((XSSFSheet) sheetIterator.next()));
}
return mapPictures;
} catch (IOException e) {
return new HashedMap<>();
}
}
/**
* 处理 Zip 文件中的条目,更新图片配置信息。
*
* @param stream Zip 输入流
* @return 配置信息的 map
* @throws IOException
*/
private Map<String, String> processZipEntries(ByteArrayInputStream stream) throws IOException {
Map<String, String> mapConfig = new HashedMap<>();
ZipInputStream zipInputStream = new ZipInputStream(stream);
ZipEntry zipEntry;
while ((zipEntry = zipInputStream.getNextEntry()) != null) {
try {
final String fileName = zipEntry.getName();
if ("xl/cellimages.xml".equals(fileName)) {
processCellImages(zipInputStream, mapConfig);
} else if ("xl/_rels/cellimages.xml.rels".equals(fileName)) {
return processCellImagesRels(zipInputStream, mapConfig);
}
} finally {
zipInputStream.closeEntry();
}
}
return new HashedMap<>();
}
/**
* 处理 Zip 文件中的 cellimages.xml 文件,更新图片配置信息。
*
* @param zipInputStream Zip 输入流
* @param mapConfig 配置信息的 map
* @throws IOException
*/
private void processCellImages(ZipInputStream zipInputStream, Map<String, String> mapConfig) throws IOException {
String content = IOUtils.toString(zipInputStream, StandardCharsets.UTF_8);
JSONObject jsonObject = XML.toJSONObject(content);
if (jsonObject != null) {
JSONObject cellImages = jsonObject.getJSONObject("etc:cellImages");
if (cellImages != null) {
JSONArray cellImageArray = cellImages.getJSONArray("etc:cellImage");
if (cellImageArray == null) {
JSONObject cellImageObj = cellImages.getJSONObject("etc:cellImage");
if (cellImageObj != null) {
cellImageArray = new JSONArray();
cellImageArray.add(cellImageObj);
}
}
if (cellImageArray != null) {
processImageItems(cellImageArray, mapConfig);
}
}
}
}
/**
* 处理 cellImageArray 中的图片项,更新图片配置信息。
*
* @param cellImageArray 图片项的 JSONArray
* @param mapConfig 配置信息的 map
*/
private void processImageItems(JSONArray cellImageArray, Map<String, String> mapConfig) {
for (int i = 0; i < cellImageArray.size(); i++) {
JSONObject imageItem = cellImageArray.getJSONObject(i);
if (imageItem != null) {
JSONObject pic = imageItem.getJSONObject("xdr:pic");
if (pic != null) {
processPic(pic, mapConfig);
}
}
}
}
/**
* 处理 pic 中的图片信息,更新图片配置信息。
*
* @param pic 图片的 JSONObject
* @param mapConfig 配置信息的 map
*/
private void processPic(JSONObject pic, Map<String, String> mapConfig) {
JSONObject nvPicPr = pic.getJSONObject("xdr:nvPicPr");
if (nvPicPr != null) {
JSONObject cNvPr = nvPicPr.getJSONObject("xdr:cNvPr");
if (cNvPr != null) {
String name = cNvPr.getStr("name");
if (StringUtils.isNotEmpty(name)) {
String strImageEmbed = updateImageEmbed(pic);
if (strImageEmbed != null) {
mapConfig.put(strImageEmbed, name);
}
}
}
}
}
/**
* 获取嵌入式图片的 embed 信息。
*
* @param pic 图片的 JSONObject
* @return embed 信息
*/
private String updateImageEmbed(JSONObject pic) {
JSONObject blipFill = pic.getJSONObject("xdr:blipFill");
if (blipFill != null) {
JSONObject blip = blipFill.getJSONObject("a:blip");
if (blip != null) {
return blip.getStr("r:embed");
}
}
return null;
}
/**
* 处理 Zip 文件中的 relationship 条目,更新配置信息。
*
* @param zipInputStream Zip 输入流
* @param mapConfig 配置信息的 map
* @return 配置信息的 map
* @throws IOException
*/
private Map<String, String> processCellImagesRels(ZipInputStream zipInputStream, Map<String, String> mapConfig) throws IOException {
String content = IOUtils.toString(zipInputStream, StandardCharsets.UTF_8);
JSONObject jsonObject = XML.toJSONObject(content);
JSONObject relationships = jsonObject.getJSONObject("Relationships");
if (relationships != null) {
JSONArray relationshipArray = relationships.getJSONArray("Relationship");
if (relationshipArray == null) {
JSONObject relationshipObj = relationships.getJSONObject("Relationship");
if (relationshipObj != null) {
relationshipArray = new JSONArray();
relationshipArray.add(relationshipObj);
}
}
if (relationshipArray != null) {
return processRelationships(relationshipArray, mapConfig);
}
}
return null;
}
/**
* 处理 relationshipArray 中的关系项,更新配置信息。
*
* @param relationshipArray 关系项的 JSONArray
* @param mapConfig 配置信息的 map
* @return 配置信息的 map
*/
private Map<String, String> processRelationships(JSONArray relationshipArray, Map<String, String> mapConfig) {
Map<String, String> mapRelationships = new HashedMap<>();
for (int i = 0; i < relationshipArray.size(); i++) {
JSONObject relaItem = relationshipArray.getJSONObject(i);
if (relaItem != null) {
String id = relaItem.getStr("Id");
String value = "/xl/" + relaItem.getStr("Target");
if (mapConfig.containsKey(id)) {
String strImageId = mapConfig.get(id);
mapRelationships.put(value, strImageId);
}
}
}
return mapRelationships;
}
/**
* @param file 数据文件
* @return {@link byte[]}
* @description
* @author bianhl
* @date 2024/4/26 13:52
*/
private byte[] getFileStream(File file) {
try (InputStream inputStream = new FileInputStream(file)) {
// 创建 ByteArrayOutputStream 来暂存流数据
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
// 将 inputStream 读取到 byteArrayOutputStream 中
byte[] buffer = new byte[1024];
int length;
while ((length = inputStream.read(buffer)) != -1) {
byteArrayOutputStream.write(buffer, 0, length);
}
// 将 byteArrayOutputStream 的内容获取为字节数组
return byteArrayOutputStream.toByteArray();
} catch (IOException e) {
return null;
}
}
}
输出结果:
拿到的图片数据