java读取Excel,(支持WPS嵌入式图片)

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文件的内部结构不一样,于是还在解决中,我这里也有些思路,但是碰到了一些问题,如果大家有什么问题可以在评论区里留言哦。

  • 42
    点赞
  • 60
    收藏
    觉得还不错? 一键收藏
  • 11
    评论
要使用POI读取Excel文件嵌入式图片,可以使用以下步骤: 1. 使用POI打开Excel文件,获取工作簿对象(Workbook)。 2. 遍历每个工作表(Sheet),并获取每个工作表的所有图形对象(Drawing)。 3. 遍历每个图形对象,并判断是否为嵌入式图片对象(XSSFClientAnchor)。 4. 如果是嵌入式图片对象,则获取该图片的二进制数据(Byte array)。 以下是一个示例代码,可以读取Excel文件的所有嵌入式图片: ``` FileInputStream fis = new FileInputStream("test.xlsx"); Workbook workbook = new XSSFWorkbook(fis); for (int i = 0; i < workbook.getNumberOfSheets(); i++) { Sheet sheet = workbook.getSheetAt(i); for (Object obj : sheet.getDrawingPatriarch().getChildren()) { if (obj instanceof XSSFPicture) { XSSFPicture pic = (XSSFPicture) obj; XSSFClientAnchor anchor = pic.getClientAnchor(); if (anchor.getAnchorType() == ClientAnchor.AnchorType.MOVE_AND_RESIZE) { byte[] data = pic.getPictureData().getData(); // 处理图片数据 } } } } workbook.close(); fis.close(); ``` 其,`test.xlsx`是要读取Excel文件名。需要注意的是,该代码只能读取Excel文件嵌入式图片数据,如果要将图片保存到本地磁盘或者插入到其他Excel文件,需要额外处理。 另外,需要注意的是,该代码只适用于读取XSSF格式(即.xlsx文件)的Excel文件,如果要读取其他格式的Excel文件(如.xls文件),需要使用HSSF格式的POI库,并修改代码的相关类名。
评论 11
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值