楼主: lkf5090
292 0

【Excel导入】读取WPS格式嵌入单元格内的图片 [推广有奖]

  • 0关注
  • 0粉丝

准贵宾(月)

学前班

40%

还不是VIP/贵宾

-

威望
0
论坛币
992 个
通用积分
0
学术水平
0 点
热心指数
0 点
信用等级
0 点
经验
20 点
帖子
1
精华
0
在线时间
0 小时
注册时间
2018-9-19
最后登录
2018-10-2

楼主
lkf5090 发表于 2025-11-12 19:14:24 |AI写论文

+2 论坛币
k人 参与回答

经管之家送您一份

应届毕业生专属福利!

求职就业群
赵安豆老师微信:zhaoandou666

经管之家联合CDA

送您一个全额奖学金名额~ !

感谢您参与论坛问题回答

经管之家送您两个论坛币!

+2 论坛币

背景:

读取WPS的自定义格式DISPIMG的图片数据。由于POI不直接支持图像单元格嵌入,因此需要依据Excel的基础实现解析。(如果是以浮动形式存在的POI格式图片,则可以通过锚点的方式直接获取)

一:工具代码

import lombok.extern.slf4j.Slf4j;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.web.multipart.MultipartFile;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;

/**
 * Excel导入处理器
 * @author c
 * date: 2025-11-05 09:19:31
 * description: 用于处理Excel文件中单元格的嵌入图片数据。
 */
@Slf4j
public class ExcelDispImgExtractorUtil {
     /**
     * 解析单元格图片映射
     * key-图片所在单元格显示的ID value-图片转数据(直接使用中可将它通过Base64编码转换成字符串使用更节省内存)
     * key在实际使用中可以通过EasyExcel的Convert进行转换直接得到(这个具体可根据个人情况使用)
     */
    public static Map<String, byte[]> importLocalExcel(MultipartFile file) {
        Map<String, byte[]> cellImageMap = new LinkedHashMap<>();

        try (InputStream inputStream = file.getInputStream();
             ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(inputStream.readAllBytes());
             ZipInputStream zis = new ZipInputStream(byteArrayInputStream)) {

            // 解析ZIP文件内容
            ZipParseResult zipResult = parseZipEntries(zis);

            // 解析关系映射
            Map<String, String> relsMap = parseRelsMapping(zipResult.getCellImagesRelsXml());

            // 解析单元格图片映射
            parseCellImages(zipResult.getCellImagesXml(), relsMap, zipResult.getImagesData(), cellImageMap);

            return cellImageMap;

        } catch (Exception e) {
            log.error("导入Excel文件异常: {}", e.getMessage(), e);
            return Collections.emptyMap();
        }
    }

    /**
     * 解析ZIP条目
     */
    private static ZipParseResult parseZipEntries(ZipInputStream zis) throws IOException {
        ZipParseResult result = new ZipParseResult();
        ZipEntry zipEntry;

        while ((zipEntry = zis.getNextEntry()) != null) {
            String entryName = zipEntry.getName();
            byte[] entryData = IOUtils.toByteArray(zis);

            switch (entryName) {
                case "xl/cellimages.xml" -> result.setCellImagesXml(new String(entryData, StandardCharsets.UTF_8));
                case "xl/_rels/cellimages.xml.rels" ->
                        result.setCellImagesRelsXml(new String(entryData, StandardCharsets.UTF_8));
                default -> {
                    if (entryName.startsWith("xl/media/")) {
                        String imageName = entryName.substring("xl/media/".length());
                        result.getImagesData().put(imageName, entryData);
                    }
                }
            }
            zis.closeEntry();
        }

        return result;
    }

    /**
     * 解析关系映射
     */
    private static Map<String, String> parseRelsMapping(String cellImagesRelsXml)
            throws ParserConfigurationException, SAXException, IOException {

        Map<String, String> relsMap = new HashMap<>();

        if (StringUtils.isEmpty(cellImagesRelsXml)) {
            return relsMap;
        }

        DocumentBuilderFactory factory = createSecureDocumentBuilderFactory();
        DocumentBuilder builder = factory.newDocumentBuilder();

        try (ByteArrayInputStream bais = new ByteArrayInputStream(
                cellImagesRelsXml.getBytes(StandardCharsets.UTF_8))) {

            Document relsDoc = builder.parse(bais);
            NodeList relNodes = relsDoc.getElementsByTagName("Relationship");

            for (int i = 0; i < relNodes.getLength(); i++) {
                Element relElement = (Element) relNodes.item(i);
                String rId = relElement.getAttribute("Id");
                String target = relElement.getAttribute("Target");

                if (target.startsWith("media/")) {
                    relsMap.put(rId, target.substring("media/".length()));
                } else {
                    log.warn("发现不符合预期的target格式: {}", target);
                }
            }
        }

        return relsMap;
    }

