欢迎光临散文网 会员登陆 & 注册

Excel嵌入图片至单元格

2023-05-09 16:49 作者:RPA驿站  | 我要投稿

from PIL import Image
import os
import zipfile
import tempfile
import xml.etree.ElementTree as ET
import secrets


class ExcelHandler:
   def __init__(self):
       self.map_img = {}
       self.xml_declaration = '<?xml version="1.0" encoding="UTF-8" standalone="yes"?>'
       self.cell_images_rels_path = "xl/_rels/cellimages.xml.rels"
       self.cell_images_path = "xl/cellimages.xml"
       self.sheet_path = "xl/worksheets/{}.xml"

   def add_img(self, zip_file, img_paths):
       """添加图片"""

       def generate_random_filename():
           filename = secrets.token_bytes(16).hex().upper() + ".png"
           return filename

       def get_img_size(path):
           # 打开图像并获取大小
           with Image.open(path) as im:
               width, height = im.size

           return width, height

       for img_path in img_paths:
           img_name = os.path.basename(img_path)
           new_img_name = generate_random_filename()
           zip_file.write(img_path, arcname=f"xl/media/{new_img_name}")
           size = get_img_size(img_path)
           self.map_img[img_name] = {"path": f"media/{new_img_name}", "size": size}

   @staticmethod
   def create_id(ids, pre=""):
       new_id = pre + "1"
       num = 1
       while new_id in ids:
           num += 1
           new_id = f"{pre}{num}"
       return new_id

   def write_cell_images_rels(self, z_in, z_out):
       if self.cell_images_rels_path not in z_in.namelist():
           raise Exception("请先在表格中任意嵌入一张图片")
       else:
           root = ET.fromstring(z_in.read(self.cell_images_rels_path))

       relationships = root.findall(
           '{http://schemas.openxmlformats.org/package/2006/relationships}Relationship')

       rids = list(map(lambda node: node.get('Id'), relationships))
       rid = self.create_id(rids, pre="rId")
       rids.append(rid)
       for img_name, info in self.map_img.items():
           new_element = ET.Element('ns0:Relationship')
           new_element.set('Id', rid)
           new_element.set('Type', "http://schemas.openxmlformats.org/officeDocument/2006/relationships/image")
           new_element.set('Target', info["path"])
           root.insert(-1, new_element)
           info["rid"] = rid
           self.map_img[img_name] = info

       xml_str = ET.tostring(root)
       z_out.writestr(self.cell_images_rels_path, self.xml_declaration + xml_str.decode())

   def write_cell_images(self, z_in, z_out):
       def create_cell_image(_id, img_uuid, descr, rid, cx, cy):
           cell_image = ET.SubElement(root, 'ns0:cellImage')
           pic = ET.SubElement(cell_image, 'ns1:pic')
           nvPicPr = ET.SubElement(pic, 'ns1:nvPicPr')
           cNvPr = ET.SubElement(nvPicPr, 'ns1:cNvPr', attrib={
               'id': _id,
               'name': img_uuid,
               'descr': descr
           })
           cNvPicPr = ET.SubElement(nvPicPr, 'ns1:cNvPicPr')
           picLocks = ET.SubElement(cNvPicPr, 'ns2:picLocks', attrib={
               'noChangeAspect': '1'
           })
           blipFill = ET.SubElement(pic, 'ns1:blipFill')
           blip = ET.SubElement(blipFill, 'ns2:blip', attrib={
               'ns3:embed': rid
           })
           stretch = ET.SubElement(blipFill, 'ns2:stretch')
           fillRect = ET.SubElement(stretch, 'ns2:fillRect')
           spPr = ET.SubElement(pic, 'ns1:spPr')
           xfrm = ET.SubElement(spPr, 'ns2:xfrm')
           off = ET.SubElement(xfrm, 'ns2:off', attrib={
               'x': '66',
               'y': '66'
           })
           ext = ET.SubElement(xfrm, 'ns2:ext', attrib={
               'cx': cx,
               'cy': cy
           })
           prstGeom = ET.SubElement(spPr, 'ns2:prstGeom', attrib={
               'prst': 'rect'
           })
           avLst = ET.SubElement(prstGeom, 'ns2:avLst')

       if self.cell_images_rels_path not in z_in.namelist():
           raise Exception("请先在表格中任意嵌入一张图片")
       else:
           root = ET.fromstring(z_in.read(self.cell_images_path))

       namespaces = {'xdr': 'http://schemas.openxmlformats.org/drawingml/2006/spreadsheetDrawing'}
       ids = [c_nv_pr.get('id') for c_nv_pr in root.findall('.//xdr:cNvPr', namespaces)]
       for img_name, info in self.map_img.items():
           w, h = info["size"]
           uuid = f'ID_{secrets.token_bytes(16).hex().upper()}'
           create_cell_image(self.create_id(ids), uuid, img_name, info["rid"], str(w * 9525), str(h * 9525))
           info["img_uuid"] = uuid
           self.map_img[img_name] = info
       xml_str = ET.tostring(root)
       z_out.writestr(self.cell_images_path, self.xml_declaration + xml_str.decode())

   def write_sheet(self, z_in, z_out, sheet_path, row, col, img_uuid):
       """
       插入图片
       """
       root = ET.fromstring(z_in.read(sheet_path))
       pre = ""
       row_node = ET.Element(f'{pre}row')
       row_node.set('r', row)
       row_node.set('spans', '1:1')
       row_node.set('customHeight', '1')
       cell_node = ET.SubElement(row_node, f"{pre}c")
       cell_node.set("r", f"{col.upper()}{row}")
       cell_node.set("t", "str")
       f_node = ET.SubElement(cell_node, f"{pre}f")
       v_node = ET.SubElement(cell_node, f"{pre}v")
       f_node.text = f'_xlfn.DISPIMG("{img_uuid}",1)'
       v_node.text = f'=DISPIMG("{img_uuid}",1)'

       sheet_data = root.find('{http://schemas.openxmlformats.org/spreadsheetml/2006/main}sheetData')
       sheet_data.append(row_node)
       xml_str = ET.tostring(root)
       z_out.writestr(sheet_path, self.xml_declaration + xml_str.decode())

   def embed_img_to_cell(self, excel_path, sheet_name, img_path, row, col):
       """
       嵌入图片至单元格
       :param excel_path: 表格路径
       :param sheet_name: 工作表名称
       :param img_path: 图片路径
       :param row: 行
       :param col: 列
       :return:
       """
       temp_path = ""
       sheet_name = sheet_name.lower()
       sheet_path = self.sheet_path.format(sheet_name)
       row = str(row)
       col = str(col).upper()
       try:
           temp_fd, temp_path = tempfile.mkstemp(dir=os.path.dirname(excel_path))
           os.close(temp_fd)
           with zipfile.ZipFile(excel_path, 'r') as z_in, zipfile.ZipFile(temp_path, 'a') as z_out:
               if sheet_path not in z_in.namelist():
                   raise Exception(f"工作表{sheet_name}不存在")
               z_out.comment = z_in.comment
               self.add_img(z_out, [img_path])
               self.write_cell_images_rels(z_in, z_out)
               self.write_cell_images(z_in, z_out)
               self.write_sheet(z_in, z_out, sheet_path, row, col, self.map_img[img_path]["img_uuid"])
               for item in z_in.infolist():
                   filename = item.filename
                   if filename == self.cell_images_rels_path:
                       continue
                   if filename == self.cell_images_path:
                       continue
                   if filename == sheet_path:
                       continue
                   z_out.writestr(item, z_in.read(filename))

           os.remove(excel_path)
           os.rename(temp_path, excel_path)
       finally:
           if os.path.exists(temp_path):
               os.remove(temp_path)


if __name__ == '__main__':
   execl_handler = ExcelHandler()
   execl_handler.embed_img_to_cell("新建 XLSX 工作表.xlsx", "sheet2", "150x150.png", 7, 2)

Excel嵌入图片至单元格的评论 (共 条)

分享到微博请遵守国家法律