无法使用 Python 将 EMF 插入 Word
Posted
技术标签:
【中文标题】无法使用 Python 将 EMF 插入 Word【英文标题】:Unable to insert EMF into Word using Python 【发布时间】:2021-08-12 18:17:00 【问题描述】:我需要将 SVG 文件插入 Word。因为,我们不能直接这样做,所以我打算将 SVG 转换为 EMF 并插入它。使用 inkscape 将 SVG 转换为 EMF 效果很好。但是,我无法想出将其插入 Word 的正确代码。 我按照this post 中的 Alvaro 人解释的步骤进行操作。已显示附件中的步骤 -
这是我的代码 -
但是,当我运行附件中显示的代码时 - 它仍然抛出 docx.image.exceptions.UnrecognizedImageError。 github 上的库的贡献者声称该库解决了这个问题。如果是这样,请告诉我是否遗漏任何内容。
我可以手动成功插入 EMF 文件。通过插入 EMF 来附加文档。此 EMF 是从 the internet 下载的,用于测试。
【问题讨论】:
看起来问题出在图像中。 EMF 格式因其故障而臭名昭著。可能有些图像可以这样处理,但有些则不能。您可以手动将图像粘贴到 DOCX 文件中吗? DOCX 文件基本上是一个带有 xml 和媒体文件的 zip 存档。您可以手动制作 DOCX 文件、保存、解压缩并查看您的 EMF 是如何存储的。然后找出如何对另一个文件做类似的事情。 @YuriKhristich - 感谢您的回复。我能够将 EMF 文件插入 Word。刚刚更新了我附加文档的问题。请看一下,让我知道。 @YuriKhristich-对不起,你是对的。我错过了 *** 不允许我们上传文件。有什么方法可以和你分享吗?如果您共享您的电子邮件地址,可以邮寄给您。 刚刚在这里上传了文档 - file.io/29Vhavlq5Mak 这是 EMF 文件 - file.io/9n8QTizYYlT2 【参考方案1】:似乎docx
模块不适用于EMF
文件。
我的意思是解决方法在这里:
import shutil
import zipfile
temp_dir = "_temp"
old_docx = "doc.docx"
new_docx = "doc_new.docx"
old_emf = temp_dir + "/word/media/image1.emf"
new_emf = "new_image.emf"
# unpack content of the docx file into the temp folder
with zipfile.ZipFile(old_docx, "r") as z:
files = z.namelist()
for f in files: z.extract(f, temp_dir)
# replace the image
shutil.copyfile(new_emf, old_emf)
# pack all files from temp folder back into the new docx file
with zipfile.ZipFile(new_docx, "a") as z:
for f in files: z.write(temp_dir + "/" + f, f)
# remove the temp folder
shutil.rmtree(temp_dir)
docx 文件的典型结构:
doc.docx
│
├─ [Content_Types].xml
│
├─ _rels
│ └─ .rels
│
├─ docProps
│ ├─ app.xml
│ └─ docProps
│
└─ word
├─ document.xml <-- text is here
├─ fontTable.xml
├─ settings.xml
├─ webSettings.xml
├─ styles.xml
│
├─ _rels
│ └─ document.xml.rels
│
├─ theme
│ └─ theme1.xml
│
└─ media
└─ image1.emf <-- your image is here
它解压缩临时文件夹_temp
中的doc 文件doc.docx
的内容,然后用当前目录中的另一个文件new_image.emf
替换临时目录中的文件image1.emf
。然后它将临时文件夹的内容打包回doc_new.docx
文件并删除临时目录。
注意:new_doc.docx
中的新图像将与旧图像具有相同的大小。
所以工作流程可以是这样的:你制作模板 docx 文件,手动放置模板 emf 图片并保存 docx 文件。然后获取新的 emf 图像,将图像放在 docx 文件旁边并运行脚本。这样你就可以得到一个带有新 emf 图像的新 docx 文件。
我想你有很多 emf 图像,所以在这个脚本中添加几行是有意义的,它能够拍摄多张图像并制作几个 docx 文件。
如果所有 emf 图像的大小相同,它将正常工作。如果它们具有不同的大小,则需要更多的编码来处理 xml 数据。
更新
我已经弄清楚如何获取 emf 图像的大小。所以这里是完整的解决方案:
from docx import Document
import shutil
import zipfile
temp_dir = "_temp"
old_docx = "doc.docx"
new_docx = "doc_new.docx"
old_emf = temp_dir + "/word/media/image1.emf" # don't change this line
new_emf = "img5.emf"
# unpack content of the docx file into temp folder
with zipfile.ZipFile(old_docx, "r") as z:
files = z.namelist()
for f in files: z.extract(f, temp_dir)
# replace the image
shutil.copyfile(new_emf, old_emf)
# pack all files from temp folder back into the new docx file
with zipfile.ZipFile(new_docx, "a") as z:
for f in files: z.write(temp_dir + "/" + f, f)
# remove temp folder
shutil.rmtree(temp_dir)
# get sizes of the emf image
with open(new_emf, "rb") as f:
f.read(16)
w1, w2 = f.read(1).hex(), f.read(1).hex()
f.read(2)
h1, h2 = f.read(1).hex(), f.read(1).hex()
width = int(str(w2) + str(w1), 16) * 762
height = int(str(h2) + str(h1), 16) * 762
# open the new docx file and set the sizes for the image
doc = Document(new_docx)
img = doc.inline_shapes[0] # suppose the first image is the image
img.width = width
img.height = height
doc.save(new_docx)
【讨论】:
谢谢,@Yuri 明天试试这个并告诉你 我可以运行它,它会将 EMF 从 doc 传输到 doc_new。但是,请告诉我如何使用它? 此脚本将 doc.docx 文件中的 EMF 图像更改为另一个 EMF 图像(取自当前文件夹)并保存名为 doc_new.docx 的新 docx 文件。如何使用它取决于您。可能,我不知道,如果您有数百个 emf 文件,您可以将它们放在一个文件夹中,并且(在对该脚本进行最小修改后)制作数百个 docx 文件,每个文件都将包含一个相关的 EMF 图像。但是你还没有提到你想做什么。 我的主要目标是将 SVG 文件插入 Word。我们使用 SVG 是因为当分辨率改变时图像不会失真。此图像将主要包含我的团队生成的一些图。这是一个示例 - file.io/IotKrLUtqdUY 由于我们找不到任何插入 SVG 的方法,我们计划使用 incscape 将 SVG 转换为 EMF,并希望构建一个将所有绘图插入到 word 文档中的脚本。如果您对处理我们的要求有任何想法,请告诉我。 查看我基于win32com
模块的另一个解决方案【参考方案2】:
这是基于win32com
模块和MS Word API的另一种解决方案:
from pathlib import Path
import win32com.client
cur_dir = Path.cwd() # get current folder
pictures = list((cur_dir / "pictures").glob("*.emf")) # get a list of pictures
word_app = win32com.client.Dispatch("Word.Application") # run Word
doc = word_app.Documents.Add() # create a new docx file
for pict in pictures: # insert all pictures
doc.InlineShapes.AddPicture(pict)
doc.SaveAs(str(cur_dir / "pictures.docx")) # save the docx file
doc.Close() # close docx
word_app.Quit() # close Word
将您的 EMF 图像放入子文件夹 pictures
并运行此脚本。之后,您将进入当前文件夹中的文件 pictures.docx
,其中包含所有这些 EMF 图像。
【讨论】:
你太棒了!非常感谢。这工作正常。让我尝试一下 kiwiwings 提供的解决方案。他给出了一个Java项目。将在关闭此问题之前尝试是否可行。 请记住,win32com
仅适用于 Windows。
会的,谢谢
您使用 win32com 的解决方案似乎满足了我们的要求。将接受作为我问题的答案。非常感谢您的宝贵时间和您提供的宝贵建议。【参考方案3】:
SVG 可以直接添加到 Word - 只需在 Word (2016) 中手动尝试。 我已经为您的用例创建了一个 example Java project 作为 POC。 无需调用 inkscape,因为备用 PNG 是通过 Batik 即时创建的。
当然,OP 要求提供 Python 解决方案 - 但如果 python-openxml 缺少某些功能,则可能需要付出更多努力使其通过 python 运行而不是调用 java 运行时.
关于通过 EMF 的解决方案 - 请注意,确定边界有多种方法 - 在我在 POI 中实现的 EMF 渲染器中,我默认扫描 Window 和 Viewport 记录并仅使用 EMF如果我找不到任何其他内容或者如果通过配置选项省略了扫描,则标头边界。这通常会给我带来更好的结果。
示例项目的相关代码片段如下:
public class AddSvgToDocument
public static void main(String[] args) throws IOException, InvalidFormatException
File tmplDocx = new File(args[0]);
File svgFile = new File(args[1]);
File outDocx = new File(args[2]);
try (FileInputStream fis = new FileInputStream(tmplDocx);
XWPFDocument doc = new XWPFDocument(fis))
SVGImageRenderer rnd = new SVGImageRenderer();
try (FileInputStream fis2 = new FileInputStream(svgFile))
rnd.loadImage(fis2, PictureData.PictureType.SVG.contentType);
Rectangle2D nativeDim = rnd.getNativeBounds();
double widthPx = 500;
double heightPx = widthPx * nativeDim.getHeight() / nativeDim.getWidth();
BufferedImage bi = rnd.getImage(new Dimension2DDouble(widthPx, heightPx));
ByteArrayOutputStream bos = new ByteArrayOutputStream(100_000);
ImageIO.write(bi, "PNG", bos);
XWPFRun run = doc.createParagraph().createRun();
int widthEmu = Units.pixelToEMU((int)widthPx);
int heightEmu = Units.pixelToEMU((int)heightPx);
XWPFPicture pic = run.addPicture(new ByteArrayInputStream(bos.toByteArray()), PictureData.PictureType.PNG.ooxmlId, "image.png", widthEmu, heightEmu);
CTOfficeArtExtensionList extLst = pic.getCTPicture().getBlipFill().getBlip().addNewExtLst();
addExt(extLst, "28A0092B-C50C-407E-A947-70E740481C1C"
, "http://schemas.microsoft.com/office/drawing/2010/main", "a14:useLocalDpi"
, "val", "0");
addExt(extLst, "96DAC541-7B7A-43D3-8B79-37D633B846F1"
, "http://schemas.microsoft.com/office/drawing/2016/SVG/main", "asvg:svgBlip"
, "r:embed", addSVG(doc, svgFile));
try (FileOutputStream fos = new FileOutputStream(outDocx))
doc.write(fos);
private static void addExt(CTOfficeArtExtensionList extLst, String uri, String namespace, String name, String attribute, String value)
CTOfficeArtExtension ext = extLst.addNewExt();
ext.setUri(uri);
XmlCursor cur = ext.newCursor();
cur.toEndToken();
String[] prefixName = name.split(":");
cur.beginElement(new QName(namespace, prefixName[1], prefixName[0]));
cur.insertNamespace(prefixName[0], namespace);
if (attribute.contains(":"))
prefixName = attribute.split(":");
String prefix = prefixName[0];
String attrNamespace = DEFAULT_XML_OPTIONS
.getSaveSuggestedPrefixes().entrySet().stream()
.filter(me -> prefix.equals(me.getValue()))
.map(Map.Entry::getKey)
.findFirst().orElse(null);
cur.insertAttributeWithValue(new QName(attrNamespace, prefixName[1], prefix), value);
else
cur.insertAttributeWithValue(attribute, value);
cur.dispose();
private static String addSVG(XWPFDocument doc, File svgFile) throws InvalidFormatException, IOException
// SVG is not thoroughly supported as of POI 5.0.0, hence we need to go the long way instead of adding a picture
OPCPackage pkg = doc.getPackage();
String svgNameTmpl = "/word/media/image#.svg";
int svgImageIdx = pkg.getUnusedPartIndex(svgNameTmpl);
PackagePartName svgPPName = PackagingURIHelper.createPartName(svgNameTmpl.replace("#", Integer.toString(svgImageIdx)));
PackagePart svgPart = pkg.createPart(svgPPName, PictureData.PictureType.SVG.contentType);
try (FileInputStream fis = new FileInputStream(svgFile);
OutputStream os = svgPart.getOutputStream())
IOUtils.copy(fis, os);
PackageRelationship svgRel = doc.getPackagePart().addRelationship(svgPPName, TargetMode.INTERNAL, IMAGE_PART);
return svgRel.getId();
【讨论】:
感谢您的回复。已将项目克隆到我的 Eclipse 中。请注意,我不是活跃的开发者。你能告诉我运行这个项目的步骤吗? 如 github 自述文件中所述,您需要安装 gradle,然后使用gradle distZip
创建一个包含所有依赖项/jar 的 zip 文件。还有shell脚本来显示程序的调用。
感谢您的宝贵时间。现在,我可以使用 Yuri 提出的建议来实现我的目标 - 使用 win32com。暂时会坚持下去。如果我们这边有任何变化,会与您联系。以上是关于无法使用 Python 将 EMF 插入 Word的主要内容,如果未能解决你的问题,请参考以下文章