0

I'm using python-docx (v1.1.2) and Python 3.11.3 to work on a tool to fix a bunch of Word documents automatically. I've been able to update fonts, titles, texts, headers and footers and tables (their borders).

However, some tables, and I haven't found the pattern of why this happens, are not updated. This is the snippet I've developed.

def update_borders(table):
    #namespaces used when updating the elements (the changes are not persistent if not used)
    NSMAP = {"w": "http://schemas.openxmlformats.org/wordprocessingml/2006/main"}
    WORD_NS = NSMAP["w"]
    # sample color map
    BORDER_COLOR_MAP = { "auto": "F07E26" }
    small_table = len(table.rows) == 1 and len(table.columns) == 2
    tbl_xml = table._element
    tbl_pr = tbl_xml.find(f'{{{WORD_NS}}}tblPr')
    if tbl_pr is None:
        tbl_pr = etree.SubElement(tbl_xml, f"{{{WORD_NS}}}tblPr")
    #Ensure tblBorders exists
    tbl_borders = tbl_pr.find(f'{{{WORD_NS}}}tblBorders')
    # If the table doesn't have a tblPr object (everything is set to "auto"), we create it
    if tbl_borders is None:
        tbl_borders = etree.SubElement(tbl_pr, f"{{{WORD_NS}}}tblBorders")
        if small_table:
            border_sides = ["top", "left", "bottom", "right"]
        else:
            border_sides = ["top", "left", "bottom", "right", "insideH", "insideV"]
        for side in border_sides:
            border = etree.SubElement(tbl_borders, f"{{{WORD_NS}}}{side}")
            border.set(f"{{{WORD_NS}}}val", "single")
            border.set(f"{{{WORD_NS}}}sz", "4")
            border.set(f"{{{WORD_NS}}}space", "0")
            border.set(f"{{{WORD_NS}}}color", "F07E26")
    else:
        # If there is a tblPr object, we update the color based in a map of colors
        for border in tbl_borders:
            border_color = border.attrib[f"{{{WORD_NS}}}color"]
            if (small_table and border_color != "auto") or not small_table:
                border.set(f"{{{WORD_NS}}}color", BORDER_COLOR_MAP[border.attrib[f"{{{WORD_NS}}}color"]])

While this script works for most of the tables, I've found some tables in which it doesn't. This table for instance.

 <w:tbl xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main" xmlns:wpc="http://schemas.microsoft.com/office/word/2010/wordprocessingCanvas" xmlns:cx="http://schemas.microsoft.com/office/drawing/2014/chartex" xmlns:cx1="http://schemas.microsoft.com/office/drawing/2015/9/8/chartex" xmlns:cx2="http://schemas.microsoft.com/office/drawing/2015/10/21/chartex" xmlns:cx3="http://schemas.microsoft.com/office/drawing/2016/5/9/chartex" xmlns:cx4="http://schemas.microsoft.com/office/drawing/2016/5/10/chartex" xmlns:cx5="http://schemas.microsoft.com/office/drawing/2016/5/11/chartex" xmlns:cx6="http://schemas.microsoft.com/office/drawing/2016/5/12/chartex" xmlns:cx7="http://schemas.microsoft.com/office/drawing/2016/5/13/chartex" xmlns:cx8="http://schemas.microsoft.com/office/drawing/2016/5/14/chartex" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:aink="http://schemas.microsoft.com/office/drawing/2016/ink" xmlns:am3d="http://schemas.microsoft.com/office/drawing/2017/model3d" xmlns:o="urn:schemas-microsoft-com:office:office" xmlns:oel="http://schemas.microsoft.com/office/2019/extlst" xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships" xmlns:m="http://schemas.openxmlformats.org/officeDocument/2006/math" xmlns:v="urn:schemas-microsoft-com:vml" xmlns:wp14="http://schemas.microsoft.com/office/word/2010/wordprocessingDrawing" xmlns:wp="http://schemas.openxmlformats.org/drawingml/2006/wordprocessingDrawing" xmlns:w10="urn:schemas-microsoft-com:office:word" xmlns:w14="http://schemas.microsoft.com/office/word/2010/wordml" xmlns:w15="http://schemas.microsoft.com/office/word/2012/wordml" xmlns:w16cex="http://schemas.microsoft.com/office/word/2018/wordml/cex" xmlns:w16cid="http://schemas.microsoft.com/office/word/2016/wordml/cid" xmlns:w16="http://schemas.microsoft.com/office/word/2018/wordml" xmlns:w16du="http://schemas.microsoft.com/office/word/2023/wordml/word16du" xmlns:w16sdtdh="http://schemas.microsoft.com/office/word/2020/wordml/sdtdatahash" xmlns:w16sdtfl="http://schemas.microsoft.com/office/word/2024/wordml/sdtformatlock" xmlns:w16se="http://schemas.microsoft.com/office/word/2015/wordml/symex" xmlns:wpg="http://schemas.microsoft.com/office/word/2010/wordprocessingGroup" xmlns:wpi="http://schemas.microsoft.com/office/word/2010/wordprocessingInk" xmlns:wne="http://schemas.microsoft.com/office/word/2006/wordml" xmlns:wps="http://schemas.microsoft.com/office/word/2010/wordprocessingShape">\n
<w:tblPr>\n
    <w:tblStyle w:val="Tablaconcuadrcula"/>\n
    <w:tblW w:w="9684" w:type="dxa"/>\n
    <w:jc w:val="center"/>\n
    <w:tblLook w:val="04A0" w:firstRow="1" w:lastRow="0" w:firstColumn="1" 
w:lastColumn="0" w:noHBand="0" w:noVBand="1"/>\n
</w:tblPr>\n
....
<w:tc>\n
        <w:tcPr>\n
            <w:tcW w:w="8062" w:type="dxa"/>\n
            <w:tcBorders>\n
                <w:top w:val="single" w:sz="4" w:space="0" w:color="auto"/>\n
                <w:left w:val="single" w:sz="4" w:space="0" w:color="auto"/>\n
                <w:bottom w:val="single" w:sz="4" w:space="0" w:color="auto"/>\n
                <w:right w:val="single" w:sz="4" w:space="0" w:color="auto"/>\n
            </w:tcBorders>\n
            <w:shd w:val="clear" w:color="auto" w:fill="FDE7D4"/>\n
            <w:hideMark/>\n
        </w:tcPr>\n

I noticed that the table doesn't have a tblBorders inside of tblPr, but that every cell has the same style set individually. However I don't understand that even when I set it manually (with etree.SubElement(tbl_pr, f"{{{WORD_NS}}}tblBorders")), if I save that document, and load it again with python-docx, the properties are not there. I assume one alternative could be to edit all the cell individually with a loop, but I don't see why this doesn't work.

2
  • Could table be set to read only? Are table that work and do not work in the same docx file? Check permission of the file. Commented Mar 21 at 13:50
  • The table is editable and has the same permissions that the word file it is on Commented Mar 22 at 10:18

1 Answer 1

0

Not clear, if I understood your question correctly. You have also update cell level borders not only table level borders. You have to iterate over each cell (<w:tc>) and modify the borders inside <w:tcPr>, too.

I added some code, which should do this, if I understood your question right.

from lxml import etree

def update_borders(table):
    NSMAP = {"w": "http://schemas.openxmlformats.org/wordprocessingml/2006/main"}
    WORD_NS = NSMAP["w"]
    BORDER_COLOR_MAP = {"auto": "F07E26"}

    small_table = len(table.rows) == 1 and len(table.columns) == 2
    tbl_xml = table._element
    tbl_pr = tbl_xml.find(f'{{{WORD_NS}}}tblPr')

    if tbl_pr is None:
        tbl_pr = etree.SubElement(tbl_xml, f"{{{WORD_NS}}}tblPr")

    tbl_borders = tbl_pr.find(f'{{{WORD_NS}}}tblBorders')
    if tbl_borders is None:
        tbl_borders = etree.SubElement(tbl_pr, f"{{{WORD_NS}}}tblBorders")
        border_sides = ["top", "left", "bottom", "right"] if small_table else ["top", "left", "bottom", "right", "insideH", "insideV"]
        for side in border_sides:
            border = etree.SubElement(tbl_borders, f"{{{WORD_NS}}}{side}")
            border.set(f"{{{WORD_NS}}}val", "single")
            border.set(f"{{{WORD_NS}}}sz", "4")
            border.set(f"{{{WORD_NS}}}space", "0")
            border.set(f"{{{WORD_NS}}}color", "F07E26")
    else:
        for border in tbl_borders:
            border_color = border.attrib.get(f"{{{WORD_NS}}}color", "auto")
            if (small_table and border_color != "auto") or not small_table:
                border.set(f"{{{WORD_NS}}}color", BORDER_COLOR_MAP.get(border_color, "F07E26"))

    # Update cell-level borders
    for row in table.rows:
        for cell in row.cells:
            cell_xml = cell._element
            tc_pr = cell_xml.find(f'.//{{{WORD_NS}}}tcPr')
            if tc_pr is not None:
                tc_borders = tc_pr.find(f'{{{WORD_NS}}}tcBorders')
                if tc_borders is None:
                    tc_borders = etree.SubElement(tc_pr, f"{{{WORD_NS}}}tcBorders")
                for border in tc_borders:
                    border_color = border.attrib.get(f"{{{WORD_NS}}}color", "auto")
                    border.set(f"{{{WORD_NS}}}color", BORDER_COLOR_MAP.get(border_color, "F07E26"))
Sign up to request clarification or add additional context in comments.

3 Comments

My point and my question is WHY can't I set tblBorders on that table? I did a test updating borders cell by cell, and while that works, seems "rough". I want an explanation or theory on why creating a tblBorder wouldn't work here while it does in the rest of tables of the document
@J.Maria Even though you’re adding <w:tblBorders> inside <w:tblPr>, Word may prioritize the <w:tcBorders> settings from individual cells, which override table-level settings.
I feel like it should at least let me save it and see them in the XML of the new Word, rather than just ignore my attempt at setting them

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.