Как извлечь изображение из таблицы в документе MS Word с помощью библиотеки docx? - PullRequest
1 голос
/ 26 октября 2019

Я работаю над программой, которая должна извлечь два изображения из документа MS Word, чтобы использовать их в другом документе. Я знаю, где расположены изображения (первая таблица в документе), но когда я пытаюсь извлечь из таблицы любую информацию (даже просто текст), я получаю пустые ячейки.

ВотДокумент Word , из которого я хочу извлечь изображения. Я хочу извлечь изображения 'Rentel' с первой страницы (первая таблица, строки 0 и 1, столбец 2).


Я попытался попробовать следующий код:

from docxtpl import DocxTemplate

source_document = DocxTemplate("Source document.docx")

# It doesn't really matter which rows or columns I use for the cells, everything is empty
print(source_document.tables[0].cell(0,0).text)

Что просто дает мне пустые строки ...


Я прочитал это обсуждение и это , что проблема может заключаться в том, что "содержитсяв элементе оболочки, который Python Docx не может прочитать ". Они предлагают изменить исходный документ, но я хочу иметь возможность выбрать любой документ, который был ранее создан с тем же шаблоном, что и исходный документ (поэтому эти документы также содержат ту же проблему, и я не могу изменить каждый документ отдельно). Таким образом, решение только для Python - это единственный способ решить проблему.


Поскольку мне также нужны только эти два конкретных изображения, извлечение любого случайного изображения из xml путем разархивирования файла Wordне очень подходит моему решению, если только я не знаю, какое имя изображения мне нужно извлечь из разархивированных папок файлов Word.


Я действительно хочу, чтобы это работало, поскольку оно является частью моей диссертации (и яЯ всего лишь инженер-электромеханик, поэтому я не очень разбираюсь в программном обеспечении).


[EDIT] : Вот код XML для первого изображения (source_document.tables[0].cell(0,2)._tc.xml) и вот оно для второго изображения (source_document.tables[0].cell(1,2)._tc.xml). Однако я заметил, что, взяв (0,2) в качестве значения строки и столбца, я получаю все строки в столбце 2 в первой «видимой» таблице . Ячейка (1,2) дает мне все строки в столбце 2 во второй "видимой" таблице .

Если проблема не решается напрямую с помощью Python Docx, возможно ли это? найти имя изображения или идентификатор или что-то в XML-коде, а затем добавить изображение, используя этот идентификатор / имя с Python Docx?

Ответы [ 2 ]

1 голос
/ 26 октября 2019

Ну, первое, что бросается в глаза, это то, что обе ячейки (w:tc элементы), которые вы разместили в каждой , содержат вложенную таблицу. Возможно, это необычно, но, безусловно, действительная композиция. Возможно, они сделали это, чтобы включить подпись в ячейку под изображением или что-то в этом роде.

Чтобы получить доступ к вложенной таблице, вам нужно сделать что-то вроде:

outer_cell = source_document.tables[0].cell(0,2)
nested_table = outer_cell.tables[0]
inner_cell_1 = nested_table.cell(0, 0)
print(inner_cell_1.text)
# ---etc....---

I 'Я не уверен, что это решит всю вашу проблему, но мне кажется, что в конце концов это два или более вопроса, первый из которых: «Почему не отображается моя ячейка таблицы?»и второе, возможно, это "Как я могу получить изображение из ячейки таблицы?"(как только вы действительно нашли нужную клетку).

0 голосов
/ 27 октября 2019

Для людей, имеющих ту же проблему, этот код помог мне решить ее:

Сначала я извлекаю вложенную ячейку из таблицы, используя следующий метод:

@staticmethod
def get_nested_cell(table, outer_row, outer_column, inner_row, inner_column):
    """
        Returns the nested cell (table inside a table) of the *document*

        :argument
            table: [docx.Table] outer table from which to get the nested table
            outer_row: [int] row of the outer table in which the nested table is
            outer_column: [int] column of the outer table in which the nested table is
            inner_row: [int] row in the nested table from which to get the nested cell
            inner_column: [int] column in the nested table from which to get the nested cell
        :return
            inner_cell: [docx.Cell] nested cell
    """
    # Get the global first cell
    outer_cell = table.cell(outer_row, outer_column)
    nested_table = outer_cell.tables[0]
    inner_cell = nested_table.cell(inner_row, inner_column)

    return inner_cell

Используя эту ячейку, я могу получить код XML и извлечь изображение из этого кода XML. Примечание:

  • Я не установил ширину и высоту изображения, потому что хотел, чтобы оно было одинаковым
  • В методе replace_logos_from_source я знаю, что таблица, в которой я хочуЛоготипы получены из 'tables [0]' и что вложенная таблица находится в external_row и external_column '0', поэтому я просто заполнил ее в методе get_nested_cell без добавления дополнительных аргументов в replace_logos_from_source
def replace_logos_from_source(self, source_document, target_document, inner_row, inner_column):
    """
        Replace the employer and client logo from the *source_document* to the *target_document*. Since the table
        in which the logos are placed are nested tables, the source and target cells with *inner_row* and
        *inner_column* are first extracted from the nested table.

        :argument
            source_document: [DocxTemplate] document from which to extract the image
            target_document: [DocxTemplate] document to which to add the extracted image
            inner_row: [int] row in the nested table from which to get the image
            inner_column: [int] column in the nested table from which to get the image
        :return
            Nothing
    """
    # Get the target and source cell (I know that the table where I want to get the logos from is 'tables[0]' and that the nested table is in outer_row and outer_column '0', so I just filled it in without adding extra arguments to the method)
    target_cell = self.get_nested_cell(target_document.tables[0], 0, 0, inner_row, inner_column)
    source_cell = self.get_nested_cell(source_document.tables[0], 0, 0, inner_row, inner_column)

    # Get the xml code of the inner cell
    inner_cell_xml = source_cell._tc.xml

    # Get the image from the xml code
    image_stream = self.get_image_from_xml(source_document, inner_cell_xml)

    # Add the image to the target cell
    paragraph = target_cell.paragraphs[0]
    if image_stream:  # If not None (image exists)
        run = paragraph.add_run()
        run.add_picture(image_stream)
    else:
        # Set the target cell text equal to the source cell text
        paragraph.add_run(source_cell.text)

@staticmethod
def get_image_from_xml(source_document, xml_code):
    """
        Returns the rId for an image in the *xml_code*

        :argument
            xml_code: [string] xml code from which to extract the image from
        :return
            image_stream: [BytesIO stream] the image to find
            None if no image exists in the xml_file

    """
    # Parse the xml code for the blip
    xml_parser = minidom.parseString(xml_code)

    items = xml_parser.getElementsByTagName('a:blip')

    # Check if an image exists
    if items:
        # Extract the rId of the image
        rId = items[0].attributes['r:embed'].value

        # Get the blob of the image
        source_document_part = source_document.part
        image_part = source_document_part.related_parts[rId]
        image_bytes = image_part._blob

        # Write the image bytes to a file (or BytesIO stream) and feed it to document.add_picture(), maybe:
        image_stream = BytesIO(image_bytes)

        return image_stream
    # If no image exists
    else:
        return None

Для вызова метода я использовал:

# Replace the employer and client logos
self.replace_logos_from_source(self.source_document, self.template_doc, 0, 2)  # Employer logo
self.replace_logos_from_source(self.source_document, self.template_doc, 1, 2)  # Client logo
...