Невозможно вставить EMF в Word с помощью Python

Мне нужно вставить файл SVG в Word. Поскольку мы не можем сделать это напрямую, я планирую преобразовать SVG в EMF и вставить его. Преобразование из SVG в EMF отлично работает с помощью inkscape. Однако я не могу придумать правильный код для вставки его в Word. Я выполнил шаги, описанные человеком Альваро в этом сообщении. Показали шаги в прикрепленном файле - введите здесь описание изображения

Это мой код -

введите здесь описание изображения

Однако, когда я запускаю код, показанный во вложении, он все равно выдает docx.image.exceptions.UnrecognizedImageError. Автор библиотеки на github утверждает, что эта библиотека решает эту проблему. Если да, то, пожалуйста, дайте мне знать, если я что-то упустил.

Я могу успешно вставить файл EMF вручную. Прикрепление документа путем вставки EMF. Этот EMF был загружен из Интернета для тестирования.


person Ashwin Kumar    schedule 24.05.2021    source источник
comment
Похоже проблема в картинках. Формат EMF известен своими глюками. Вероятно, некоторые изображения можно обрабатывать таким образом, а некоторые нет. Можете ли вы вставить изображения в файл DOCX вручную? Файл DOCX представляет собой zip-архив с xml и медиафайлами. Вы можете создать файл DOCX вручную, сохранить его, разархивировать и посмотреть, как хранятся ваши EMF. А потом придумать, как можно сделать что-то подобное с другим файлом.   -  person Yuri Khristich    schedule 24.05.2021
comment
@YuriKhristich - Спасибо за ответ. Я могу вставить файл EMF в Word. Только что обновил мой вопрос, прикрепив документ. Пожалуйста, посмотрите и дайте мне знать.   -  person Ashwin Kumar    schedule 24.05.2021
comment
@YuriKhristich-Извините, вы правы. Я пропустил, что Stackoverflow не позволяет нам загружать файлы. Могу ли я поделиться им с вами? Могу отправить вам по почте, если вы поделитесь своим адресом электронной почты.   -  person Ashwin Kumar    schedule 25.05.2021
comment
Только что загрузили документ сюда - file.io/29Vhavlq5Mak   -  person Ashwin Kumar    schedule 25.05.2021
comment
Это файл EMF — file.io/9n8QTizYYlT2.   -  person Ashwin Kumar    schedule 25.05.2021
comment
Давайте продолжим обсуждение в чате.   -  person Ashwin Kumar    schedule 25.05.2021


Ответы (3)


Вот еще одно решение, основанное на модуле 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.

person Yuri Khristich    schedule 01.06.2021
comment
Ты замечательный! Большое спасибо. Это прекрасно работает. Позвольте мне попробовать решение, которое предлагает kiwiwings. Он предоставил Java-проект. Попробую, если это сработает, прежде чем закрыть этот вопрос. - person Ashwin Kumar; 04.06.2021
comment
Имейте в виду, что win32com работает только в Windows. - person Yuri Khristich; 04.06.2021
comment
Сделаю, спасибо - person Ashwin Kumar; 04.06.2021
comment
ваше решение с использованием win32com, кажется, отвечает нашим требованиям. Приму это как ответ на мой вопрос. Большое спасибо за ваше время и ценные предложения, которые вы дали. - person Ashwin Kumar; 09.06.2021

Похоже модуль 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

Он распаковывает содержимое файла doc doc.docx во временную папку _temp, затем заменяет файл image1.emf во временной папке другим файлом new_image.emf из текущей папки. Затем он упаковывает содержимое временной папки обратно в файл doc_new.docx и удаляет временную папку.

Примечание: новое изображение будет иметь тот же размер в new_doc.docx, что и старое.

Таким образом, рабочий процесс может быть таким: вы создаете файл шаблона docx, помещаете туда вручную шаблон изображения emf и сохраняете файл docx. Затем вы берете новый образ emf, кладете изображение рядом с файлом docx и запускаете скрипт. Таким образом вы получите новый файл docx с новым изображением emf.

Я полагаю, у вас много изображений в формате 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)
person Yuri Khristich    schedule 25.05.2021
comment
Спасибо, @Yuri Попробую завтра и сообщу - person Ashwin Kumar; 30.05.2021
comment
Я могу запустить это, и он передает EMF из doc в doc_new. Однако, пожалуйста, дайте мне знать, как я могу использовать это? - person Ashwin Kumar; 01.06.2021
comment
Этот скрипт заменяет изображение EMF в файле doc.docx другим изображением EMF (взятым из текущей папки) и сохраняет новый файл docx с именем doc_new.docx. Вам решать, как это использовать. Возможно, я не знаю, если у вас есть сотни emf-файлов, вы можете поместить их в папку и (после минимальных изменений этого скрипта) сделать сотню docx-файлов, каждый из которых будет содержать соответствующее изображение EMF. Но вы все еще не упомянули, что вы пытаетесь сделать. - person Yuri Khristich; 01.06.2021
comment
Моя основная цель — вставить файлы SVG в Word. Мы прибегаем к SVG, потому что изображение остается неискаженным при изменении разрешения. Это изображение в основном будет содержать некоторые графики, созданные моей командой. Вот пример: file.io/IotKrLUtqdUY Поскольку мы не смогли найти способ вставки SVG, мы планировали преобразовать SVG в EMF с помощью incscape и хотел создать скрипт, который вставляет все графики в текстовый документ. Пожалуйста, дайте мне знать, если у вас есть какие-либо мысли для обработки нашего требования. - person Ashwin Kumar; 01.06.2021
comment
См. мое другое решение, основанное на модуле win32com - person Yuri Khristich; 01.06.2021

SVG можно добавить непосредственно в Word — просто попробуйте вручную в Word (2016). Я создал пример проекта Java в качестве POC для вашего варианта использования. Нет необходимости вызывать inkscape, потому что резервный PNG создается на лету через Batik.

Конечно, ОП попросил решение Python, но в случае, если в python-openxml отсутствуют некоторые функции, может быть момент, когда необходимо приложить больше усилий для его запуска через python, а не для вызова среды выполнения java.

Что касается обходного решения с помощью EMF — имейте в виду, что существуют различные методы определения границ — в средстве визуализации EMF, которое я реализовал в POI, я по умолчанию просматриваю записи 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();
    }
}
person kiwiwings    schedule 02.06.2021
comment
Спасибо за ваш ответ. Клонировали проект в мой Eclipse. Обратите внимание, я не являюсь активным разработчиком. Не могли бы вы дать мне шаги для запуска этого проекта? - person Ashwin Kumar; 04.06.2021
comment
Как упоминалось в github readme, вам необходимо установить gradle, а затем использовать gradle distZip для создания zip-файла со всеми зависимостями/jar-файлами. . Там также есть сценарий оболочки, показывающий вызов программы. - person kiwiwings; 04.06.2021
comment
Спасибо за ваше время. На данный момент я могу достичь своей цели, используя предложение Юрия - используя win32com. Будет придерживаться его на данный момент. Мы свяжемся с вами, если что-то изменится с нашей стороны. - person Ashwin Kumar; 09.06.2021