从excel中提取嵌入式图片的解决方法

1  发现问题

我的excel中有浮动图片和嵌入式图片,但是openpyxl的_image对象只提取到了浮动图片,通过阅读其源码发现,这是因为openpyxl只解析了drawing文件导致的,所以确定需要自己解析

2  解决思路

1、解析出media资源

2、解析出xml,这可以得到资源的rNvpr-rId-image target的关系

3、从xlrd或openpyxl中得到单元格cNvpr,定位到图片

3  解析xlsx

先把xlsx解压出来,得到的文件如下,其中xl文件夹是我们需要的

我分析了里面的所有文件,发现这两个文件存储了嵌入式图片的关键信息

  • xl/cellimages.xml
  • xl/_rels/cellimages.xml

打开这两个文件看看,到底存储了什么?

3.1  xl/cellimages.xml

 

有效信息在cellImage对象中

  • cellImage.pic.nvPicPr.cNvPr.name:记录了函数名
  • cellImage.pic.blipFill.blip.embed:记录了rId

记录这个关系,并建立映射关系 { ID_xxx: rId }

3.2  xl/_rels/cellimages.xml

 

有效信息在Relationship对象中

  • Relationship.id:文件rId
  • Relationship.target:图片地址

建立这个映射关系 { rId: target }

到这一步我们已经可以从函数名定位到图片资源了,剩下一步建立excel单元格和图片的关系,接下来解析excel文件

{ ID_xxx: rId } + { rId: target } = ID_xxx -> target

4  代码实现

接下来简单的代码实现,有问题可以评论区留言,看到会回复

此实现基于openpyxl

from xml.etree.ElementTree import fromstring
from io import BytesIO
from zipfile import ZipFile

from openpyxl import load_workbook
from openpyxl.packaging.relationship import get_rels_path, get_dependents
from openpyxl.xml.constants import SHEET_DRAWING_NS, REL_NS, IMAGE_NS
from openpyxl.drawing.image import Image, PILImage


def parse_element(element):
    """
    将XML解析为 {ID_XXX: rId}
    :param element:
        <etc:cellImage>
            <xdr:pic>
                <xdr:nvPicPr>
                    <xdr:cNvPr id="2" name="ID_CBD7CEBC94B44923A5B447F3F21C1995" descr="upload_post_object_v2_167528160"/><xdr:cNvPicPr/>
                </xdr:nvPicPr>
                <xdr:blipFill>
                    <a:blip r:embed="rId1"/>
                    <a:stretch><a:fillRect/></a:stretch>
                </xdr:blipFill>
                <xdr:spPr>
                    <a:xfrm>
                    <a:off x="0" y="0"/>
                    <a:ext cx="9144000" cy="4796155"/>
                </a:xfrm>
                <a:prstGeom prst="rect">
                    <a:avLst/>
                </a:prstGeom>
                </xdr:spPr>
            </xdr:pic>
        </etc:cellImage>
    :return:
    """
    data = {}
    xdr_namespace = "{%s}" % SHEET_DRAWING_NS
    targets = level_order_traversal(element, xdr_namespace + "nvPicPr")

    for target in targets:
        # 是一个cellimage
        cNvPr = embed = ""
        for child in target:
            if child.tag == xdr_namespace + "nvPicPr":
                cNvPr = child[0].attrib["name"]
            elif child.tag == xdr_namespace + "blipFill":
                _rel_embed = "{%s}embed" % REL_NS
                embed = child[0].attrib[_rel_embed]

        if cNvPr:
            data[cNvPr] = embed

    return data


def level_order_traversal(root, flag):
    """层次遍历,查找目标节点"""
    queue = [root]
    targets = []
    while queue:
        node = queue.pop(0)
        children = [child.tag for child in node]
        if flag in children:
            targets.append(node)
            continue

        for child in node:
            queue.append(child)

    return targets



def handle_images(deps, archive) -> []:
    """
    将图片二进制内容封装为Image对象
    """
    images = []
    if not PILImage:  # Pillow not installed, drop images
        return images

    for dep in deps:
        if dep.Type != IMAGE_NS:
            msg = "{0} image format is not supported so the image is being dropped".format(dep.Type)
            print(msg)
            continue

        try:
            image_io = archive.read(dep.target)
            image = Image(BytesIO(image_io))
        except OSError:
            msg = "The image {0} will be removed because it cannot be read".format(dep.target)
            print(msg)
            continue
        if image.format.upper() == "WMF":  # cannot save
            msg = "{0} image format is not supported so the image is being dropped".format(image.format)
            print(msg)
            continue
        image.embed = dep.id         # 文件rId
        image.target = dep.target    # 文件地址
        images.append(image)

    return images

