Skip to content

Only first embedded Excel object is openable in generated Word document #621

Description

@a77goddess

Describe the bug

When using docxtpl.DocxTemplate to generate a Word document containing multiple embedded Excel files through sub-documents, only the first embedded Excel object can be opened normally.
All others appear visually correct but cannot be opened (no response when double-clicked), and the context menu lacks the “Worksheet Object” option.

To Reproduce

import os
import openpyxl
from docxtpl import DocxTemplate


class ExcelOperator(object):
    def __init__(self, excel_file_path):
        self.excel_file_path = excel_file_path
        self.headers = ["Column1", "Column2", "Column3", "Column4", "Column5"]

    def _create_excel(self):
        wb = openpyxl.Workbook()
        ws = wb.active
        ws.append(self.headers)
        return wb, ws

    def write_excel(self, data, sheet_name="DataSheet"):
        work_book, work_sheet = self._create_excel()
        work_sheet.title = sheet_name
        for item in data:
            work_sheet.append(item)
        work_book.save(self.excel_file_path)


class WordOperator(object):
    def __init__(self, main_tpl_file_path, sub_tpl_file_path, output_file_path):
        self.current_path = os.path.dirname(os.path.abspath(__file__))
        self.main_tpl_file_path = main_tpl_file_path
        self.sub_tpl_file_path = sub_tpl_file_path
        self.output_file_path = output_file_path

    def _build_sub_excel_docx(self, index, item_data):
        # Generate Excel data file
        excel_file_path = os.path.join(self.current_path, f"output/0.tmp_excel_{index}.xlsx")
        excel_operator = ExcelOperator(excel_file_path)
        excel_operator.write_excel(item_data["data"], item_data["name"])
        sub_docx_file_path = os.path.join(self.current_path, f"output/0.tmp_sub_doc_{index}.docx")
        # Load sub template file and replace the embedded Excel file with the newly generated Excel data file
        sub_docx = DocxTemplate(self.sub_tpl_file_path)
        sub_docx.replace_zipname('word/embeddings/Microsoft_Excel_Worksheet.xlsx', excel_file_path)
        sub_docx.save(sub_docx_file_path)
        return sub_docx_file_path

    def write_word(self, data_list):
        # Create main template file
        main_ctx = {
            "sub_docs": []
        }
        main_docx = DocxTemplate(self.main_tpl_file_path)
        # Cycle through data list to create sub documents
        for index, item in enumerate(data_list):
            # build sub document with embedded Excel
            sub_docx_file_path = self._build_sub_excel_docx(index, item)
            # load sub document into main document context
            sub_doc_tpl = main_docx.new_subdoc(sub_docx_file_path)
            main_ctx["sub_docs"].append(sub_doc_tpl)
        # Render and save main document
        main_docx.render(main_ctx)
        main_docx.save(self.output_file_path)


if __name__ == "__main__":
    data_list = [
        {"name": "No.1", "data": [["1-1", "1-2", "1-3", "1-4", "1-5"]]},
        {"name": "No.2", "data": [["2-1", "2-2", "2-3", "2-4", "2-5"]]},
        {"name": "No.3", "data": [["3-1", "3-2", "3-3", "3-4", "3-5"]]},
        {"name": "No.4", "data": [["4-1", "4-2", "4-3", "4-4", "4-5"]]},
        {"name": "No.5", "data": [["5-1", "5-2", "5-3", "5-4", "5-5"]]},
        {"name": "No.6", "data": [["6-1", "6-2", "6-3", "6-4", "6-5"]]},
        {"name": "No.7", "data": [["7-1", "7-2", "7-3", "7-4", "7-5"]]}
    ]
    current_path = os.path.dirname(os.path.abspath(__file__))
    # Generate Word document with embedded Excel files
    main_template_file_path = os.path.join(current_path, f"templates/tpl_demo_doc_main_v3.docx")
    sub_template_file_path = os.path.join(current_path, f"templates/tpl_demo_doc_sub_excel.docx")
    result_file_path = os.path.join(current_path, f"output/demo_doc_with_excel.docx")
    word_operator = WordOperator(main_template_file_path, sub_template_file_path, result_file_path)
    word_operator.write_word(data_list)

Expected behavior

All embedded Excel objects should behave like the first one — openable by double-click and recognized as valid “Worksheet Object” embeddings.

Screenshots

Image

Additional context

  • The generated main document contains 7 embedded Excel icons (verified inside the .docx archive at word/embeddings/):
    Microsoft_Excel_Worksheet1.xlsx
    Microsoft_Excel_Worksheet2.xlsx
    Microsoft_Excel_Worksheet3.xlsx
    Microsoft_Excel_Worksheet4.xlsx
    Microsoft_Excel_Worksheet5.xlsx
    Microsoft_Excel_Worksheet6.xlsx
    Microsoft_Excel_Worksheet7.xlsx
  • All 7 .xlsx files in the word/embeddings/ folder are valid and open normally if extracted.
  • However:
    • Only the first embedded Excel object inside the final .docx opens correctly when double-clicked.
    • The rest cannot be opened and show no context menu entry for “Worksheet Object”.
  • See the compressed package for code and running result files docx_demo_v2.tar.gz

docx_demo_v2.tar.gz

Environment

Python 3.11.3
docxtpl 0.20.1
python-docx 1.2.0
openpyxl 3.1.2

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions