Python Forum
How to create a table with different sizes of columns in MS word
Thread Rating:
  • 0 Vote(s) - 0 Average
  • 1
  • 2
  • 3
  • 4
  • 5
How to create a table with different sizes of columns in MS word
#1
Hi,
I have a code to create a table in Microsoft word (see code below). The problem is that once the table is created, it has equal column size. I need a help so that the columns sizes be proportional to the content of each column of the table, but with a certain maximum. For example, with regards to the code I provide below, I want the column of the age column to be smaller than the column of Name and the column of Name be smaller than the column of Occupation and the column of Occupation to remain as it is.
from docx import Document
from docx.shared import Pt
from docx.enum.table import WD_ALIGN_VERTICAL

def create_table(data, headers, filename):
    # Create a new Word document
    doc = Document()

    # Add a table with the given number of rows and columns
    rows = len(data) + 1  # Add one for the header row
    cols = len(headers)
    table = doc.add_table(rows=rows, cols=cols)

    # Set the table style (optional)
    table.style = 'Table Grid'

    # Populate the header row
    header_cells = table.rows[0].cells
    for col_index, header_text in enumerate(headers):
        header_cells[col_index].text = header_text

    # Populate the data rows
    for row_index, row_data in enumerate(data, start=1):
        row_cells = table.rows[row_index].cells
        for col_index, cell_value in enumerate(row_data):
            row_cells[col_index].text = str(cell_value)

            # Set cell formatting (e.g., vertical alignment)
            row_cells[col_index].vertical_alignment = WD_ALIGN_VERTICAL.CENTER

    # Set column widths based on content
    for col_index in range(cols):
        max_width = max(len(str(row_data[col_index])) for row_data in data)
        
        # Set a minimum width for columns to avoid being too narrow
        min_width = Pt(1.0)
        table.columns[col_index].width = max(min_width, Pt(max_width * 1.2))  # Adjust the factor as needed

    # Save the Word document
    doc.save(filename)

# Example usage:
data = [
    ["John Doe", 30, "Engineer"],
    ["Jane Smith", 25, "Construction Designer"],
     ["Samantha", 25, "Hair worker and assistant to Manager"],
    ["Bob Johnson", 35, "Manager"]
]

headers = ["Name", "Age", "Occupation"]

create_table(data, headers, "example_table.docx")
Reply
#2
Hola Pepe!

This was tricky. Especially because, if you open this .docx file in Libre Office, the table has 3 equal-width columns! Libre Office seems to ignore the column size settings. For a while, I was going crazy!

But if you open it in MS Word the columns have the correct widths!

I just made a 1 row table to see if the column widths are set correctly.

import the imports, then just run myApp() Of course, you need to set your path for filename!

from docx import Document
from docx.shared import Pt, Mm, Cm, Inches
from docx.enum.table import WD_ALIGN_VERTICAL, WD_TABLE_ALIGNMENT

def myApp():
    filename = "/home/pedro/myPython/docxFiles/example_table.docx"
    doc = Document()
    heading = 'Shiny new but awkward table" \n\n'
    doc.add_heading(heading, 4)
    table = doc.add_table(rows=1, cols=3)
    table.alignment = WD_TABLE_ALIGNMENT.CENTER
    table.autofit = False
    table.allow_autofit = False
    table.style = 'Table Grid'
    headers = ["Name", "Age", "Occupation"]
    widths = [15.9, 11.8, 26.7]
    for col, col_data in enumerate(headers):
        print(col, col_data)
        table.cell(0,col).text = col_data
       
    for col, width in enumerate(widths):
       print(col, width)
       table.cell(0, col).width = Mm(widths[col])
    doc.save(filename)
I think you need these 2:

Quote:table.autofit = False
table.allow_autofit = False

I had to start Windows and open the file with MS Word, then it displays correctly.

Now you just need to set the column widths to the largest string length, as you were doing.

However, if a string is too long, your table will run off the page. So you better set a maximum column width, I think.
Reply
#3
Opening the code in Ms Word the table is not displaying on my side. The support from ChatGPT provided the code but with the same problem: The table has columns with equal size. see the code below from Chat GPT support:
from docx import Document
from docx.shared import Pt, Mm, Cm, Inches
from docx.enum.table import WD_ALIGN_VERTICAL, WD_TABLE_ALIGNMENT