def main():
    CELLIMAGE_PATH = "xl/cellimages.xml"
    PARSE_FILE_PATH = 'C:/Users/user/Downloads/TCI验收问题.xlsx'

    archive = ZipFile(PARSE_FILE_PATH, "r")
    wb = load_workbook(PARSE_FILE_PATH)

    src = archive.read(CELLIMAGE_PATH)                              # 打开cellImage.xml文件
    deps = get_dependents(archive, get_rels_path(CELLIMAGE_PATH))   # 解析cellImage.xml._rel文件
    image_rels = handle_images(deps=deps.Relationship, archive=archive)

    node = fromstring(src)
    cellimages_xml = parse_element(node)
    cellimages_rel = {}
    for image in image_rels:
        cellimages_rel[image.embed] = image

    for cnvpr, embed in cellimages_xml.items():
        cellimages_xml[cnvpr] = cellimages_rel.get(embed)

    # df = pd.read_excel(PARSE_FILE_PATH)
    # df["行号"] = df.index + 2
    # image_mappings = ParserXLSXEmbed(wb=wb, df=df).extract_images(start_from=max(0, 1) + 1)
    # image_mappings.update(cellimages_xml)

    archive.close()  # 关闭压缩文件对象,防止内存泄漏

    print(cellimages_xml)


if __name__ == '__main__':
    main()

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/60871.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

eclipse Java Editor Templates

​ Window - Preferences - Java - Editor - Templates ​ date ${currentDate:date(yyyy.MM.dd)}

W6100-EVB-PICO做DNS Client进行域名解析(四)

前言 在上一章节中我们用W6100-EVB-PICO通过dhcp获取ip地址&#xff08;网关&#xff0c;子网掩码&#xff0c;dns服务器&#xff09;等信息&#xff0c;给我们的开发板配置网络信息&#xff0c;成功的接入网络中&#xff0c;那么本章将教大家如何让我们的开发板进行DNS域名解…

使用 OpenCV 和 Python 卡通化图像-附源码

介绍 在本文中,我们将构建一个有趣的应用程序,它将卡通化提供给它的图像。为了构建这个卡通化器应用程序,我们将使用 python 和 OpenCV。这是机器学习令人兴奋的应用之一。在构建此应用程序时,我们还将了解如何使用 easygui、Tkinter 等库。在这里,您必须选择图像,然后应…

(亲测解决)PyCharm 从目录下导包提示 unresolved reference(完整图解)

最近在进行一个Flask项目的过程中遇到了unresolved reference 包名的问题&#xff0c;在网上找了好久解决方案&#xff0c;并没有一个能让我一步到位解决问题的。 后来&#xff0c;我对该问题和网上的解决方案进行了分析&#xff0c;发现网上大多数都是针对项目同一目录下的py…

变压器参数测定中空载实验和短路实验的理解

确定变压器的参数是在《电机学》和《电力系统分析》中非常重要的一个环节&#xff0c;这里用自己习惯的方式讲一下怎样理解 首先要讲下变压器的额定参数&#xff0c;这个也是个常考的知识点 额定功率&#xff0c;即视在功率&#xff0c;电压电流&#xff0c;单位是VA或者kVA额…

K8s工作原理

K8s title: Kubernetes之初探 subtitle: K8s的工作原理 date: 2018-09-18 18:26:37K8s概述 我清晰地记得曾经读到过的一篇博文&#xff0c;上面是这样写的&#xff0c; “云端教父AWS云端架构策略副总裁Adrian Cockcroft曾指出&#xff0c;两者虽然都是运用容器技术&#xff0…

Vue中,$forceUpdate()的使用

在Vue官方文档中指出&#xff0c;$forceUpdate具有强制刷新的作用。 那在vue框架中&#xff0c;如果data中有一个变量:age&#xff0c;修改他&#xff0c;页面会自动更新。 但如果data中的变量为数组或对象&#xff0c;我们直接去给某个对象或数组添加属性&#xff0c;页面是识…

剑指 Offer 53 - I. 在排序数组中查找数字 I