    /**
     * 解析单元格图片
     */
    private static void parseCellImages(String cellImagesXml, Map<String, String> relsMap,
                                        Map<String, byte[]> imagesData, Map<String, byte[]> cellImageMap)
            throws ParserConfigurationException, SAXException, IOException {

        if (StringUtils.isEmpty(cellImagesXml)) {
            return;
        }

        DocumentBuilderFactory factory = createSecureDocumentBuilderFactory();
        DocumentBuilder builder = factory.newDocumentBuilder();

        try (ByteArrayInputStream bais = new ByteArrayInputStream(
                cellImagesXml.getBytes(StandardCharsets.UTF_8))) {

            Document cellImagesDoc = builder.parse(bais);
            NodeList cellImageNodes = cellImagesDoc.getElementsByTagName("etc:cellImage");

            for (int i = 0; i < cellImageNodes.getLength(); i++) {
                Element cellImageElement = (Element) cellImageNodes.item(i);
                processSingleCellImage(cellImageElement, relsMap, imagesData, cellImageMap);
            }
        }
    }

    /**
     * 处理单个单元格图片
     */
    private static void processSingleCellImage(Element cellImageElement, Map<String, String> relsMap,
                                               Map<String, byte[]> imagesData, Map<String, byte[]> cellImageMap) {
        try {
            Element picElement = (Element) cellImageElement.getElementsByTagName("xdr:pic").item(0);
            if (picElement == null) return;

            Element cNvPr = (Element) picElement.getElementsByTagName("xdr:cNvPr").item(0);
            if (cNvPr == null) return;

            String imageName = cNvPr.getAttribute("name");
            if (StringUtils.isEmpty(imageName)) return;

            Element blipFill = (Element) picElement.getElementsByTagName("xdr:blipFill").item(0);
            if (blipFill == null) return;

            Element blip = (Element) blipFill.getElementsByTagName("a:blip").item(0);
            if (blip == null) return;

            String rId = blip.getAttribute("r:embed");
            if (StringUtils.isEmpty(rId)) return;

            String imageFileName = relsMap.get(rId);
            if (StringUtils.isEmpty(imageFileName)) {
                cellImageMap.put(imageName, null);
                return;
            }

            byte[] imageBytes = imagesData.get(imageFileName);
            cellImageMap.put(imageName, imageBytes);

        } catch (Exception e) {
            log.warn("处理单元格图片时发生异常: {}", e.getMessage(), e);
        }
    }

    /**
     * 创建安全的DocumentBuilderFactory
     */
    private static DocumentBuilderFactory createSecureDocumentBuilderFactory() throws ParserConfigurationException {
        DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
        // 防止XXE攻击
        factory.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
        factory.setFeature("http://xml.org/sax/features/external-general-entities", false);
        factory.setFeature("http://xml.org/sax/features/external-parameter-entities", false);
        factory.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", false);
        factory.setXIncludeAware(false);
        factory.setExpandEntityReferences(false);
        return factory;
    }

    /**
     * ZIP文件解析结果容器
     */
    private static class ZipParseResult {
        private String cellImagesXml;
        private String cellImagesRelsXml;
        private final Map<String, byte[]> imagesData = new HashMap<>();

        public String getCellImagesXml() {
            return cellImagesXml;
        }

        public void setCellImagesXml(String cellImagesXml) {
            this.cellImagesXml = cellImagesXml;
        }

        public String getCellImagesRelsXml() {
            return cellImagesRelsXml;
        }

        public void setCellImagesRelsXml(String cellImagesRelsXml) {
            this.cellImagesRelsXml = cellImagesRelsXml;
        }

        public Map<String, byte[]> getImagesData() {
            return imagesData;
        }
    }

}

扩展:提供基于EasyExcel的转换器

import com.alibaba.excel.converters.Converter;
import com.alibaba.excel.metadata.GlobalConfiguration;
import com.alibaba.excel.metadata.data.ReadCellData;
import com.alibaba.excel.metadata.property.ExcelContentProperty;
import lombok.extern.slf4j.Slf4j;

/**
 * @author c
 * date: 2025-11-11 15:16:46
 * description: 将Excel单元格数据转换为字符串(图片ID)
 */
@Slf4j
public class ByteGetIdConverter implements Converter<String> {

    /**
     * 将Excel单元格数据转换为字符串(图片ID)
     */
    @Override
    public String convertToJavaData(ReadCellData<?> cellData, ExcelContentProperty contentProperty, GlobalConfiguration globalConfiguration) {
        try {
            // 检查单元格是否包含DISPIMG公式
            if (cellData.getStringValue() != null) {
                String stringValue = cellData.getStringValue();
                if (stringValue.contains("DISPIMG")) {
                    // 提取图片ID
                    String imageId = extractImageIdFromFormula(stringValue);
                    if (imageId != null) {
                        return imageId;
                    }
                }
            }
            // 如果没有公式,返回单元格的字符串值
            return cellData.getStringValue();
        } catch (Exception e) {
            log.warn("转换图片ID时发生异常: {}", e.getMessage());
            return cellData.getStringValue();
        }
    }