def myApp():
    filename = r'C:\Users\mypython\Desktop\table.docx'
    doc = Document()
    heading = 'Shiny new but awkward table\n\n'
    doc.add_heading(heading, 4)
    table = doc.add_table(rows=1, cols=3)
    table.alignment = WD_TABLE_ALIGNMENT.CENTER
    table.autofit = False
    table.allow_autofit = False
    table.style = 'Table Grid'
    headers = ["Name", "Age", "Occupation"]
    widths = [15.9, 11.8, 26.7]
    
    for col, col_data in enumerate(headers):
        print(col, col_data)
        table.cell(0, col).text = col_data

    for col, width in enumerate(widths):
        print(col, width)
        table.columns[col].width = Mm(width)

    doc.save(filename)

myApp()
Reply
#4
Oh dear!

Don't open the code in MS Word, open the file it produces!

I checked again. I started Windows 11 and opened the file from my usb stick.

The table is centred and has 3 different column widths.

Sorry, I don't know how to attach the actual file I created so you could open it.
Reply
#5
I don't know why widths can be proportional on one computer but not proportional on another computer. Also the thumbnail created by python code is different from the thumbnail of a word document I create manually. I searched the program name and found it is Microsoft Macintosh Word whereas the word document I created manually is Microsoft Office Word, is it the cause of the problem?
Reply
#6
Sorry, no idea about why.

I just changed the widths list to 30, 15, 60

Opened the file in windows, displays perfectly
Reply
#7
Walking along the beach today, I had an idea:

Make a copy of your file but rename it example.zip Open the zip file.

MS Office files are zip files. XML does the work of formatting.

You will see 2 folders and 7 .xml files which carry the settings for everything!

Even a very simple file has very long xml files.

I can't easily find the settings for the table, but there are many.

I also don't know which file has priority for the settings of a table. Logically, you might think the setting would be in settings.xml.

Maybe you can find them!

This bit near the top of document.xml looks like the cause of the problem of equal-width columns:

<w:tblGrid>
<w:gridCol w:w="2880"/>
<w:gridCol w:w="2880"/>
<w:gridCol w:w="2880"/>
</w:tblGrid>
But, as I said, in my Windows 11 and using Word, I see the table displayed correctly, so there must be a setting that overrrides these settings!

Maybe that will at least put you on the road to a solution!
Reply
#8
I manually set the table column widths to "minimise column widths" in Libre Office.

Then, if I open the Word document with a zip archive manager and look in the folder word at document.xml, the values are:

<w:tblGrid>
<w:gridCol w:w="900"/>
<w:gridCol w:w="667"/>
<w:gridCol w:w="1510"/>
</w:tblGrid>
So, you could open this xml file and manipulate these settings directly in Python.
Reply
#9
This myApp() manipulates the document.xml file within the zip file, which is the .docx file, and creates a new file with different column widths, which actually show up in Libre Office!

Haven't tried to open the new file in Windows. I'm happy if it works in Libre Office!

Word is putting the settings somewhere else, not in document.xml, because, when I open the file in Windows, without manipulating document.xml, it displays correctly. This may be a version problem.

Maybe someone here could help me tidy this up a bit?? I never worked with zipfile before!

from zipfile import ZipFile 
import os
import re