题目描述 统计一个数字在排序数组中出现的次数。示例 思路 1、暴力法 注意while循环中先判断数组是否越界再判断其值是否相等 class Solution {public int search(int[] nums, int target) {int count 0;for(int i 0; i < nums.length; i) {if(nums[i] target) {whil…

谷粒商城第九天-解决商品品牌问题以及前后端使用检验框架检验参数

目录 一、总述 二、商品分类问题 三、前端检验 四、后端检验 五、总结 一、总述 在完成完商品分类的时候&#xff0c;后来测试的时候还是发现了一些问题&#xff0c;现在将其进行解决&#xff0c;问题如下&#xff1a; 1. 取消显示的时候&#xff0c;如果取消了显示&…

java-IDEA MAVEN查看依赖树,解决jar包重复和冲突

如果这里面的依赖关系有红线,就说明有包冲突,一般都是版本不一致,可以在idea里下一个插件Maven Helper,点击install并重启IDEA 打开pom.xml文件&#xff0c;在下方会出现Dependency Analyzer&#xff0c;选择它会出现重复依赖列表&#xff0c;选择对应的依赖&#xff0c;右键红…

【小沐学前端】VuePress制作在线电子书、技术文档(VuePress + Markdown + node)

文章目录 1、简介1.1 VuePress简介1.2 它是如何工作的&#xff1f; 2、安装node3、安装VuePress4、配置VuePress4.1 修改标题4.2 修改导航条4.3 修改右侧栏4.4 修改正文 结语 1、简介 Vue驱动的静态网站生成器&#xff0c;生成的网页内容放到自己服务器上管理&#xff0c;可用于…

flutter开发实战-实现首页分类目录入口切换功能

。 在开发中经常遇到首页的分类入口&#xff0c;如美团的美食团购、打车等入口&#xff0c;左右切换还可以分页更多展示。 一、使用flutter_swiper_null_safety 在pubspec.yaml引入 # 轮播图flutter_swiper_null_safety: ^1.0.2二、实现swiper分页代码 由于我这里按照一页8…

shell指令的应用

整理思维导图判断家目录下&#xff0c;普通文件的个数和目录文件的个数输入一个文件名&#xff0c;判断是否为shell脚本文件&#xff0c;如果是脚本文件&#xff0c;判断是否有可执行权限&#xff0c;如果有可执行权限&#xff0c;运行文件&#xff0c;如果没有可执行权限&…

从URL取值传给后端

从URL传值给后端 http://127.0.0.1:8080/blog_content.html?id8点击浏览文章详情&#xff0c;跳转至详情页面 从 url 中拿出文章 id&#xff0c;传给后端 首先拿到url然后判断是否有值&#xff0c;从问号后面取值params.split(&) 以 & 作为分割然后遍历字符数组 param…

一百四十六、Xmanager——Xmanager5连接Xshell7并控制服务器桌面

一、目的 由于kettle安装在Linux上&#xff0c;Xshell启动后需要Xmanager。而Xmanager7版本受限、没有免费版&#xff0c;所以就用Xmanager5去连接Xshell7 二、Xmanager5安装包来源 &#xff08;一&#xff09;注册码 注册码&#xff1a;101210-450789-147200 &#xff08…

跑步蓝牙耳机哪种好、推荐几款专业跑步耳机

近年来&#xff0c;全民运动热潮逐渐兴起&#xff0c;运动耳机也成为各个年龄段的运动爱好者追捧的对象。作为一个热爱跑步的人&#xff0c;我可以负责任地告诉你&#xff0c;戴上耳机跑步会更加愉快。很多时候&#xff0c;运动的单调可能会让你产生放弃锻炼的想法&#xff0c;…

思科模拟器配置静态路由(下一跳使用IP)

Router0配置代码&#xff1a;##端口配置 Router(config)#int fastEthernet 0/0 Router(config-if)#ip address 192.168.10.254 255.255.255.0 Router(config-if)#no shutdown Router(config-if)#int fastEthernet 0/1 Router(config-if)#ip address 192.168.20.1 255.255.255.2…

ruoyi-cloud-notes02

1、Validated RequestBody 配合使用 Validated 和 RequestBody 都是 Spring Boot 中用于在请求中验证数据的注解。但是&#xff0c;它们的作用和使用方式略有不同。 Validated 用于在方法参数、URL、请求体、Map中的数据上进行验证&#xff0c;确保数据的有效性。它会在验证失…

vivo全球商城:电商交易平台设计

一、背景 vivo官方商城经过了七年的迭代&#xff0c;从单体架构逐步演进到微服务架构&#xff0c;我们的开发团队沉淀了许多宝贵的技术与经验&#xff0c;对电商领域业务也有相当深刻的理解。 去年初&#xff0c;团队承接了O2O商城的建设任务&#xff0c;还有即将成立的礼品中…

Java的变量与常量

目录 变量 声明变量 变量的声明类型 变量的声明方式&#xff1a;变量名 变量名的标识符 初始化变量 常量 关键字final 类常量 总结 变量和常量都是用来存储值和数据的基本数据类型存储方式&#xff0c;但二者之间有一些关键差别。 变量 在Java中&#xff0c;每个变…