0

I'm using docx in python to create tables in a Word document. The code uses an Excel spreadsheet as input and based on the amount of information creates tables for a word document. After the table is generated I need to fill in a subset of cells, but I would like to build this into the code itself.

I found that I can use this code to target specific cells: python docx set table cell background and text color

from docx.oxml.ns import nsdecls

from docx.oxml import parse_xml

shading_elm_1 = parse_xml(r'<w:shd {} w:fill="1F5C8B"/>'.format(nsdecls('w')))
table.rows[0].cells[0]._tc.get_or_add_tcPr().append(shading_elm_1)

Unfortunately using this method requires a new element. The number and location of the cells can be defined by the code but I wouldn't know a priori how many cells there would be nor their location. My question therefore is two-fold:

  1. Is there a method that could be used to create new elements using a for loop or other method?

  2. Is there a method for calling these elements?

So for example if my excel input dictates that I will need 100 elements can a write something that creates those elements and then, how would I use a code that can call those (...use shading_elm_1 then shading_elm_2 and so on...)

Jon Heston
  • 41
  • 1
  • 6
  • Not getting what you mean by "would like to build this into the code itself". Are you saying you're looking for a streaming solution that builds the table incrementally? – scanny Jan 23 '20 at 18:00
  • Basically I want the code to be able to color the cells rather than me having to do it manually after the document is created. – Jon Heston Jan 23 '20 at 18:20
  • Is there a problem with assessing the size of the required table ahead of time and creating the table before populating it? – scanny Jan 24 '20 at 00:15
  • Thats a possibility. I could do a little pre-processing and determine that number, but even that presents a problem. The number might run as high as 100 cells. Would I have to individually create 100 elements and write code to individually assign those or is way that this can be done with a for loop? – Jon Heston Jan 24 '20 at 16:55

1 Answers1

0

I think the key would be to encapsulate the set-cell-background-shading operation in a function and then call that function for each cell that needed it. There's not enough information in your question for me to tell exactly what that would look like, which is why I was asking for clarification. But something like this might give the gist:

from docx.oxml import parse_xml
from docx.oxml.ns import nsdecls

def shade_cell(cell, fill):
    """Add a background `fill` to `cell`.

    `fill` is a 6-character str hexadecimal representation of the desired
    fill color, like "AF6342".
    """
    shd = parse_xml(r'<w:shd {0} w:fill="{1}"/>'.format(nsdecls('w'), fill))
    cell._tc.get_or_add_tcPr().append(shd)

and then elsewhere:

for i, row in enumerate(table.rows):
    for j, cell in enumerate(row.cells):
        if cell_needs_shading(i, j):
            shade_cell(cell, "1F5C8B")

You would need to provide cell_needs_shading(row, col) yourself based on your decision criteria.

Note that in general, XML elements in WordprocessingML (Word's XML vocabulary) have a prescribed sequence. So you can't always just add one (like <w:shd>) to the end of another (w:tcPr in this case) and have things work properly. If it works, fine, but if you get a repair error, this is something to look at first.

scanny
  • 26,423
  • 5
  • 54
  • 80