# got to change the overall table width too!
def myApp():
    path2files = "/home/pedro/myPython/zipfile/"
    # change to zfile directory
    os.chdir(path2files)
    # specifying the zip file name 
    zfile = "example_copy.docx"
    # opening the zip file in READ mode
    # closes automatically
    with ZipFile(zfile, 'r') as zf: 
        # printing all the contents of the zip file 
        zf.printdir()
        # extracting document.xml
        zf.extract('word/document.xml')
        # returns bytes
        docdata = zf.read('word/document.xml')
        print(f'opened the zip file word/document.xml as {type(docdata)}') 

    # set new column sizes
    newcolsizes = [500, 300, 600]
    twidth = sum(newcolsizes)
    # search pattern
    regex1 = b'<w:tblW w:w="\d+" w:type="dxa"/>'
    # set the table width to twidth which is: sum(newcolsizes) (only 1 of these, so far, could be more +2500   
    matches = re.finditer(regex1, docdata, re.MULTILINE)
    for num, match in enumerate(matches):
        print(num, match.group(), type(match[0]))
        # I don't understand what the numbers mean, maybe Pt
        # but trial and error shows with these column sizes +2500 works
        widthstring = str(twidth + 2500)
        print(f'widthstring is {widthstring}, type(widthstring) is {type(widthstring)}')
        byte_val = widthstring.encode()
        print(f'byte_val is {byte_val}, type(byte_val) is {type(byte_val)}')
        # pattern must be bytes or you get an error: b'\d+'
        newbytes = re.sub(b'\d+', byte_val, match[0], flags=0)
        print(newbytes)
        newdata = re.sub(match[0], newbytes, docdata, count=1)
        # reassign or only the last one will be changed
        docdata = newdata
    # find the column size lines in docdata and change them to the desired values from the list newcolsizes
    regex2 = b'<w:gridCol w:w="\d+"/>'
    matches = re.finditer(regex2, docdata, re.MULTILINE)
    for num, match in enumerate(matches):
        print(num, match.group(), type(match[0]))
        widthstring = str(newcolsizes[num])
        print(f'widthstring is {widthstring}, type(widthstring) is {type(widthstring)}')
        byte_val = widthstring.encode()
        print(f'byte_val is {byte_val}, type(byte_val) is {type(byte_val)}')
        # pattern must be bytes or you get an error: b'\d+'
        newbytes = re.sub(b'\d+', byte_val, match[0], flags=0)
        print(newbytes)
        newdata = re.sub(match[0], newbytes, docdata, count=1)
        # reassign or only the last one will be changed
        docdata = newdata

    # reopen to create a new file which doesn't have word/document.xml
    # basically, make a copy but leave out the original word/document.xml 
    # then add docdata from above to the new zip file as word/document.xml 
    zin = ZipFile (zfile, 'r')
    zout = ZipFile ('example_copy2.docx', 'w')
    for item in zin.infolist():
        print(item.filename)
        if item.filename != 'word/document.xml':
            buffer = zin.read(item.filename)
            zout.writestr(item, buffer)
        elif item.filename == 'word/document.xml':
            zout.writestr(item, docdata)
    # close the files
    # seems to work. At least displays correctly in Libre Office, have not tried in Windows
    zin.close()
    zout.close()
If there are several tables, this may get more complicated!
Reply


Possibly Related Threads…
Thread Author Replies Views Last Post
  Create Choices from .ods file columns cspower 3 614 Dec-28-2023, 09:59 PM
Last Post: deanhystad
  Create csv file with 4 columns for process mining thomaskissas33 3 759 Nov-06-2023, 09:36 PM
Last Post: deanhystad
Thumbs Up Convert word into pdf and copy table to outlook body in a prescribed format email2kmahe 1 759 Sep-22-2023, 02:33 PM
Last Post: carecavoador
  Printing effect sizes for variables in an anova eyavuz21 2 990 Feb-01-2023, 02:12 PM
Last Post: eyavuz21
  Output difference from 2 lists of different sizes with words gracenz 5 1,323 Sep-02-2022, 05:09 PM
Last Post: Larz60+
  group by create pivot table python dawid294 1 1,305 Jun-22-2022, 06:13 PM
Last Post: Larz60+
  Sum the values in a pandas pivot table specific columns klllmmm 1 4,651 Nov-19-2021, 04:43 PM
Last Post: klllmmm
Question Problem: Check if a list contains a word and then continue with the next word Mangono 2 2,517 Aug-12-2021, 04:25 PM
Last Post: palladium
  SaltStack: MySQL returner save less data into Database table columns xtc14 2 2,183 Jul-02-2021, 02:19 PM
Last Post: xtc14
  Create SQLite columns from a list or tuple? snakes 6 8,733 May-04-2021, 12:06 PM
Last Post: snakes

Forum Jump:

User Panel Messages

Announcements
Announcement #1 8/1/2020
Announcement #2 8/2/2020
Announcement #3 8/6/2020