    /**
     * 从DISPIMG公式中提取图片ID
     */
    private String extractImageIdFromFormula(String formula) {
        try {
            // 公式格式: _xlfn.DISPIMG("ID_XXXX",1)
            int startIndex = formula.indexOf("\"");
            int endIndex = formula.lastIndexOf("\"");
            if (startIndex != -1 && endIndex != -1 && endIndex > startIndex) {
                return formula.substring(startIndex + 1, endIndex);
            }
        } catch (Exception e) {
            log.warn("从公式中提取图片ID失败: {}", formula);
        }
        return null;
    }

}
@Data
public class ImportData {

    @ExcelProperty(value="测试")
    private String test;

    @ExcelProperty(value="详情")
    private String checkRole;

    @ExcelProperty(value="图片", converter = ByteGetIdConverter.class)
    private String image;

}

二:读取原理

Excel文档(例如

.xlsx
文件)由多个部分构成。实际上,它是一个压缩文件,内含多种类型的数据、样式和资源,通过不同的文件格式组合而成。深入了解其组成部分可以解释为何Excel能够存储图像。

2.1. Excel文档的组成结构

.xlsx
文件本质上是一个 压缩包 ,采用 ZIP 格式压缩。这意味着,它并非单一文件,而是多个文件和文件夹的集合,使用ZIP格式存储。你可以通过更改文件扩展名将
.xlsx
转变为
.zip
,然后解压查看以下主要组成部分:
  • [xl/contents.xml]:包含工作簿的主要内容,如工作表数据、单元格信息等。
  • [xl/worksheets/]:保存每个工作表的数据(如单元格值、公式、格式等)。
  • [xl/drawings/]:存储与图形、形状和图像相关的资源。
  • [xl/theme/]:包含文档的主题和样式信息。
  • [xl/styles.xml]:包含工作簿的样式设置,如字体、颜色、单元格边框等。
  • [xl/media/]:存储文档中嵌入的媒体文件(例如图像、视频等)。
  • [docProps/]:保存文件的元数据,如作者、创建日期、最后修改日期等。

2.2. Excel中的图片存储机制

在Excel文件中,图片通常存储在
xl/media/
文件夹内。以下是图片如何被存储的细节:

图片作为独立文件:Excel文档中的图像会以嵌入文件的形式存储。它们通常是图像文件(如

.jpg
.png
.bmp
等格式)的一部分,这些图像被存储在
xl/media/
文件夹内。

图片与工作表内容关联:图片不直接嵌入单元格中,而是作为独立对象存储在工作簿中。每张图片都有一个唯一的ID,在Excel的XML内容中引用该ID指向

xl/media/
中的实际图像文件。Excel中的工作表(通常是
xl/worksheets/sheet1.xml
)会记录图片的位置信息(例如,图片在工作表中的锚点、大小、位置等)。

锚点和定位:图片在Excel工作表中通常被称为“对象”,这些图像通过**锚点(Anchor)**来定位。锚点指示图片应与哪个单元格关联。Excel使用锚点机制控制图片的位置,确保图片随单元格大小调整而变化(例如,通过“随单元格调整大小”设置)。

图片的压缩存储:图像通常会被压缩以减少文件大小,特别是在使用现代Excel格式(.xlsx)时。ZIP压缩格式可以有效减小存储空间。

2.3. Excel文档存储图片的具体过程

图片插入:当你插入一张图像时,Excel将该图保存为一个图像文件,并放入

xl/media/
目录中。

图片引用:Excel会生成一个XML文件(通常在

xl/worksheets/sheetX.xml
中),并在其中记录图片的位置信息(锚点、大小、层次等),以便打开工作表时,图像能够正确显示在预定位置。

图片的显示:当你打开Excel文件时,Excel通过这些XML文件读取位置信息,并根据存储的图像文件将图片显示在工作表中。显示时会遵循锚点设置(例如:图片是否随单元格调整大小)。

2.4. Excel如何将图片与单元格关联

Excel通过 锚点(anchor) 机制将图像与工作表中的特定单元格关联。锚点定义了图像的位置和尺寸。图像可以设置为:

  • 固定位置:图片位置不随单元格调整而变化。
  • 随单元格调整大小:图片大小会根据单元格的尺寸自动调整。

这种机制使Excel能够灵活地将图像与单元格关联,确保图片正确显示并跟随单元格大小的变化。

总结:

Excel文档 是一个ZIP格式的压缩文件,包含多个子文件和文件夹。 图片 存储在

xl/media/
文件夹中,以图像文件的形式存储。 Excel通过XML文件记录图片的 锚点 和位置,并引用存储在
xl/media/
文件夹中的实际图片文件。 图片在Excel工作表中作为 对象 存在, 通过锚点机制 与单元格相关联。 因此,Excel能够存储图片,主要是因为它允许将图像文件嵌入到文件结构中,并通过XML文件记录图片的位置和显示方式。图片不直接嵌入单元格内,而是作为工作表对象的一部分,随单元格的变化调整显示。

二维码

扫码加我 拉你入群

请注明:姓名-公司-职位

以便审核进群资格,未注明则拒绝

关键词:EXCEL xcel exce wps cel

您需要登录后才可以回帖 登录 | 我要注册

本版微信群
扫码
拉您进交流群
GMT+8, 2026-2-10 20:28